diff --git a/.bundle/config b/.bundle/config deleted file mode 100644 index 9bc01b4c32..0000000000 --- a/.bundle/config +++ /dev/null @@ -1,3 +0,0 @@ ---- -BUNDLE_PATH: "vendor/bundle" -BUNDLE_DISABLE_SHARED_GEMS: "true" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 808f492a7d..e56f07a0ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,16 +2,16 @@ name: Build on: [push, pull_request] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6.9 + ruby-version: 3.2.6 bundler-cache: true - name: Set up coursier - uses: coursier/setup-action@v1.2.0-M2 + uses: coursier/setup-action@v1.3.5 with: jvm: adopt:11 - name: Run mdoc @@ -25,8 +25,9 @@ jobs: # # Checking for docs.scala-lang/blob/main leads to a chicken and egg problem because of the edit links of new pages. bundle exec htmlproofer ./_site/\ --only-4xx\ - --http-status-ignore "400,401,403,429"\ - --empty-alt-ignore\ + --ignore-status-codes "400,401,403,429"\ + --ignore-empty-alt\ --allow-hash-href\ - --url-ignore '/https://github.com/scala/docs.scala-lang/blob/main/.*/,/www.oracle.com/' + --no-enforce-https\ + --ignore-urls '/https://github.com/scala/,/www.oracle.com/' diff --git a/.gitignore b/.gitignore index e3f650f0b3..055aee462d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ vendor/bundle .idea/ /coursier .sass-cache/ +.jekyll-cache/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2fe3d70737..b2bbc255f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,12 @@ -FROM ruby:2.6 +FROM ruby:3.2.6 -RUN gem install bundler jekyll +RUN gem install bundler:2.6.5 WORKDIR /srv/jekyll COPY Gemfile . COPY Gemfile.lock . +RUN echo -n "bundle version: " && bundle --version +RUN chmod u+s /bin/chown RUN bundle install - diff --git a/Gemfile b/Gemfile index 2152cdafa4..31cb37fbea 100644 --- a/Gemfile +++ b/Gemfile @@ -1,17 +1,6 @@ source 'https://rubygems.org' -gem 'jekyll-redirect-from' -gem 'jekyll-scalafiddle' +gem 'github-pages' +gem 'webrick' +# gem 'html-proofer' -gem 'kramdown-parser-gfm' -# gem 'html-proofer' # link-checking: bundle exec htmlproofer ./_site/ --only-4xx --empty-alt-ignore --allow-hash-href - -# 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 - -gem "webrick", "~> 1.7" +# gem 'html-proofer' # link-checking: bundle exec htmlproofer ./_site/ --only-4xx --ignore-empty-alt=true --allow-hash-href=true diff --git a/Gemfile.lock b/Gemfile.lock index 5b52d7697a..8088be3873 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,34 +1,146 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + Ascii85 (2.0.1) + activesupport (8.0.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + afm (0.2.2) + async (2.23.0) + console (~> 1.29) + fiber-annotation + io-event (~> 1.9) + metrics (~> 0.12) + traces (~> 0.15) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.1.9) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) colorator (1.1.0) - concurrent-ruby (1.1.8) - em-websocket (0.5.2) + commonmarker (0.23.11) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) + console (1.29.3) + fiber-annotation + fiber-local (~> 1.1) + json + csv (3.3.2) + dnsruby (1.72.3) + base64 (~> 0.2.0) + simpleidn (~> 0.2.1) + drb (2.2.1) + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.12.0) - ffi (>= 1.3.0) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) - ffi (1.15.0) + execjs (2.10.0) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.0) + net-http (>= 0.5.0) + ffi (1.17.1-arm64-darwin) + ffi (1.17.1-x64-mingw-ucrt) + ffi (1.17.1-x86_64-linux-gnu) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.0) forwardable-extended (2.6.0) - html-proofer (3.15.3) - addressable (~> 2.3) + gemoji (4.1.0) + github-pages (232) + github-pages-health-check (= 1.18.2) + jekyll (= 3.10.0) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.5.1) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.16.1) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.13.0) + kramdown (= 2.4.0) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) mercenary (~> 0.3) - nokogumbo (~> 2.0) - parallel (~> 1.3) + minima (= 2.5.1) + nokogiri (>= 1.16.2, < 2.0) + rouge (= 3.30.0) + terminal-table (~> 1.4) + webrick (~> 1.8) + github-pages-health-check (1.18.2) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) + typhoeus (~> 1.3) + hashery (2.1.2) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + html-proofer (5.0.10) + addressable (~> 2.3) + async (~> 2.1) + nokogiri (~> 1.13) + pdf-reader (~> 2.11) rainbow (~> 3.0) typhoeus (~> 1.3) yell (~> 2.0) - http_parser.rb (0.6.0) - i18n (0.9.5) + zeitwerk (~> 2.5) + http_parser.rb (0.8.0) + i18n (1.14.7) concurrent-ruby (~> 1.0) - jekyll (3.9.1) + io-event (1.9.0) + jekyll (3.10.0) addressable (~> 2.4) colorator (~> 1.0) + csv (~> 3.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -37,60 +149,185 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) + webrick (>= 1.0) + jekyll-avatar (0.8.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.2.2) + coffee-script (~> 2.2) + coffee-script-source (~> 1.12) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.5.1) + commonmarker (>= 0.23.7, < 1.1.0) + jekyll (>= 3.9, < 4.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.16.1) + jekyll (>= 3.4, < 5.0) + octokit (>= 4, < 7, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) jekyll-redirect-from (0.16.0) jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-scalafiddle (1.0.1) - jekyll (~> 3.0) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - kramdown (2.3.1) + jemoji (0.13.0) + gemoji (>= 3, < 5) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + json (2.10.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.5.1) + liquid (4.0.4) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.6) mercenary (0.3.6) - mini_portile2 (2.8.0) - nokogiri (1.13.3) - mini_portile2 (~> 2.8.0) + metrics (0.12.1) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.25.4) + net-http (0.6.0) + uri + nokogiri (1.18.8-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.8-x64-mingw-ucrt) racc (~> 1.4) - nokogumbo (2.0.2) - nokogiri (~> 1.8, >= 1.8.4) - parallel (1.19.2) + nokogiri (1.18.8-x86_64-linux-gnu) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.6) - racc (1.6.0) - rainbow (3.0.0) - rb-fsevent (0.11.0) - rb-inotify (0.10.1) + pdf-reader (2.14.1) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk + public_suffix (5.1.1) + racc (1.8.1) + rainbow (3.1.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.2.5) - rouge (3.26.0) + rexml (3.4.1) + rouge (3.30.0) + ruby-rc4 (0.1.5) + rubyzip (2.4.1) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - typhoeus (1.4.0) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + securerandom (0.4.1) + simpleidn (0.2.3) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + traces (0.15.2) + ttfunk (1.8.0) + bigdecimal (~> 3.1) + typhoeus (1.4.1) ethon (>= 0.9.0) - webrick (1.7.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (1.8.0) + uri (1.0.3) + webrick (1.9.1) yell (2.2.2) + zeitwerk (2.7.2) PLATFORMS - ruby + arm64-darwin-22 + arm64-darwin-23 + arm64-darwin-24 + x64-mingw-ucrt + x86_64-linux DEPENDENCIES + github-pages html-proofer - jekyll-redirect-from - jekyll-scalafiddle - kramdown-parser-gfm - webrick (~> 1.7) + webrick BUNDLED WITH - 2.3.6 + 2.6.5 diff --git a/README.md b/README.md index fe377e9f6b..013a66267c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,52 @@ # Scala Documentation # -[![Build Status](https://ci.scala-lang.org/api/badges/scala/docs.scala-lang/status.svg)](https://platform-ci.scala-lang.org/scala/docs.scala-lang) +[![Build Status](https://github.com/scala/docs.scala-lang/actions/workflows/build.yml/badge.svg)](https://github.com/scala/docs.scala-lang/actions/workflows/build.yml?query=branch%3Amain) This repository contains the source for the Scala documentation website, as well as the source for "Scala Improvement Process" (SIP) documents. +## Dependencies ## + +This site uses a Jekyll, a Ruby framework. You'll need Ruby and Bundler installed; see [Jekyll installation instructions](https://jekyllrb.com/docs/installation/) for the details. + ## Quickstart ## To build and view the site locally: - gem install --user-install bundler jekyll + bundle install bundle exec jekyll serve -I ([Trouble on MacOS?](https://github.com/scala/docs.scala-lang/issues/1150)) For more details, read on. -## Quickstart with Docker ## +## Quickstart with Docker Compose ## + +You need to have [Docker Engine](https://docs.docker.com/engine/) and [Docker Compose](https://docs.docker.com/compose/) installed on your machine. +Under macOS (Intel or Apple silicon), instead of installing [Docker Desktop](https://docs.docker.com/desktop/) you can also use [HomeBrew](https://brew.sh/) with [Colima](https://github.com/abiosoft/colima): `brew install colima docker docker-compose`. +UID and GID environment variables are needed to avoid docker from writing files as root in your directory. +By default, docker-compose will use the file docker-compose.yml which will build the website and serve it on 0.0.0.0:4000 . +If you just need to build the website, add ```-f docker-compose_build-only.yml``` + +``` +env UID="$(id -u)" GID="$(id -g)" docker-compose up +``` -To build and view site with Docker: +The generated site is available at `http://localhost:4000`. - docker-compose up +When the website dependencies change (the content of the `Gemfile`), +you have to re-build the Docker image: -It will incrementally build and serve site at `http://localhost:4000`. +``` +env UID="$(id -u)" GID="$(id -g)" docker-compose up --build +``` -For more details on the Docker option, see [this issue](https://github.com/scala/docs.scala-lang/issues/1286). +If you have problems with the Docker image or want to force the rebuild of the Docker image: +``` +env UID="$(id -u)" GID="$(id -g)" docker-compose build --no-cache +``` + + +For more details on the Docker option, see also [this issue](https://github.com/scala/docs.scala-lang/issues/1286). ## Contributing ## @@ -34,10 +57,6 @@ Small changes, or corrected typos will generally be pulled in right away. Large existing documents will be thoroughly reviewed-- please keep in mind that, generally, new documents must be very well-polished, complete, and maintained in order to be accepted. -## Dependencies ## - -This site uses a Jekyll, a Ruby framework. You'll need Ruby and Bundler installed; see [Jekyll installation instructions](https://jekyllrb.com/docs/installation/) for the details. - ## Building & Viewing ## cd into the directory where you cloned this repository, then install the required gems with `bundle install`. This will automatically put the gems into `./vendor/bundle`. diff --git a/_ba/tour/automatic-closures.md b/_ba/tour/automatic-closures.md deleted file mode 100644 index 90f751ee2c..0000000000 --- a/_ba/tour/automatic-closures.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: tour -title: Automatic Type-Dependent Closure Construction -partof: scala-tour - -language: ba ---- diff --git a/_ba/tour/basics.md b/_ba/tour/basics.md index bff0c133c4..97956e6149 100644 --- a/_ba/tour/basics.md +++ b/_ba/tour/basics.md @@ -14,9 +14,9 @@ Na ovoj stranici ćemo objasniti osnove Scale. ## Probavanje Scale u browseru -Scalu možete probati u Vašem browser sa ScalaFiddle aplikacijom. +Scalu možete probati u Vašem browser sa Scastie aplikacijom. -1. Idite na [https://scalafiddle.io](https://scalafiddle.io). +1. Idite na [Scastie](https://scastie.scala-lang.org/). 2. Zalijepite `println("Hello, world!")` u lijevi panel. 3. Kliknite "Run" dugme. Izlaz će se pojaviti u desnom panelu. @@ -46,7 +46,7 @@ val x = 1 + 1 println(x) // 2 ``` -Imenovani rezultati, kao `x` ovdje, nazivaju se vrijednostima. +Imenovani rezultati, kao `x` ovdje, nazivaju se vrijednostima. Referenciranje vrijednosti ne okida njeno ponovno izračunavanje. Vrijednosti se ne mogu mijenjati. @@ -61,7 +61,7 @@ Tipovi vrijednosti mogu biti (automatski) zaključeni, ali možete i eksplicitno val x: Int = 1 + 1 ``` -Primijetite da deklaracija tipa `Int` dolazi nakon identifikatora `x`. Također morate dodati i `:`. +Primijetite da deklaracija tipa `Int` dolazi nakon identifikatora `x`. Također morate dodati i `:`. ### Varijable @@ -157,7 +157,6 @@ Postoje i neke druge razlike, ali zasad, možete misliti o njima kao nečemu sli Metode mogu imati višelinijske izraze također. -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -165,7 +164,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} Zadnjo izraz u tijelu metode je povratna vrijednost metode. (Scala ima ključnu riječ `return`, ali se rijetko koristi.) @@ -179,9 +177,9 @@ class Greeter(prefix: String, suffix: String) { println(prefix + name + suffix) } ``` -Povratni tip metode `greet` je `Unit`, koji kaže da metoda ne vraća ništa značajno. -Koristi se slično kao `void` u Javi ili C-u. -(Razlika je u tome što svaki Scalin izraz mora imati neku vrijednost, postoji singlton vrijednost tipa `Unit`, piše se `()`. +Povratni tip metode `greet` je `Unit`, koji kaže da metoda ne vraća ništa značajno. +Koristi se slično kao `void` u Javi ili C-u. +(Razlika je u tome što svaki Scalin izraz mora imati neku vrijednost, postoji singlton vrijednost tipa `Unit`, piše se `()`. Ne prenosi nikakvu korisnu informaciju.) Instancu klase možete kreirati pomoću ključne riječi `new`. @@ -195,7 +193,7 @@ Detaljniji pregled klasa biće dat [kasnije](classes.html). ## Case klase -Scala ima poseban tip klase koji se zove "case" klasa. +Scala ima poseban tip klase koji se zove "case" klasa. Po defaultu, case klase su nepromjenjive i porede se po vrijednosti. Možete ih definisati s `case class` ključnim riječima. ```scala mdoc @@ -214,15 +212,15 @@ I porede se po vrijednosti. ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$point and $anotherPoint are different.") } // Point(1,2) i Point(1,2) su iste. if (point == yetAnotherPoint) { - println(point + " and " + yetAnotherPoint + " are the same.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) su Point(2,2) različite. ``` @@ -301,7 +299,7 @@ Trejtove ćemo pokriti u dubinu [kasnije](traits.html). ## Glavna metoda -Glavna metoda je ulazna tačka programa. +Glavna metoda je ulazna tačka programa. Java Virtuelna Mašina traži da se glavna metoda zove `main` i da prima jedan argument, niz stringova. Koristeći objekt, možete definisati glavnu metodu ovako: diff --git a/_ba/tour/extractor-objects.md b/_ba/tour/extractor-objects.md index ac948e255d..0d0618aa00 100644 --- a/_ba/tour/extractor-objects.md +++ b/_ba/tour/extractor-objects.md @@ -11,7 +11,7 @@ previous-page: regular-expression-patterns --- Ekstraktor objekat je objekat koji ima `unapply` metodu. -Dok je `apply` metoda kao konstruktor koji uzima argumente i kreira objekat, `unapply` metoda prima objekat i pokušava vratiti argumente. +Dok je `apply` metoda kao konstruktor koji uzima argumente i kreira objekat, `unapply` metoda prima objekat i pokušava vratiti argumente. Ovo se najčešće koristi u podudaranju uzoraka i parcijalnim funkcijama. ```scala mdoc @@ -19,7 +19,7 @@ import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val name = customerID.split("--").head @@ -34,9 +34,9 @@ customer1ID match { } ``` -Metoda `apply` kreira `CustomerID` string od argumenta `name`. -Metoda `unapply` radi suprotno da dobije `name` nazad. -Kada pozovemo `CustomerID("Sukyoung")`, to je skraćena sintaksa za `CustomerID.apply("Sukyoung")`. +Metoda `apply` kreira `CustomerID` string od argumenta `name`. +Metoda `unapply` radi suprotno da dobije `name` nazad. +Kada pozovemo `CustomerID("Sukyoung")`, to je skraćena sintaksa za `CustomerID.apply("Sukyoung")`. Kada pozovemo `case CustomerID(name) => customer1ID`, ustvari pozivamo `unapply` metodu. Metoda `unapply` se može koristiti i za dodjelu vrijednosti. diff --git a/_ba/tour/higher-order-functions.md b/_ba/tour/higher-order-functions.md index 8ddead84a5..56f1c1807a 100644 --- a/_ba/tour/higher-order-functions.md +++ b/_ba/tour/higher-order-functions.md @@ -21,19 +21,19 @@ def apply(f: Int => String, v: Int) = f(v) _Napomena: metode se automatski pretvaraju u funkcije ako to kontekst zahtijeva._ Ovo je još jedan primjer: - + ```scala mdoc class Decorator(left: String, right: String) { def layout[A](x: A) = left + x.toString() + right } object FunTest extends App { - override def apply(f: Int => String, v: Int) = f(v) + def apply(f: Int => String, v: Int) = f(v) val decorator = new Decorator("[", "]") println(apply(decorator.layout, 7)) } ``` - + Izvršavanjem se dobije izlaz: ``` diff --git a/_ba/tour/implicit-conversions.md b/_ba/tour/implicit-conversions.md index d794590c45..5a1ea3b9fa 100644 --- a/_ba/tour/implicit-conversions.md +++ b/_ba/tour/implicit-conversions.md @@ -46,8 +46,8 @@ Možete, zato što `Predef` uključuje slj. implicitnu konverziju: ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + Integer.valueOf(x) ``` Pošto su implicitne konverzije opasne ako se koriste pogrešno, kompajler upozorava kada kompajlira definiciju implicitne konverzije. diff --git a/_ba/tour/inner-classes.md b/_ba/tour/inner-classes.md index 10eac53bfb..ef72aa8929 100644 --- a/_ba/tour/inner-classes.md +++ b/_ba/tour/inner-classes.md @@ -21,7 +21,7 @@ Radi ilustracije razlike, prikazaćemo implementaciju klase grafa: class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -35,7 +35,7 @@ class Graph { } } ``` - + U našem programu, grafovi su predstavljeni listom čvorova (`List[Node]`). Svaki čvor ima listu drugih čvorova s kojima je povezan (`connectedNodes`). Klasa `Node` je _path-dependent tip_ jer je ugniježdena u klasi `Graph`. Stoga, svi čvorovi u `connectedNodes` moraju biti kreirani koristeći `newNode` iz iste instance klase `Graph`. @@ -47,13 +47,13 @@ val node3: graph1.Node = graph1.newNode node1.connectTo(node2) node3.connectTo(node1) ``` - + Eksplicitno smo deklarisali tip `node1`, `node2`, i `node3` kao `graph1.Node` zbog jasnosti ali ga je kompajler mogao sam zaključiti. Pošto kada pozivamo `graph1.newNode` koja poziva `new Node`, metoda koristi instancu `Node` specifičnu instanci `graph1`. Da imamo dva grafa, sistem tipova Scale ne dozvoljava miješanje čvorova definisanih u različitim grafovima, jer čvorovi različitih grafova imaju različit tip. Ovo je primjer netačnog programa: - + ```scala mdoc:fail val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -69,12 +69,12 @@ U Javi bi 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: - + ```scala mdoc:nest class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -88,6 +88,6 @@ class Graph { } } ``` - + > 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/mixin-class-composition.md b/_ba/tour/mixin-class-composition.md index a38c2ba2e1..a8216abfb6 100644 --- a/_ba/tour/mixin-class-composition.md +++ b/_ba/tour/mixin-class-composition.md @@ -29,11 +29,11 @@ val d = new D d.message // I'm an instance of class B d.loudMessage // I'M AN INSTANCE OF CLASS B ``` -Klasa `D` je nadklasa od `B` i mixina `C`. +Klasa `D` je nadklasa od `B` i mixina `C`. Klase mogu imati samo jednu nadklasu alid mogu imati više mixina (koristeći ključne riječi `extends` i `with` respektivno). Mixini i nadklasa mogu imati isti nadtip. Pogledajmo sada zanimljiviji primjer počevši od apstraktne klase: - + ```scala mdoc abstract class AbsIterator { type T @@ -41,7 +41,7 @@ abstract class AbsIterator { def next(): T } ``` - + Klasa ima apstraktni tip `T` i standardne metode iteratora. Dalje, implementiraćemo konkretnu klasu (svi apstraktni članovi `T`, `hasNext`, i `next` imaju implementacije): @@ -59,9 +59,9 @@ class StringIterator(s: String) extends AbsIterator { ``` `StringIterator` prima `String` i može se koristiti za iteraciju nad `String`om (npr. da vidimo da li sadrži određeni karakter). - + trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next()) } + def foreach(f: T => Unit): Unit = { while (hasNext) f(next()) } } Kreirajmo sada trejt koji također nasljeđuje `AbsIterator`. @@ -74,7 +74,7 @@ trait RichIterator extends AbsIterator { Pošto je `RichIterator` trejt, on ne mora implementirati apstraktne članove `AbsIterator`a. -Željeli bismo iskombinirati funkcionalnosti `StringIterator`a i `RichIterator`a u jednoj klasi. +Željeli bismo iskombinirati funkcionalnosti `StringIterator`a i `RichIterator`a u jednoj klasi. ```scala mdoc object StringIteratorTest extends App { @@ -83,7 +83,7 @@ object StringIteratorTest extends App { iter foreach println } ``` - + Nova klasa `Iter` ima `StringIterator` kao nadklasu i `RichIterator` kao mixin. S jednostrukim nasljeđivanjem ne bismo mogli postići ovaj nivo fleksibilnosti. diff --git a/_ba/tour/unified-types.md b/_ba/tour/unified-types.md index c03e54ab0b..92c1e2a61e 100644 --- a/_ba/tour/unified-types.md +++ b/_ba/tour/unified-types.md @@ -18,14 +18,14 @@ Dijagram ispod prikazuje hijerarhiju Scala klasa. ## Hijerarhija tipova u Scali ## -[`Any`](https://www.scala-lang.org/api/2.12.1/scala/Any.html) je nadtip svih tipova, zove se još i vrh-tip. +[`Any`](https://www.scala-lang.org/api/2.12.1/scala/Any.html) je nadtip svih tipova, zove se još i vrh-tip. Definiše određene univerzalne metode kao što su `equals`, `hashCode` i `toString`. `Any` ima dvije direktne podklase, `AnyVal` i `AnyRef`. -`AnyVal` predstavlja vrijednosne tipove. Postoji devet predefinisanih vrijednosnih tipova i oni ne mogu biti `null`: +`AnyVal` predstavlja vrijednosne tipove. Postoji devet predefinisanih vrijednosnih tipova i oni ne mogu biti `null`: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit` i `Boolean`. -`Unit` je vrijednosni tip koji ne nosi značajnu informaciju. Postoji tačno jedna instanca tipa `Unit` koja se piše `()`. +`Unit` je vrijednosni tip koji ne nosi značajnu informaciju. Postoji tačno jedna instanca tipa `Unit` koja se piše `()`. Sve funkcije moraju vratiti nešto tako da je `Unit` ponekad koristan povratni tip. `AnyRef` predstavlja referencne tipove. Svi nevrijednosni tipovi definišu se kao referencni. @@ -66,7 +66,7 @@ Npr: ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (određena doza preciznosti se gubi ovdje) +val y: Float = x.toFloat // 9.8765434E8 (određena doza preciznosti se gubi ovdje) val face: Char = '☺' val number: Int = face // 9786 @@ -76,17 +76,17 @@ Kastovanje je jednosmjerno. Ovo se ne kompajlira: ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // Does not conform ``` Također možete kastovati i referencni tip u podtip. Ovo će biti pokriveno kasnije. ## Nothing i Null -`Nothing` je podtip svih tipova, također se zove i donji tip (en. bottom type). Ne postoji vrijednost koja ima tip `Nothing`. +`Nothing` je podtip svih tipova, također se zove i donji tip (en. bottom type). Ne postoji vrijednost koja ima tip `Nothing`. Česta upotreba ovog tipa je signalizacija neterminacije kao što je bacanje izuzetka, izlaz iz programa, ili beskonačna petlja (tj. tip izraza koji se ne izračunava u vrijednost, ili metoda koja se ne završava normalno). -`Null` je podtip svih referencnih tipova (tj. bilo kog podtipa `AnyRef`). -Ima jednu vrijednost koja se piše literalom `null`. -`Null` se uglavnom koristi radi interoperabilnosti s ostalim JVM jezicima i skoro nikad se ne koristi u Scala kodu. +`Null` je podtip svih referencnih tipova (tj. bilo kog podtipa `AnyRef`). +Ima jednu vrijednost koja se piše literalom `null`. +`Null` se uglavnom koristi radi interoperabilnosti s ostalim JVM jezicima i skoro nikad se ne koristi u Scala kodu. Alternative za `null` obradićemo kasnije. diff --git a/_books/3-scala-for-the-impatient.md b/_books/3-scala-for-the-impatient.md new file mode 100644 index 0000000000..72c7c01f6d --- /dev/null +++ b/_books/3-scala-for-the-impatient.md @@ -0,0 +1,23 @@ +--- +title: "Scala for the Impatient" +link: https://horstmann.com/scala/ +image: /resources/img/books/scala_for_the_impatient.jpg +status: Updated for Scala 3 +authors: ["Cay Horstmann"] +publisher: Addison-Wesley Professional +publisherLink: https://www.oreilly.com/publisher/addison-wesley-professional/ +--- + +What you get: + +* Up to date coverage of Scala 3 +* A rapid introduction to Scala for programmers who are competent in another language such as Java, C#, Python, JavaScript, 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-hands-on-scala.md b/_books/4-hands-on-scala.md similarity index 100% rename from _books/3-hands-on-scala.md rename to _books/4-hands-on-scala.md diff --git a/_books/5-get-programming.bd b/_books/5-get-programming.md similarity index 100% rename from _books/5-get-programming.bd rename to _books/5-get-programming.md diff --git a/_books/4-functional-programming-in-scala.md b/_books/7-functional-programming-in-scala.md similarity index 64% rename from _books/4-functional-programming-in-scala.md rename to _books/7-functional-programming-in-scala.md index 27881827ba..0b878c6b15 100644 --- a/_books/4-functional-programming-in-scala.md +++ b/_books/7-functional-programming-in-scala.md @@ -1,15 +1,13 @@ --- title: "Functional Programming in Scala" -link: https://www.manning.com/books/functional-programming-in-scala -image: /resources/img/books/FPiS_93x116.png -status: Covers Scala 2; Scala 3 version in early-access -authors: ["Paul Chiusano", "Rúnar Bjarnason"] +link: https://www.manning.com/books/functional-programming-in-scala-second-edition +image: /resources/img/books/FPiS_93x116.jpg +status: Updated for Scala 3 +authors: ["Michael Pilquist", "Paul Chiusano", "Rúnar Bjarnason"] publisher: Manning publisherLink: https://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." -Foreword by Martin Odersky. - -The [Scala 3 version](https://www.manning.com/books/functional-programming-in-scala-second-edition), co-authored by Michael Pilquist, is in early-access as of February 2022. +Forewords by Daniel Spiewak and Martin Odersky. diff --git a/_cheatsheets/index.md b/_cheatsheets/index.md index 2b4cc489ec..679e4ed242 100644 --- a/_cheatsheets/index.md +++ b/_cheatsheets/index.md @@ -7,7 +7,7 @@ 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, th, ru] +languages: [ba, fr, ja, pl, pt-br, zh-cn, th, ru, uk] --- @@ -220,7 +220,6 @@ languages: [ba, fr, ja, pl, pt-br, zh-cn, th, ru]
import scala.util.control.Breaks._
-
 breakable {
   for (x <- xs) {
     if (Math.random < 0.1)
@@ -332,7 +331,7 @@ breakable {
   var y = x
   val readonly = 5
   private var secret = 1
-  def this = this(42)
+  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. diff --git a/_config.yml b/_config.yml index 43f6dfd536..e1ed8d682e 100644 --- a/_config.yml +++ b/_config.yml @@ -15,12 +15,11 @@ keywords: - Document - Guide -scala-version: 2.13.8 -scala-212-version: 2.12.15 -scala-3-version: 3.1.1 +scala-version: 2.13.16 +scala-212-version: 2.12.20 +scala-3-version: 3.7.1 collections: - contribute_resources: style: output: true overviews: @@ -42,11 +41,6 @@ collections: permalink: /:collection/:path.html books: output: false - getting-started: - output: true - permalink: /:collection/:path.html - scala3-reference: # not really a collection, but this is the only way I found to be able to generate the navigation bar on the right - output: true ja: # Japanese translations output: true permalink: /:collection/:path.html @@ -86,6 +80,9 @@ collections: th: # Thai translations output: true permalink: /:collection/:path.html + uk: # Ukrainian translations + output: true + permalink: /:collection/:path.html defaults: - @@ -94,6 +91,47 @@ defaults: type: "tour" values: overview-name: "Tour of Scala" + - + scope: + path: "_overviews/getting-started" + values: + permalink: "/:path.html" + - + scope: + path: "_overviews/macros" + values: + scala2: true + versionSpecific: true + - + scope: + path: "_overviews/reflection" + values: + scala2: true + versionSpecific: true + - + scope: + path: "_overviews/quasiquotes" + values: + scala2: true + versionSpecific: true + - + scope: + path: "_overviews/repl" + values: + scala2: true + versionSpecific: true + - + scope: + path: "_overviews/plugins" + values: + scala2: true + versionSpecific: true + - + scope: + path: "_overviews/compiler-options" + values: + scala2: true + versionSpecific: true - scope: path: "_overviews/scala3-book" @@ -110,7 +148,7 @@ defaults: path: "_overviews/contribute" values: partof: scala-contribution - overview-name: Contributing to Scala + overview-name: Contributing to Scala's OSS Ecosystem layout: multipage-overview permalink: "/contribute/:title.html" - @@ -139,6 +177,7 @@ defaults: path: "_overviews/scala3-macros" values: scala3: true + versionSpecific: true partof: scala3-macros overview-name: "Macros in Scala 3" layout: multipage-overview @@ -148,20 +187,19 @@ defaults: path: "_overviews/scala3-scaladoc" values: scala3: true + versionSpecific: true partof: scala3-scaladoc overview-name: "Scaladoc" layout: multipage-overview permalink: "/scala3/guides/scaladoc/:title.html" - scope: - path: "_scala3-reference" + path: "_overviews/toolkit" values: - scala3: true - partof: scala3-reference - type: section - overview-name: "Scala 3 Language Reference" + partof: toolkit + overview-name: "The Scala Toolkit" layout: multipage-overview - permalink: "/scala3/reference/:path.html" + permalink: "/toolkit/:title.html" - scope: path: "scala3" @@ -172,8 +210,7 @@ defaults: highlighter: rouge permalink: /:categories/:title.html:output_ext baseurl: -scala3ref: "/scala3/reference" -exclude: ["vendor"] +scala3ref: "https://docs.scala-lang.org/scala3/reference" +exclude: ["vendor", ".metals"] plugins: - jekyll-redirect-from - - jekyll-scalafiddle diff --git a/_contribute_resources/1-documentation.md b/_contribute_resources/1-documentation.md deleted file mode 100644 index e11b539e16..0000000000 --- a/_contribute_resources/1-documentation.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Documentation -link: /contribute/documentation.html -icon: fa fa-book ---- -[Library API docs][scala-standard-library-api-documentation], [new guides][add-guides] on [docs.scala-lang.org][home], and help -with [scala-lang.org](https://github.com/scala/scala-lang). - -[add-guides]: {% link _overviews/contribute/add-guides.md %} -[scala-standard-library-api-documentation]: {% link _overviews/contribute/scala-standard-library-api-documentation.md %} -[home]: {% link index.md %} diff --git a/_contribute_resources/2-bug-fixes.md b/_contribute_resources/2-bug-fixes.md deleted file mode 100644 index 7026e7e15b..0000000000 --- a/_contribute_resources/2-bug-fixes.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Bug fixes -link: /contribute/guide.html -icon: fa fa-bug ---- -Issues with the tools, core libraries and compiler. Also you can help us by [reporting bugs][bug-reporting-guide]. - -[bug-reporting-guide]: {% link _overviews/contribute/bug-reporting-guide.md %} diff --git a/_contribute_resources/3-code-reviews.md b/_contribute_resources/3-code-reviews.md deleted file mode 100644 index e03305beb5..0000000000 --- a/_contribute_resources/3-code-reviews.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Code Reviews -link: /contribute/codereviews.html -icon: fa fa-eye ---- -Review pull requests against [scala/scala](https://github.com/scala/scala/pulls), -[lampepfl/dotty](https://github.com/lampepfl/dotty/pulls), -[scala/scala-lang](https://github.com/scala/scala-lang/pulls), -[scala/docs.scala-lang](https://github.com/scala/docs.scala-lang/pulls) and others. diff --git a/_contribute_resources/4-core-libraries.md b/_contribute_resources/4-core-libraries.md deleted file mode 100644 index 06f1018479..0000000000 --- a/_contribute_resources/4-core-libraries.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Core Libraries -link: /contribute/corelibs.html -icon: fa fa-clipboard ---- -Update and expand the capabilities of the core (and associated) Scala libraries. diff --git a/_contribute_resources/5-ide-and-build-tools.md b/_contribute_resources/5-ide-and-build-tools.md deleted file mode 100644 index 7202f0d953..0000000000 --- a/_contribute_resources/5-ide-and-build-tools.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: IDE and Build Tools -link: /contribute/tools.html -icon: fa fa-terminal ---- -Enhance the Scala tools with features for build tools, IDE plug-ins and other related projects. diff --git a/_contribute_resources/6-compiler-language.md b/_contribute_resources/6-compiler-language.md deleted file mode 100644 index d54e34fac9..0000000000 --- a/_contribute_resources/6-compiler-language.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Compiler/Language -link: /contribute/guide.html#larger-changes-new-features -icon: fa fa-cogs ---- -Larger language features and compiler enhancements including language specification and SIPs. diff --git a/_data/compiler-options.yml b/_data/compiler-options.yml index 0490b25127..f4cced5028 100644 --- a/_data/compiler-options.yml +++ b/_data/compiler-options.yml @@ -260,7 +260,7 @@ type: "String" arg: "release" default: - description: "Compile for a specific version of the Java platform. Supported targets: 6, 7, 8, 9" + description: "Compile for a specific version of the Java platform. Supported targets: 8, 11, or any higher version listed at https://docs.scala-lang.org/overviews/jdk-compatibility/overview.html" abbreviations: - "--release" - option: "-sourcepath" @@ -379,8 +379,6 @@ description: "Warn when nullary methods return Unit." - choice: "inaccessible" description: "Warn about inaccessible types in method signatures." - - choice: "nullary-override" - description: "Warn when non-nullary `def f()` overrides nullary `def f`." - choice: "infer-any" description: "Warn when a type argument is inferred to be `Any`." - choice: "missing-interpolator" @@ -469,10 +467,6 @@ schema: type: "Boolean" description: "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation." - - option: "-Xno-uescape" - schema: - type: "Boolean" - description: "Disable handling of \\u unicode escapes." - option: "-Xnojline" schema: type: "Boolean" @@ -1139,8 +1133,6 @@ description: "Warn when nullary methods return Unit." - choice: "inaccessible" description: "Warn about inaccessible types in method signatures." - - choice: "nullary-override" - description: "Warn when non-nullary `def f()` overrides nullary `def f`." - choice: "infer-any" description: "Warn when a type argument is inferred to be `Any`." - choice: "missing-interpolator" diff --git a/_data/doc-nav-header.yml b/_data/doc-nav-header.yml index 52b66be1da..772da79703 100644 --- a/_data/doc-nav-header.yml +++ b/_data/doc-nav-header.yml @@ -1,16 +1,32 @@ - title: Getting Started - url: "/getting-started/index.html" + url: "#" + submenu: + - title: Install Scala + url: "/getting-started/install-scala.html" + - title: Scala IDEs + url: "/getting-started/scala-ides.html" - title: Learn url: "#" submenu: - title: Tour of Scala url: "/tour/tour-of-scala.html" - - title: Scala Book + - title: Scala 3 Book + url: "/scala3/book/introduction.html" + - title: Scala 2 Book url: "/overviews/scala-book/introduction.html" - title: Online Courses url: "/online-courses.html" - - title: Online Resources - url: "/learn.html" +- title: Scala 3 Migration + url: "#" + submenu: + - title: What's New? + url: "/scala3/new-in-scala3.html" + - title: Migrating From Scala 2 + url: "/scala3/guides/migration/compatibility-intro.html" + - title: New Features for Scaladoc + url: "/scala3/scaladoc.html" + - title: Videos and Talks + url: "/scala3/talks.html" - title: Tutorials url: "#" submenu: @@ -24,6 +40,8 @@ url: "/tutorials/scala-on-android.html" - title: Scala with Maven url: "/tutorials/scala-with-maven.html" + - title: Using the Scala Toolkit + url: "/toolkit/introduction.html" - title: Reference url: "#" submenu: @@ -33,9 +51,13 @@ url: "/books.html" - title: Scala FAQ url: "/tutorials/FAQ/index.html" - - title: Language Specification + - title: Scala 2 Language Specification url: http://scala-lang.org/files/archive/spec/2.13/ - - title: Contribution Guide + - title: Scala 3 Language Specification + url: http://scala-lang.org/files/archive/spec/3.4/ + - title: Scala 3 Language Reference + url: "https://docs.scala-lang.org/scala3/reference" + - title: Scala Contribution Guide url: "/contribute/" - title: Style Guide url: "/style/index.html" @@ -44,11 +66,6 @@ - title: Glossary url: "/glossary/index.html" - title: API - url: "#" - submenu: - - title: Current - url: https://www.scala-lang.org/api/current/ - - title: All Versions - url: "/api/all.html" + url: "/api/all.html" - title: SIPs url: "/sips/index.html" diff --git a/_data/footer.yml b/_data/footer.yml index b810181f92..789017f8b9 100644 --- a/_data/footer.yml +++ b/_data/footer.yml @@ -21,9 +21,11 @@ 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 + - title: Scala Ambassadors + url: "http://scala-lang.org/ambassadors/" + - title: Forums + url: "http://scala-lang.org/community/index.html#forums" + - title: Chat 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" @@ -39,16 +41,28 @@ - title: Scala class: scala links: + - title: Governance + url: "http://scala-lang.org/governance/" - 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: Security Policy + url: "http://scala-lang.org/security/" - title: Social class: social links: - title: GitHub url: "https://github.com/scala/scala" - - title: Twitter - url: "https://twitter.com/scala_lang" + - title: Mastodon + url: "https://fosstodon.org/@scala_lang" + - title: Bluesky + url: "https://bsky.app/profile/scala-lang.org" + - title: X + url: "https://x.com/scala_lang" + - title: Discord + url: "https://discord.com/invite/scala" + - title: LinkedIn + url: "https://www.linkedin.com/company/scala-center/" \ No newline at end of file diff --git a/_data/messages.yml b/_data/messages.yml new file mode 100644 index 0000000000..642a7ac557 --- /dev/null +++ b/_data/messages.yml @@ -0,0 +1 @@ +scam-banner: "**⚠️ Beware of Scams**: since Feb 2024, scammers are using [fake Scala websites to sell courses](https://www.scala-lang.org/blog/2024/03/01/fake-scala-courses.html), please check you are using an official source." diff --git a/_data/nav-header.yml b/_data/nav-header.yml index eeaa88291c..792c68fc1e 100644 --- a/_data/nav-header.yml +++ b/_data/nav-header.yml @@ -8,5 +8,7 @@ url: https://index.scala-lang.org - title: Community url: https://www.scala-lang.org/community/ +- title: Governance + url: https://www.scala-lang.org/governance/ - title: Blog url: https://www.scala-lang.org/blog/ diff --git a/_data/overviews-ja.yml b/_data/overviews-ja.yml index 13be915210..60277bd90c 100644 --- a/_data/overviews-ja.yml +++ b/_data/overviews-ja.yml @@ -58,6 +58,30 @@ - category: 言語 description: "Scala 言語の機能をカバーするガイドと概要" overviews: + - title: "Scala 2 から Scala 3 への移行" + icon: suitcase + root: "scala3/guides/" + url: "migration/compatibility-intro.html" + description: "Scala 3 との互換性と移行について知っておくべきことすべて" + - title: "Scala 3 マクロ" + by: Nicolas Stucki + icon: magic + root: "scala3/guides/" + url: "macros" + description: "Scala 3 のマクロの書き方に関係する全ての機能をカバーする詳しいチュートリアル" + label-text: new in Scala 3 + - title: 値クラスと汎用トレイト + by: Mark Harrah + description: "値クラスは Scala で実行時のオブジェクトアロケーションを避ける新しい仕組みだ。これは新しい AnyVal サブクラスを定義することで達成できる。" + icon: gem + url: "core/value-classes.html" + - title: TASTyの概要 + by: Alvin Alexander + icon: birthday-cake + root: "scala3/guides/" + url: "tasty-overview.html" + description: "Scala のエンドユーザー向けの TASTy のフォーマットの概要" + label-text: new in Scala 3 - title: 文字列補間 icon: dollar-sign url: "core/string-interpolation.html" @@ -70,11 +94,6 @@ by: Josh Suereth description: "Scala 2.10 は暗黙クラス(implicit class)と呼ばれる新しい機能を導入した。暗黙クラスは implicit キーワードでマークされたクラスだ。このキーワードはそのクラスがスコープ内にあるとき、そのプライマリコンストラクターが暗黙変換に利用可能にする。" url: "core/implicit-classes.html" - - title: 値クラスと汎用トレイト - by: Mark Harrah - description: "値クラスは Scala で実行時のオブジェクトアロケーションを避ける新しい仕組みだ。これは新しい AnyVal サブクラスを定義することで達成できる。" - icon: gem - url: "core/value-classes.html" - category: ライブラリの作成 description: "Scala エコシステム向けのオープンソースライブラリの貢献方法のガイド" @@ -258,18 +277,6 @@ - category: レガシー description: "最近の Scala バージョン(2.12以上)には関係なくなった機能をカバーするガイド。" overviews: - - title: Scala アクター移行ガイド - by: Vojin Jovanovic and Philipp Haller - icon: truck - url: "core/actors-migration-guide.html" - description: "Scala アクターから Akka への移行を容易にするため、Actor Migration Kit(AMK)を用意した。AMK は、Scala アクターの拡張から構成され、プロジェクトのクラスパスに scala-actors-migration.jar を含めることで有効になる。加えて、Akka 2.1 はアクター DSL シングルトンのような機能を導入し、Scala アクターを使ったコードを Akka へ容易に変換することを可能にしている。このドキュメントの目的はユーザーに移行プロセスを案内し、AMK の使い方を説明することだ。" - - title: Scala アクター API - by: Philipp Haller and Stephen Tu - icon: users - url: "core/actors.html" - description: "このガイドは Scala 2.8/2.9 の scala.actors パッケージの API を説明する。組織は論理的に同類の型のグループに従う。トレイト階層は個々のセクションを構造化することを考慮している。既存の Scaladoc ベースの API ドキュメントを補間するため、これらトレイトが定義する様々なメソッドの実行時の挙動にフォーカスする。" - label-color: "#899295" - label-text: deprecated - title: Scala 2.8 から 2.12 までのコレクション by: Martin Odersky icon: sitemap diff --git a/_data/overviews-ru.yml b/_data/overviews-ru.yml index 8fdadb53e5..f4a652a032 100644 --- a/_data/overviews-ru.yml +++ b/_data/overviews-ru.yml @@ -258,18 +258,6 @@ - category: Наследие description: "Руководство по функционалу, которые больше не соответствуют последним версиям Scala (2.12+)." overviews: - - title: Руководство по миграции Scala Акторов - by: Vojin Jovanovic и Philipp Haller - icon: truck - url: "core/actors-migration-guide.html" - description: "Для облегчения миграции со Скала Актеров на Акка мы предоставили Миграционный Комплект для Актеров (МКА). МКА состоит из расширения Scala Акторов, которое позволяет включить scala-actors-migration.jar в пространство классов проекта. Кроме того, Akka 2.1 включает в себя такие функции, как ActorDSL singleton, которые позволяют осуществлять простое преобразование кода с использованием Scala Actors в Akka. Цель этого документа - помочь пользователям пройти через процесс миграции и объяснить, как использовать МКА." - - title: API Scala Акторов - by: Philipp Haller и Stephen Tu - icon: users - url: "core/actors.html" - description: "В данном руководстве описывается API пакета scala.actors версии 2.8/2.9. Сгруппированы по типам, которые логически принадлежат друг другу. Иерархия трейтов учитывается при структурировании отдельных разделов. Основное внимание уделяется поведению во время исполнения различных методов, которое дополняет существующую документацию по API на основе Scaladoc." - label-color: "#899295" - label-text: устарело - title: Scala коллекции с 2.8 по 2.12 by: Martin Odersky icon: sitemap diff --git a/_data/overviews-uk.yml b/_data/overviews-uk.yml new file mode 100644 index 0000000000..02be500922 --- /dev/null +++ b/_data/overviews-uk.yml @@ -0,0 +1,348 @@ +- category: Стандартна бібліотека + description: "Посібники та огляди стандартної бібліотеки Scala." + overviews: + - title: Scala колекції + by: Martin Odersky та Julien Richard-Foy + icon: sitemap + url: "collections-2.13/introduction.html" + description: "Бібліотека колекцій Scala." + subdocs: + - title: Вступ + url: "collections-2.13/introduction.html" + - title: Змінювані та незмінювані колекції + url: "collections-2.13/overview.html" + - title: Трейт Iterable + url: "collections-2.13/trait-iterable.html" + - title: Трейти послідовностей. Seq, IndexedSeq та LinearSeq + url: "collections-2.13/seqs.html" + - title: Реалізація незмінюваних колекцій + url: "collections-2.13/concrete-immutable-collection-classes.html" + - title: Реалізація змінюваних колекцій + url: "collections-2.13/concrete-mutable-collection-classes.html" + - title: Масиви + url: "collections-2.13/arrays.html" + - title: Рядки + url: "collections-2.13/strings.html" + - title: Показники продуктивності + url: "collections-2.13/performance-characteristics.html" + - title: Рівність + url: "collections-2.13/equality.html" + - title: Відображення + url: "collections-2.13/views.html" + - title: Ітератори + url: "collections-2.13/iterators.html" + - title: Створення колекцій з нуля + url: "collections-2.13/creating-collections-from-scratch.html" + - title: Перетворення між колекціями Java та Scala + url: "collections-2.13/conversions-between-java-and-scala-collections.html" + - title: Міграція проєкту до колекцій Scala 2.13 + icon: sitemap + url: "core/collections-migration-213.html" + description: "Ця сторінка описує основні зміни в колекціях для користувачів, які переходять на Scala 2.13. Також, розглянуто варіанти побудови проєкти з перехресною сумісністю для Scala 2.11/2.12 і 2.13." + - title: Архітектура колекцій Scala + icon: sitemap + url: "core/architecture-of-scala-213-collections.html" + by: Julien Richard-Foy + description: "Ці сторінки описують архітектуру фреймворку колекцій, представленого в Scala 2.13. У порівнянні з Collections API ви дізнаєтеся більше про внутрішню роботу фреймворка." + - title: Реалізація користувацьких колекцій + icon: building + url: "core/custom-collections.html" + by: Martin Odersky, Lex Spoon та Julien Richard-Foy + description: "У цьому документі ви дізнаєтеся, як фреймворк колекцій допомагає вам визначати власні колекції за допомогою кількох рядків коду, повторно використовуючи переважну частину функцій колекції з фреймворку." + - title: Додавання спеціальних операцій до колекцій + icon: building + url: "core/custom-collection-operations.html" + by: Julien Richard-Foy + description: "У цьому посібнику показано, як писати перетворення, що застосовуються до всіх типів колекцій і повертати той самий тип колекції. Також, як писати операції, які параметризуються типом колекції." + +- category: Мова + description: "Посібники та огляди, що охоплюють функції на мові Scala." + overviews: + - title: Міграція зі Scala 2 на Scala 3 + by: Adrien Piquerez + icon: suitcase + root: "scala3/guides/" + url: "migration/compatibility-intro.html" + description: "Все, що потрібно знати про сумісність і міграцію на Scala 3." + - title: Макроси Scala 3 + by: Nicolas Stucki + icon: magic + root: "scala3/guides/" + url: "macros" + description: "Детальний підручник, який охоплює всі можливості, пов'язані з написанням макросів у Scala 3." + label-text: нове в Scala 3 + - title: Класи значень та універсальні трейти + by: Mark Harrah + description: "Класи значень – це новий механізм у Scala, що дозволяє уникнути виділення об'єктів під час виконання. Це досягається за допомогою визначення нових підкласів AnyVal." + icon: gem + url: "core/value-classes.html" + - title: Огляд TASTy + by: Alvin Alexander + icon: birthday-cake + label-text: нове в Scala 3 + root: "scala3/guides/" + url: "tasty-overview.html" + description: "Огляд формату TASTy, призначеного для користувачів мови Scala." + - title: Інтерполяція рядків + icon: dollar-sign + url: "core/string-interpolation.html" + description: > + Інтерполяція рядків дозволяє користувачам вбудовувати посилання на змінні безпосередньо в оброблені рядкові літерали. Ось приклад: +
val name = "James"
+          println(s"Hello, $name")  // Hello, James
+ Літерал s"Hello, $name" є рядковим літералом, який буде додатково оброблено. Це означає, що компілятор виконує додаткову роботу над цим літералом. Оброблений рядковий літерал позначається набором символів, що передують ". Інтерполяція рядків була введена в SIP-11. + - title: Неявні класи + by: Josh Suereth + description: "Scala 2.10 представила нову функцію під назвою неявні класи. Неявний клас — це клас, позначений ключовим словом implicit. Це ключове слово робить основний конструктор класу доступним для неявних перетворень, коли клас знаходиться в області видимості." + url: "core/implicit-classes.html" + +- category: Створення бібліотек + description: "Посібники щодо розробки бібліотек з відкритим кодом для екосистеми Scala." + overviews: + - title: Посібник для авторів бібліотек + by: Julien Richard-Foy + icon: tasks + url: "contributors/index.html" + description: "Перелічує всі інструменти, які автори бібліотек мають налаштувати для публікації та документування своїх бібліотек." + +- category: Паралельне та конкурентне програмування + description: "Повні посібники, що охоплюють деякі бібліотеки Scala для паралельного та конкурентного програмування." + overviews: + - title: Future та Promise + by: Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn та Vojin Jovanovic + icon: tasks + url: "core/futures.html" + description: "Ф'ючери дають можливість міркувати про паралельне виконання багатьох операцій – ефективним і не блокуючим способом. Ф'ючер — це об’єкт-заповнювач для значення, яке може ще не існувати. Як правило, вартість Ф'ючеру надається одночасно і може згодом використовуватися. Складання одночасних завдань таким чином, як правило, призводить до швидшого, асинхронного, не блокувального паралельного коду." + - title: Паралельні колекції + by: Aleksandar Prokopec та Heather Miller + icon: rocket + url: "parallel-collections/overview.html" + description: "Бібліотека паралельних колекцій Scala." + subdocs: + - title: Огляд + url: "parallel-collections/overview.html" + - title: Реалізація паралельних колекцій + url: "parallel-collections/concrete-parallel-collections.html" + - title: Перетворення паралельних колекцій + url: "parallel-collections/conversions.html" + - title: Конкурентні Try + url: "parallel-collections/ctries.html" + - title: Архітектура бібліотеки паралельних колекцій + url: "parallel-collections/architecture.html" + - title: Створення користувацьких паралельних колекцій + url: "parallel-collections/custom-parallel-collections.html" + - title: Конфігурація паралельних колекцій + url: "parallel-collections/configuration.html" + - title: Вимірювання продуктивності + url: "parallel-collections/performance.html" + +- category: Сумісність + description: "Що з чим працює (чи ні)." + overviews: + - title: Сумісність версій JDK + description: "Які версії Scala працюють на яких версіях JDK" + icon: coffee + url: "jdk-compatibility/overview.html" + - title: Бінарна сумісність релізів Scala + description: "Якщо дві версії Scala бінарно сумісні, можна безпечно скомпілювати свій проєкт на одній версії Scala та зв'язати з іншою версією Scala під час виконання. Безпечне зв'язування під час виконання (тільки!) означає, що JVM не генерує (підклас) LinkageError під час виконання вашої програми у змішаному сценарії, припускаючи, що вона не виникає при компіляції та запуску в одній версії Scala. Конкретно це означає, що ви можете мати зовнішні залежності від вашого шляху до класу під час виконання, які використовують іншу версію Scala, ніж та, з якою ви компілюєте, за умови, що вони сумісні з бінарними файлами. Іншими словами, окрема компіляція в різних версіях, сумісних з бінарними файлами, не створює проблем у порівнянні з компіляцією та запуском всього в одній версії Scala." + icon: puzzle-piece + url: "core/binary-compatibility-of-scala-releases.html" + - title: Бінарна сумісність для авторів бібліотек + description: "Різноманітний і повний набір бібліотек важливий для будь-якої продуктивної екосистеми програмного забезпечення. Хоча розробляти та розповсюджувати бібліотеки Scala легко, добре авторство бібліотеки виходить за рамки простого написання коду та його публікації. У цьому посібнику ми розглянемо важливу тему бінарної сумісності." + icon: puzzle-piece + url: "core/binary-compatibility-for-library-authors.html" + +- category: Інструменти + description: "Довідковий матеріал про основні інструменти Scala, такі як покоління Scala REPL і Scaladoc." + overviews: + - title: Scala 2 REPL + icon: terminal + url: "repl/overview.html" + description: | + Scala REPL це інструмент (scala) для виконання виразів в Scala. +

+ Команда scala виконає скрипт шляхом обгортання його в шаблон, а потім компіляції та виконання отриманої програми + - title: Scaladoc для Scala 3 + by: Krzysztof Romanowski, Aleksander Boruch-Gruszecki, Andrzej Ratajczak, Kacper Korban, Filip Zybała + icon: book + root: "scala3/guides/" + url: "scaladoc" + label-text: оновлено + description: "Оновлення в Scala 3 для інструменту генерації документації API." + - title: Scaladoc + url: "scaladoc/overview.html" + icon: book + description: "Інструмент Scala для генерації документації для API." + subdocs: + - title: Огляд + url: "scaladoc/overview.html" + - title: Scaladoc для авторів бібліотек + url: "scaladoc/for-library-authors.html" + - title: Використання інтерфейсу Scaladoc + url: "scaladoc/interface.html" + +- category: Компілятор + description: "Посібники та огляди компілятора Scala: плагіни компілятора, інструменти рефлексії та метапрограмування, такі як макроси." + overviews: + - title: "Посібник з внесення змін у Scala 3" + by: Jamie Thompson, Anatolii Kmetiuk + icon: cogs + root: "scala3/guides/" + url: "contribution/contribution-intro.html" + description: "Посібник з компілятора Scala 3 та вирішення проблем." + - title: Рефлексія в Scala 2 + by: Heather Miller, Eugene Burmako та Philipp Haller + icon: binoculars + url: "reflection/overview.html" + description: Фреймворк Scala для рефлексії під час виконання/компіляції. + label-text: відсутнє в Scala 3 + subdocs: + - title: Огляд + url: "reflection/overview.html" + - title: Environment, Universe та Mirror + url: "reflection/environment-universes-mirrors.html" + - title: Symbol, Tree та Type + url: "reflection/symbols-trees-types.html" + - title: Annotation, Name, Scope та More + url: "reflection/annotations-names-scopes.html" + - title: TypeTag та Manifest + url: "reflection/typetags-manifests.html" + - title: Безпека потоків + url: "reflection/thread-safety.html" + - title: Зміни в Scala 2.11 + url: "reflection/changelog211.html" + - title: Макроси в Scala 2 + by: Eugene Burmako + icon: magic + url: "macros/usecases.html" + description: "Фреймворк метапрограмування Scala." + label-text: відсутнє в Scala 3 + subdocs: + - title: Випадки використання + url: "macros/usecases.html" + - title: Blackbox проти Whitebox + url: "macros/blackbox-whitebox.html" + - title: Макроси Def + url: "macros/overview.html" + - title: Квазіцитати + url: "quasiquotes/intro.html" + - title: Пакети макросів + url: "macros/bundles.html" + - title: Неявні макроси + url: "macros/implicits.html" + - title: Макроси-екстрактори + url: "macros/extractors.html" + - title: Провайдери типів + url: "macros/typeproviders.html" + - title: Анотації макросів + url: "macros/annotations.html" + - title: Макрос Paradise + url: "macros/paradise.html" + - title: Дорожня карта + url: "macros/roadmap.html" + - title: Зміни в 2.11 + url: "macros/changelog211.html" + - title: Квазіцитати в Scala 2 + by: Denys Shabalin + icon: quote-left + url: "quasiquotes/setup.html" + description: "Квазіцитати — це зручний спосіб маніпулювати синтаксичними деревами Scala." + label-text: відсутнє в Scala 3 + subdocs: + - title: Залежності та налаштування + url: "quasiquotes/setup.html" + - title: Вступ + url: "quasiquotes/intro.html" + - title: Підіймання + url: "quasiquotes/lifting.html" + - title: Опускання + url: "quasiquotes/unlifting.html" + - title: Гігієна + url: "quasiquotes/hygiene.html" + - title: Випадки використання + url: "quasiquotes/usecases.html" + - title: Резюме синтаксису + url: "quasiquotes/syntax-summary.html" + - title: Деталі виразів + url: "quasiquotes/expression-details.html" + - title: Деталі типів + url: "quasiquotes/type-details.html" + - title: Деталі патернів + url: "quasiquotes/pattern-details.html" + - title: Деталі визначення та імпорту + url: "quasiquotes/definition-details.html" + - title: Резюме термінології + url: "quasiquotes/terminology.html" + - title: Майбутні перспективи + url: "quasiquotes/future.html" + - title: Плагіни компілятора + by: Lex Spoon та Seth Tisue + icon: puzzle-piece + url: "plugins/index.html" + description: "Плагіни компілятора дозволяють налаштовувати та розширювати компілятор Scala. У цьому підручнику описується функція плагіну та пояснюється, як створити простий плагін." + - title: Параметри компілятора + by: Community + icon: cog + url: "compiler-options/index.html" + description: "Різні параметри того як scalac компілює ваш код." + - title: Форматування помилок + by: Torsten Schmits + icon: cog + url: "compiler-options/errors.html" + description: "Новий механізм для більш зручних повідомлень про помилки, друку ланцюжків залежних неявних параметрів та кольорових відмінностей знайдених/потрібних типів." + - title: Оптимізатор + by: Lukas Rytz та Andrew Marki + icon: cog + url: "compiler-options/optimizer.html" + description: "Компілятор може виконувати різні оптимізації." + +- category: Спадщина (legacy) + description: "Посібники, що охоплюють функції, які більше не стосуються останніх версій Scala (2.12+)." + overviews: + - title: Колекції Scala з 2.8 до 2.12 + by: Martin Odersky + icon: sitemap + url: "collections/introduction.html" + description: "Бібліотека колекцій Scala." + subdocs: + - title: Вступ + url: "collections/introduction.html" + - title: Змінювані та незмінювані колекції + url: "collections/overview.html" + - title: Трейт Traversable + url: "collections/trait-traversable.html" + - title: Трейт Iterable + url: "collections/trait-iterable.html" + - title: Трейти послідовностей. Seq, IndexedSeq та LinearSeq + url: "collections/seqs.html" + - title: Множини + url: "collections/sets.html" + - title: Асоціативні масиви + url: "collections/maps.html" + - title: Реалізація незмінюваних колекцій + url: "collections/concrete-immutable-collection-classes.html" + - title: Реалізація змінюваних колекцій + url: "collections/concrete-mutable-collection-classes.html" + - title: Масиви + url: "collections/arrays.html" + - title: Рядки + url: "collections/strings.html" + - title: Показники продуктивності + url: "collections/performance-characteristics.html" + - title: Рівність + url: "collections/equality.html" + - title: Відображення + url: "collections/views.html" + - title: Ітератори + url: "collections/iterators.html" + - title: Створення колекцій з нуля + url: "collections/creating-collections-from-scratch.html" + - title: Перетворення між колекціями Java та Scala + url: "collections/conversions-between-java-and-scala-collections.html" + - title: Міграція з версії Scala 2.7 + url: "collections/migrating-from-scala-27.html" + - title: Архітектура колекцій Scala з 2.8 до 2.12 + icon: building + url: "core/architecture-of-scala-collections.html" + by: Martin Odersky та Lex Spoon + description: "На цих сторінках детально описується архітектура фреймворку колекцій Scala. У порівнянні з Collections API ви дізнаєтеся більше про внутрішню роботу фреймворку. Ви також дізнаєтеся, як ця архітектура допомагає вам визначати власні колекції за допомогою кількох рядків коду, повторно використовуючи переважну частину функцій колекції з фреймворку." diff --git a/_data/overviews-zh-cn.yml b/_data/overviews-zh-cn.yml index d264b4e5d1..1c48218eef 100644 --- a/_data/overviews-zh-cn.yml +++ b/_data/overviews-zh-cn.yml @@ -263,18 +263,6 @@ - category: 遗留问题 description: "涵盖一些与最近的 Scala 版本(2.12+)不再相关的特性的参考" overviews: - - title: Scala Actors迁移参考 - by: Vojin Jovanovic and Philipp Haller - icon: truck - url: "core/actors-migration-guide.html" - description: "为简化 Scala Actors 到 Akka 的迁移我们提供了 Actor 迁移包(Actor Migration Kit,简称 AMK)。迁移包中包含了 Scala Actors 的一个扩展,可以通过项目类路径中包含 scala-actors-migration.jar 来引入该扩展。另外,Akka 2.1 还包含一些新特性,比如 ActorDSL 单例,可使用 Scala Actors 编写的代码更易转换成 Akka 的。本篇目的是指导用户迁移过程并阐释如何使用 Actor 迁移包。" - - title: Scala Actors API - by: Philipp Haller and Stephen Tu - icon: users - url: "core/actors.html" - description: "本篇描述了 Scala 2.8/2.9 下 scala.actors 包的 API。其组织方式遵循逻辑上属于一块的组类型,特质层是考虑到组成其各自的部分,主要关注点在这些特质所定义的各种方法的运行时表现,由此也增补了已有的基于 Scaladoc 的API文档。" - label-color: "#899295" - label-text: 弃用 - title: Scala 2.8 到 2.12 的容器 by: Martin Odersky icon: sitemap diff --git a/_data/overviews.yml b/_data/overviews.yml index a8bc055f7c..5756db5e3e 100644 --- a/_data/overviews.yml +++ b/_data/overviews.yml @@ -1,4 +1,3 @@ - - category: Standard Library description: "Guides and overviews covering the Scala standard library." overviews: @@ -60,6 +59,31 @@ - category: Language description: "Guides and overviews covering features in the Scala language." overviews: + - title: "Migration from Scala 2 to Scala 3" + by: Adrien Piquerez + icon: suitcase + root: "scala3/guides/" + url: "migration/compatibility-intro.html" + description: "Everything you need to know about compatibility and migration to Scala 3." + - title: Scala 3 Macros + by: Nicolas Stucki + icon: magic + root: "scala3/guides/" + url: "macros" + description: "A detailed tutorial to cover all the features involved in writing macros in Scala 3." + label-text: new in Scala 3 + - 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: gem + url: "core/value-classes.html" + - title: An Overview of TASTy + by: Alvin Alexander + icon: birthday-cake + label-text: new in Scala 3 + root: "scala3/guides/" + url: "tasty-overview.html" + description: "An overview over the TASTy format aimed at end-users of the Scala language." - title: String Interpolation icon: dollar-sign url: "core/string-interpolation.html" @@ -72,11 +96,15 @@ 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: gem - url: "core/value-classes.html" + - title: The Scala Book + by: Alvin Alexander + icon: book + label-color: "#899295" + label-text: archived + url: "scala-book/introduction.html" + description: > + A light introduction to the Scala language, focused on Scala 2. + Now updated for Scala 3, we are in the process of merging the two. - category: Authoring Libraries description: "Guides for contributing open source libraries to the Scala ecosystem." @@ -133,17 +161,27 @@ description: "A diverse and comprehensive set of libraries is important to any productive software ecosystem. While it is easy to develop and distribute Scala libraries, good library authorship goes beyond just writing code and publishing it. In this guide, we cover the important topic of Binary Compatibility." icon: puzzle-piece url: "core/binary-compatibility-for-library-authors.html" + - title: Nightly Versions of Scala + description: "We regularly publish 'nightlies' of both Scala 3 and Scala 2 so that users can preview and test the contents of upcoming releases. Here's how to find and use these versions." + url: "core/nightlies.html" - category: "Tools" description: "Reference material on core Scala tools like the Scala REPL and Scaladoc generation." overviews: - - title: Scala REPL + - title: Scala 2 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 + - title: Scaladoc For Scala 3 + by: Krzysztof Romanowski, Aleksander Boruch-Gruszecki, Andrzej Ratajczak, Kacper Korban, Filip Zybała + icon: book + root: "scala3/guides/" + url: "scaladoc" + description: "Updates in Scala 3 to Scala’s API documentation generation tool." + label-text: updated - title: Scaladoc url: "scaladoc/overview.html" icon: book @@ -159,12 +197,12 @@ - category: Compiler description: "Guides and overviews covering the Scala compiler: compiler plugins, reflection, and metaprogramming tools such as macros." overviews: - - title: Reflection + - title: Scala 2 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 + label-text: removed in Scala 3 subdocs: - title: Overview url: "reflection/overview.html" @@ -180,12 +218,12 @@ url: "reflection/thread-safety.html" - title: Changes in Scala 2.11 url: "reflection/changelog211.html" - - title: Macros + - title: Scala 2 Macros by: Eugene Burmako icon: magic url: "macros/usecases.html" description: "Scala's metaprogramming framework." - label-text: experimental + label-text: removed in Scala 3 subdocs: - title: Use Cases url: "macros/usecases.html" @@ -211,12 +249,12 @@ url: "macros/roadmap.html" - title: Changes in 2.11 url: "macros/changelog211.html" - - title: Quasiquotes + - title: Quasiquotes in Scala 2 by: Denys Shabalin icon: quote-left url: "quasiquotes/setup.html" description: "Quasiquotes are a convenient way to manipulate Scala syntax trees." - label-text: experimental + label-text: removed in Scala 3 subdocs: - title: Dependencies and setup url: "quasiquotes/setup.html" @@ -244,12 +282,12 @@ url: "quasiquotes/terminology.html" - title: Future prospects url: "quasiquotes/future.html" - - title: Compiler Plugins + - title: Scala 2 Compiler Plugins by: Lex Spoon and Seth Tisue icon: puzzle-piece url: "plugins/index.html" description: "Compiler plugins permit customizing and extending the Scala compiler. This tutorial describes the plugin facility and walks you through how to create a simple plugin." - - title: Compiler Options + - title: Scala 2 Compiler Options by: Community icon: cog url: "compiler-options/index.html" @@ -265,22 +303,9 @@ url: "compiler-options/optimizer.html" description: "The compiler can perform various optimizations." - - category: Legacy description: "Guides covering features no longer relevant to recent Scala versions (2.12+)." overviews: - - 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 - title: Scala 2.8 to 2.12’s Collections by: Martin Odersky icon: sitemap diff --git a/_data/scala3-doc-nav-header.yml b/_data/scala3-doc-nav-header.yml deleted file mode 100644 index 4b2201d17f..0000000000 --- a/_data/scala3-doc-nav-header.yml +++ /dev/null @@ -1,24 +0,0 @@ -- title: Getting Started - url: "/scala3/getting-started.html" -- title: Learn - url: "#" - submenu: - - title: New in Scala 3 - url: "/scala3/new-in-scala3.html" - - title: Scala 3 Book - url: "/scala3/book/introduction.html" - - title: Online Courses - url: "/online-courses.html" -- title: Migrate - url: "/scala3/guides/migration/compatibility-intro.html" -- title: Reference - url: '#' - submenu: - - title: Guides - url: '/scala3/guides.html' - - title: Language Specification - url: "/scala3/reference/overview.html" - - title: Scala Library API - url: "https://scala-lang.org/api/3.x/" -- title: Contributing Guide - url: "/scala3/guides/contribution/contribution-intro.html" diff --git a/_data/setup-scala.yml b/_data/setup-scala.yml new file mode 100644 index 0000000000..cda4c2361b --- /dev/null +++ b/_data/setup-scala.yml @@ -0,0 +1,6 @@ +linux-x86-64: curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup +linux-arm64: curl -fL https://github.com/VirtusLab/coursier-m1/releases/latest/download/cs-aarch64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup +macOS-x86-64: curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-apple-darwin.gz | gzip -d > cs && chmod +x cs && (xattr -d com.apple.quarantine cs || true) && ./cs setup +macOS-arm64: curl -fL https://github.com/VirtusLab/coursier-m1/releases/latest/download/cs-aarch64-apple-darwin.gz | gzip -d > cs && chmod +x cs && (xattr -d com.apple.quarantine cs || true) && ./cs setup +macOS-brew: brew install coursier && coursier setup +windows-link: https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-win32.zip diff --git a/_data/sip-data.yml b/_data/sip-data.yml index 6a6b19fe68..0a351b24da 100644 --- a/_data/sip-data.yml +++ b/_data/sip-data.yml @@ -1,27 +1,47 @@ +design: + color: "#839496" + text: "Design" + +implementation: + color: "#839496" + text: "Implementation" + +submitted: + color: "#2aa198" + text: "Submitted" + under-review: color: "#b58900" text: "Under Review" -pending: +vote-requested: color: "#b58900" - text: "Pending" - -dormant: - color: "#839496" - text: "Dormant" + text: "Vote Requested" -under-revision: - color: "#2aa198" - text: "Under Revision" +waiting-for-implementation: + color: "#b58900" + text: "Waiting for Implementation" accepted: color: "#859900" text: "Accepted" -complete: +shipped: color: "#859900" - text: "Complete" + text: "Shipped" rejected: color: "#dc322f" text: "Rejected" + +withdrawn: + color: "#839496" + text: "Withdrawn" + +accept: + color: "#859900" + text: "Accept" + +reject: + color: "#dc322f" + text: "Reject" diff --git a/_de/tutorials/scala-for-java-programmers.md b/_de/tutorials/scala-for-java-programmers.md index e4d64108b7..9055d7caea 100644 --- a/_de/tutorials/scala-for-java-programmers.md +++ b/_de/tutorials/scala-for-java-programmers.md @@ -23,7 +23,7 @@ einfach ist, eignet es sich sehr gut, Scalas Funktionsweise zu demonstrieren, oh über die Sprache wissen muss. object HalloWelt { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { println("Hallo, Welt!") } } @@ -93,7 +93,7 @@ Klassen der Java-Pakete importieren: import java.text.DateFormat._ object FrenchDate { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format now) @@ -183,7 +183,7 @@ einmal pro Sekunde aus. println("Die Zeit vergeht wie im Flug.") } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } } @@ -209,7 +209,7 @@ Variante des obigen Timer-Programmes verwendet eine anonyme Funktion anstatt der } } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(() => println("Die Zeit vergeht wie im Flug.")) } } @@ -256,7 +256,7 @@ Ein Problem der obigen Methoden `re` und `im` ist, dass man, um sie zu verwenden Klammerpaar hinter ihren Namen anhängen muss: object ComplexNumbers { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val c = new Complex(1.2, 3.4) println("imaginary part: " + c.im()) } @@ -433,7 +433,7 @@ noch aus. Zu diesem Zweck soll eine `main`-Methode dienen, die den Ausdruck `(x+ 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]) { + def main(args: Array[String]): Unit = { val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) val env: Environment = { case "x" => 5 @@ -597,7 +597,7 @@ Um diese Referenz-Klasse zu verwenden, muss der generische Typ bei der Erzeugung angegeben werden. Für einen Ganzzahl-Container soll folgendes Beispiel dienen: object IntegerReference { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) diff --git a/_es/overviews/core/actors.md b/_es/overviews/core/actors.md deleted file mode 100644 index 19851b6c26..0000000000 --- a/_es/overviews/core/actors.md +++ /dev/null @@ -1,494 +0,0 @@ ---- -layout: singlepage-overview -title: API de actores en Scala - -partof: actors - -language: es ---- - -**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` 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`. 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/parallel-collections/architecture.md b/_es/overviews/parallel-collections/architecture.md index 8e60e87a59..138a5dee08 100644 --- a/_es/overviews/parallel-collections/architecture.md +++ b/_es/overviews/parallel-collections/architecture.md @@ -87,7 +87,7 @@ de la librería de colecciones secuenciales -- de hecho, "replican" los correspo traits presentes en el framework de colecciones secuenciales, tal y como se muestra a continuación. -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) +[Hierarchy of Scala Collections and Parallel Collections]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png)
Jerarquía de clases de las librerías de colecciones secuenciales y paralelas de Scala

diff --git a/_es/tour/abstract-type-members.md b/_es/tour/abstract-type-members.md index 841fa8778e..1e9afc50d7 100644 --- a/_es/tour/abstract-type-members.md +++ b/_es/tour/abstract-type-members.md @@ -40,16 +40,14 @@ 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) -} +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]`. diff --git a/_es/tour/automatic-closures.md b/_es/tour/automatic-closures.md deleted file mode 100644 index bb26c5a665..0000000000 --- a/_es/tour/automatic-closures.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: tour -title: Construcción de closures automáticas -partof: scala-tour - -num: 16 -language: es - -next-page: operators -previous-page: multiple-parameter-lists ---- - -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/basics.md b/_es/tour/basics.md index ba3519ef02..484470a508 100644 --- a/_es/tour/basics.md +++ b/_es/tour/basics.md @@ -13,16 +13,14 @@ En esta página, practicaremos conceptos básicos de Scala. ## Probando Scala en el navegador -Puedes ejecutar Scala en tu navegador con ScalaFiddle. +Puedes ejecutar Scala en tu navegador con Scastie. -1. Ve a [https://scalafiddle.io](https://scalafiddle.io). +1. Ve a [Scastie](https://scastie.scala-lang.org/). 2. Escribe `println("Hello, world!")` en el panel a la izquierda. 3. Presiona el botón "Run". En el panel de la derecha aparecerá el resultado. Así, de manera fácil y sin preparación, puedes probar fragmentos de código Scala. -Muchos ejemplos de código en esta documentación están integrados con ScalaFiddle, y así puedes probarlos directamente solo con pulsar el botón "Run". - ## Expresiones Las expresiones son sentencias computables. @@ -33,14 +31,12 @@ Las expresiones son sentencias computables. Se puede ver el resultado de evaluar expresiones usando `println`. -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ## Valores @@ -110,21 +106,17 @@ La lista de parámetros de la función está a la izquierda de la flecha `=>`, y También podemos asignarle un nombre a la función. -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} Las funciones pueden tomar varios parámetros. -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} O ninguno. @@ -135,27 +127,23 @@ println(getTheAnswer()) // 42 ## Métodos -Los métodos se parecen y comportan casi como a las funciones, pero se diferencian en dos aspectos clave: +Los métodos se parecen y comportan casi como a las funciones, pero se diferencian en dos aspectos clave: Un método se define con la palabra reservada `def`, seguida por el nombre del método, la lista de parámetros, el tipo de valores que el método devuelve, y el cuerpo del método. -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} Observe que el tipo de retorno se declara _después_ de la lista de parámetros, y separado con dos puntos, p.ej. `: Int`. Un método puede tener varias listas de parámetros. -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} O ninguna lista de parámetros. @@ -168,7 +156,6 @@ Hay otras diferencias, pero para simplificar, podemos pensar que son similares a Los métodos también pueden tener expresiones de varias lineas. -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -176,7 +163,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} La ultima expresión en el cuerpo del método es el valor de retorno del mismo. (Scala tiene una palabra reservada `return`, pero se usa raramente y no se aconseja usarla) @@ -212,7 +198,7 @@ Una case class se define con las palabras reservadas `case class`: case class Point(x: Int, y: Int) ``` -Se puede crear una instancia de una `case class`, sin usar la palabra reservada `new`. +Se puede crear una instancia de una `case class`, sin usar la palabra reservada `new`. ```scala mdoc val point = Point(1, 2) @@ -224,15 +210,15 @@ Y son comparadas por valor. ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$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.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) and Point(2,2) are different. ``` @@ -279,7 +265,6 @@ trait Greeter { Un `trait` también puede definir un método, o un valor, con una implementación por defecto. -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -304,7 +289,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} Aquí, `DefaultGreeter` extiende un solo trait, pero puede extender múltiples traits. diff --git a/_es/tour/case-classes.md b/_es/tour/case-classes.md index c47a3b9428..7a4989bde5 100644 --- a/_es/tour/case-classes.md +++ b/_es/tour/case-classes.md @@ -19,7 +19,7 @@ A continuación se muestra un ejemplo para una jerarquía de clases la cual cons 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](https://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. +Esta jerarquía de clases puede ser usada para representar términos de [cálculo lambda no tipado](https://es.wikipedia.org/wiki/C%C3%A1lculo_lambda). 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: diff --git a/_es/tour/classes.md b/_es/tour/classes.md index 90bd399be0..3f3939b3bc 100644 --- a/_es/tour/classes.md +++ b/_es/tour/classes.md @@ -29,7 +29,7 @@ Las clases en Scala son parametrizadas con argumentos constructores (inicializad Para instanciar una clase es necesario usar la primitiva `new`, como se muestra en el siguiente ejemplo: object Classes { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val pt = new Point(1, 2) println(pt) pt.move(10, 10) diff --git a/_es/tour/generic-classes.md b/_es/tour/generic-classes.md index 2a1ed5a2ba..b89b603ae3 100644 --- a/_es/tour/generic-classes.md +++ b/_es/tour/generic-classes.md @@ -14,13 +14,15 @@ Tal como en Java 5, Scala provee soporte nativo para clases parametrizados con t A continuación se muestra un ejemplo: - class Stack[T] { - var elems: List[T] = Nil - def push(x: T): Unit = - elems = x :: elems - def top: T = elems.head - def pop() { elems = elems.tail } - } +```scala mdoc +class Stack[T] { + var elems: List[T] = Nil + def push(x: T): Unit = + elems = x :: elems + def top: T = elems.head + def pop(): Unit = { 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`). diff --git a/_es/tour/inner-classes.md b/_es/tour/inner-classes.md index 461c72eeb1..9b04862d27 100644 --- a/_es/tour/inner-classes.md +++ b/_es/tour/inner-classes.md @@ -12,44 +12,50 @@ 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.exists(node.equals)) { - connectedNodes = node :: connectedNodes - } - } - } - var nodes: List[Node] = Nil - def newNode: Node = { - val res = new Node - nodes = res :: nodes - res +```scala mdoc +class Graph { + class Node { + var connectedNodes: List[Node] = Nil + def connectTo(node: Node): Unit = { + if (!connectedNodes.exists(node.equals)) { + 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) - } +```scala mdoc:nest +def graphTest: Unit = { + 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) - } +```scala mdoc:nest +def graphTest: Unit = { + 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. @@ -70,7 +76,7 @@ Por favor note que en Java la última linea del ejemplo anterior hubiese sido co class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil // Graph#Node en lugar de Node - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } diff --git a/_es/tour/mixin-class-composition.md b/_es/tour/mixin-class-composition.md index 9221859891..bd53274158 100644 --- a/_es/tour/mixin-class-composition.md +++ b/_es/tour/mixin-class-composition.md @@ -22,7 +22,7 @@ A diferencia de lenguajes que solo soportan _herencia simple_, Scala tiene una n 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()) } + def foreach(f: T => Unit): Unit = { while (hasNext) f(next()) } } Aquí se muestra una clase iterador concreta, la cual retorna caracteres sucesivos de una cadena de caracteres dada: @@ -37,7 +37,7 @@ Aquí se muestra una clase iterador concreta, la cual retorna caracteres sucesiv 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]) { + def main(args: Array[String]): Unit = { class Iter extends StringIterator("Scala") with RichIterator val iter = new Iter iter foreach println diff --git a/_es/tour/multiple-parameter-lists.md b/_es/tour/multiple-parameter-lists.md index 9ed0531042..83b7218c0b 100644 --- a/_es/tour/multiple-parameter-lists.md +++ b/_es/tour/multiple-parameter-lists.md @@ -6,7 +6,7 @@ partof: scala-tour num: 15 language: es -next-page: automatic-closures +next-page: operators previous-page: nested-functions --- @@ -26,18 +26,15 @@ def foldLeft[B](z: B)(op: (B, A) => B): B Comenzando con un valor inicial 0, `foldLeft` aplica la función `(m, n) => m + n` a cada uno de los elementos de la lista y al valor acumulado previo. -{% scalafiddle %} ```scala mdoc val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(0)((m, n) => m + n) println(res) // 55 ``` -{% endscalafiddle %} A continuación se muestra otro ejemplo: -{% scalafiddle %} ```scala mdoc object CurryTest extends App { @@ -53,7 +50,6 @@ A continuación se muestra otro ejemplo: println(filter(nums, modN(3))) } ``` -{% endscalafiddle %} _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`._ diff --git a/_es/tour/named-arguments.md b/_es/tour/named-arguments.md index 38dd0574d4..fe38fe15d4 100644 --- a/_es/tour/named-arguments.md +++ b/_es/tour/named-arguments.md @@ -11,24 +11,24 @@ 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) = { + def imprimirNombre(nombre: String, apellido: String) = { println(nombre + " " + apellido) } imprimirNombre("John","Smith") // Imprime "John Smith" - imprimirNombre(first = "John",last = "Smith") + imprimirNombre(nombre = "John", apellido = "Smith") // Imprime "John Smith" - imprimirNombre(last = "Smith",first = "John") + imprimirNombre(apellido = "Smith", nombre = "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: - def imprimirNombre(nombre:String = "John", apellido:String = "Smith") = { + def imprimirNombre(nombre: String = "John", apellido: String = "Smith") = { println(nombre + " " + apellido) } - printName(apellido = "Jones") + imprimirNombre(apellido = "Jones") // Imprime "John Jones" language: es diff --git a/_es/tour/operators.md b/_es/tour/operators.md index a2d3b5e4be..6aeb98e046 100644 --- a/_es/tour/operators.md +++ b/_es/tour/operators.md @@ -7,7 +7,7 @@ num: 17 language: es next-page: higher-order-functions -previous-page: automatic-closures +previous-page: multiple-parameter-lists --- 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`. diff --git a/_es/tour/self-types.md b/_es/tour/self-types.md index 79714212a7..df02b7dc0a 100644 --- a/_es/tour/self-types.md +++ b/_es/tour/self-types.md @@ -91,7 +91,7 @@ Por favor nótese que en esta clase nos es posible instanciar `NodoImpl` porque Aquí hay un ejemplo de uso de la clase `GrafoDirigidoConcreto`: - object GraphTest extends App { + def graphTest: Unit = { val g: Grafo = new GrafoDirigidoConcreto val n1 = g.agregarNodo val n2 = g.agregarNodo diff --git a/_es/tour/singleton-objects.md b/_es/tour/singleton-objects.md index 83aa22ef9b..dceed2d7ad 100644 --- a/_es/tour/singleton-objects.md +++ b/_es/tour/singleton-objects.md @@ -26,7 +26,7 @@ Un objeto singleton puede extender clases y _traits_. De hecho, una [clase Case] ## 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. +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 clase 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](/style/scaladoc.html) 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. diff --git a/_es/tour/tour-of-scala.md b/_es/tour/tour-of-scala.md index 19b4f60af8..b742b271ab 100644 --- a/_es/tour/tour-of-scala.md +++ b/_es/tour/tour-of-scala.md @@ -37,7 +37,6 @@ El [mecanismo de inferencia de tipos locales](type-inference.html) se encarga de 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 bibliotecas: * 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. diff --git a/_es/tour/unified-types.md b/_es/tour/unified-types.md index 5f37f7b47d..3a1db1e651 100644 --- a/_es/tour/unified-types.md +++ b/_es/tour/unified-types.md @@ -17,7 +17,7 @@ A diferencia de Java, todos los valores en Scala son objetos (incluyendo valores ## 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. +Por favor note que el diagrama superior también muestra conversiones implícitas llamadas vistas 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: diff --git a/_es/tutorials/scala-for-java-programmers.md b/_es/tutorials/scala-for-java-programmers.md index f4cc568f84..120d93d316 100644 --- a/_es/tutorials/scala-for-java-programmers.md +++ b/_es/tutorials/scala-for-java-programmers.md @@ -18,7 +18,7 @@ Este documento provee una rápida introducción al lenguaje Scala como también 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]) { + def main(args: Array[String]): Unit = { println("¡Hola, mundo!") } } @@ -59,7 +59,7 @@ Las librerías de clases de Java definen clases de utilería poderosas, como `Da import java.text.DateFormat._ object FrenchDate { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val ahora = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format ahora) @@ -116,7 +116,7 @@ En el siguiente programa, la función del temporizador se llama `unaVezPorSegund def tiempoVuela() { println("El tiempo vuela como una flecha...") } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { unaVezPorSegundo(tiempoVuela) } } @@ -134,7 +134,7 @@ El programa anterior es fácil de entender, pero puede ser refinado aún más. P Thread sleep 1000 } } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { unaVezPorSegundo( () => println("El tiempo vuela como una flecha...") ) @@ -167,7 +167,7 @@ El compilador no es siempre capaz de inferir los tipos como lo hace aquí, y des 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]) { + def main(args: Array[String]): Unit = { val c = new Complejo(1.2, 3.4) println("Parte imaginaria: " + c.im()) } @@ -282,7 +282,7 @@ Esta función introduce dos nuevos conceptos relacionados al pattern matching. P 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]) { + def main(args: Array[String]): Unit = { 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) @@ -386,7 +386,7 @@ El ejemplo anterior introduce a las variables en Scala, que no deberían requeri 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]) { + def main(args: Array[String]): Unit = { val ref = new Referencia[Int] ref.set(13) println("La referencia tiene la mitad de " + (ref.get * 2)) diff --git a/_fr/getting-started/index.md b/_fr/getting-started/install-scala.md similarity index 84% rename from _fr/getting-started/index.md rename to _fr/getting-started/install-scala.md index f9b05d7df9..76f7c537f9 100644 --- a/_fr/getting-started/index.md +++ b/_fr/getting-started/install-scala.md @@ -24,15 +24,46 @@ Nous recommandons l'utilisation de l'outil d'installation "Coursier" qui va auto ### Utilisation de l'installateur Scala (recommandé) L'installateur Scala est un outil nommé [Coursier](https://get-coursier.io/docs/cli-overview), la commande principale de l'outil est `cs`. -Il s'assure que la JVM est les outils standards de Scala sont installés sur votre système. +Il s'assure que la JVM est les outils standards de Scala sont installés sur votre système. Installez-le sur votre système avec les instructions suivantes. -
-
-

Suivez les instructions pour installer la commande cs puis exécutez :

-

$ ./cs setup

-
-
+ +{% tabs install-cs-setup-tabs class=platform-os-options %} + + +{% tab macOS for=install-cs-setup-tabs %} +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-brew %} +{% altDetails cs-setup-macos-nobrew "Alternativement, si vous n'utilisez pas Homebrew:" %} + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-x86-64 %} +{% endaltDetails %} +{% endtab %} + + + +{% tab Linux for=install-cs-setup-tabs %} + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-x86-64 %} +{% endtab %} + + + +{% tab Windows for=install-cs-setup-tabs %} + Téléchargez et exécutez [l'intallateur Scala pour Windows]({{site.data.setup-scala.windows-link}}) basé sur Coursier. +{% endtab %} + + + +{% tab Other for=install-cs-setup-tabs defaultTab %} + + Suivez + [les instructions pour installer la commande `cs`](https://get-coursier.io/docs/cli-installation) + puis exécutez `./cs setup`. +{% endtab %} + + +{% endtabs %} + En plus de gérer les JVMs, `cs setup` installe aussi des utilitaires en ligne de commande : @@ -157,7 +188,7 @@ Une fois que vous avez terminé le tutoriel ce dessus, vous pouvez consulter : * [The Scala Book](/scala3/book/introduction.html) ([Lien](/overviews/scala-book/introduction.html) vers la version Scala 2), qui fournit un ensemble de courtes leçons et introduit les fonctionnalités principales de Scala. * [The Tour of Scala](/tour/tour-of-scala.html) pour une introduction des fonctionnalités Scala. -* [Learning Resources](/learn.html), qui contient des tutoriels et des cours interactifs. +* [Learning Courses](/online-courses.html), qui contient des tutoriels et des cours interactifs. * [Our list of some popular Scala books](/books.html). * [The migration guide](/scala3/guides/migration/compatibility-intro.html) pour vous aider à migrer votre code Scala 2 vers Scala 3. @@ -165,30 +196,3 @@ Une fois que vous avez terminé le tutoriel ce dessus, vous pouvez consulter : Il y a plusieurs listes de diffusion et canaux de discussions instantanés si vous souhaitez rencontrer rapidement d'autres utilisateurs de Scala. Allez faire un tour sur notre page [community](https://scala-lang.org/community/) pour consulter la liste des ces ressources et obtenir de l'aide. Traduction par Antoine Pointeau. - - - - - - - - - diff --git a/_fr/tour/automatic-closures.md b/_fr/tour/automatic-closures.md deleted file mode 100644 index f5a06a5f5f..0000000000 --- a/_fr/tour/automatic-closures.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: tour -title: Automatic Closures -partof: scala-tour - -language: fr ---- diff --git a/_fr/tour/extractor-objects.md b/_fr/tour/extractor-objects.md index 9ffc529a58..1f864b7f39 100644 --- a/_fr/tour/extractor-objects.md +++ b/_fr/tour/extractor-objects.md @@ -12,13 +12,13 @@ previous-page: regular-expression-patterns --- Un objet extracteur est un objet avec une méthode `unapply`. Tandis que la méthode `apply` ressemble à un constructeur qui prend des arguments et crée un objet, `unapply` prend un object et essaye de retourner ses arguments. Il est utilisé le plus souvent en filtrage par motif (*pattern matching*) ou avec les fonctions partielles. - + ```scala mdoc import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") @@ -35,7 +35,7 @@ customer1ID match { La méthode `apply` crée une chaîne de caractères `CustomerID` depuis `name`. La méthode `unapply` fait l'inverse pour retrouver le `name`. Lorsqu'on appelle `CustomerID("Sukyoung")`, c'est un raccourci pour `CustomerID.apply("Sukyoung")`. Lorsqu'on appelle `case CustomerID(name) => println(name)`, on appelle la méthode `unapply` avec `CustomerID.unapply(customer1ID)`. -Sachant qu'une définition de valeur peut utiliser une décomposition pour introduire une nouvelle variable, un extracteur peut être utilisé pour initialiser la variable, avec la méthode `unapply` pour fournir la valeur. +Sachant qu'une définition de valeur peut utiliser une décomposition pour introduire une nouvelle variable, un extracteur peut être utilisé pour initialiser la variable, avec la méthode `unapply` pour fournir la valeur. ```scala mdoc val customer2ID = CustomerID("Nico") @@ -63,4 +63,4 @@ Le type de retour de `unapply` doit être choisi comme suit : Parfois, le nombre de valeurs à extraire n'est pas fixe et on souhaiterait retourner un nombre arbitraire de valeurs, en fonction des données d'entrée. Pour ce cas, vous pouvez définir des extracteurs avec la méthode `unapplySeq` qui retourne un `Option[Seq[T]]`. Un exemple commun d'utilisation est la déconstruction d'une liste en utilisant `case List(x, y, z) =>`. Un autre est la décomposition d'une `String` en utilisant une expression régulière `Regex`, comme `case r(name, remainingFields @ _*) =>`. -Traduit par Antoine Pointeau. \ No newline at end of file +Traduit par Antoine Pointeau. diff --git a/_fr/tour/tour-of-scala.md b/_fr/tour/tour-of-scala.md index cb9fb2fcc6..f5d0f5d20a 100644 --- a/_fr/tour/tour-of-scala.md +++ b/_fr/tour/tour-of-scala.md @@ -14,8 +14,8 @@ Ce tour contient une introduction morceaux par morceaux aux fonctionnalités les utilisées en Scala. Il est adressé aux novices de Scala. Ceci est un bref tour du language, non pas un tutoriel complet. -Si vous recherchez un guide plus détaillé, il est préférable d'opter pour [un livre](/books.html) ou de consulter -[d'autres ressources](/learn.html). +Si vous recherchez un guide plus détaillé, il est préférable d'opter pour [un livre](/books.html) ou de suivre +[un cours en ligne](/online-courses.html). ## Qu'est-ce que le Scala ? Scala est un langage de programmation à multiples paradigmes désigné pour exprimer des motifs de programmation communs de diff --git a/_getting-started/index.md b/_getting-started/index.md deleted file mode 100644 index 03af9270c4..0000000000 --- a/_getting-started/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: singlepage-overview -title: Getting Started -partof: getting-started -languages: [fr, ja] -includeTOC: true - -redirect_from: "/getting-started.html" ---- - -{% include getting-started.md %} diff --git a/_glossary/index.md b/_glossary/index.md index 3eba952f88..9d4d490c65 100644 --- a/_glossary/index.md +++ b/_glossary/index.md @@ -16,380 +16,380 @@ languages: [zh-cn]   -* #### algebraic data type +* ### algebraic data type A type defined by providing several alternatives, each of which comes with its own constructor. It usually comes with a way to decompose the type through pattern matching. The concept is found in specification languages and functional programming languages. Algebraic data types can be emulated in Scala with case classes. -* #### alternative +* ### alternative A branch of a match expression. It has the form “`case` _pattern_ => _expression_.” Another name for alternative is _case_. -* #### annotation +* ### annotation An annotation appears in source code and is attached to some part of the syntax. Annotations are computer processable, so you can use them to effectively add an extension to Scala. -* #### anonymous class +* ### anonymous class An anonymous class is a synthetic subclass generated by the Scala compiler from a new expression in which the class or trait name is followed by curly braces. The curly braces contains the body of the anonymous subclass, which may be empty. However, if the name following new refers to a trait or class that contains abstract members, these must be made concrete inside the curly braces that define the body of the anonymous subclass. -* #### anonymous function +* ### anonymous function Another name for [function literal](#function-literal). -* #### apply +* ### apply You can apply a method, function, or closure to arguments, which means you invoke it on those arguments. -* #### argument +* ### argument When a function is invoked, an argument is passed for each parameter of that function. The parameter is the variable that refers to the argument. The argument is the object passed at invocation time. In addition, applications can take (command line) arguments that show up in the `Array[String]` passed to main methods of singleton objects. -* #### assign +* ### assign You can assign an object to a variable. Afterwards, the variable will refer to the object. -* #### auxiliary constructor +* ### auxiliary constructor Extra constructors defined inside the curly braces of the class definition, which look like method definitions named `this`, but with no result type. -* #### block -One or more expressions and declarations surrounded by curly braces. When the block evaluates, all of its expressions and declarations are processed in order, and then the block returns the value of the last expression as its own value. Blocks are commonly used as the bodies of functions, [for expressions](#for-expression), `while` loops, and any other place where you want to group a number of statements together. More formally, a block is an encapsulation construct for which you can only see side effects and a result value. The curly braces in which you define a class or object do not, therefore, form a block, because fields and methods (which are defined inside those curly braces) are visible from the out- side. Such curly braces form a template. +* ### block +One or more expressions and declarations surrounded by curly braces. When the block evaluates, all of its expressions and declarations are processed in order, and then the block returns the value of the last expression as its own value. Blocks are commonly used as the bodies of functions, [for expressions](#for-expression), `while` loops, and any other place where you want to group a number of statements together. More formally, a block is an encapsulation construct for which you can only see side effects and a result value. The curly braces in which you define a class or object do not, therefore, form a block, because fields and methods (which are defined inside those curly braces) are visible from the outside. Such curly braces form a template. -* #### bound variable +* ### bound variable A bound variable of an expression is a variable that’s both used and defined inside the expression. For instance, in the function literal expression `(x: Int) => (x, y)`, both variables `x` and `y` are used, but only `x` is bound, because it is defined in the expression as an `Int` and the sole argument to the function described by the expression. -* #### by-name parameter +* ### by-name parameter A parameter that is marked with a `=>` in front of the parameter type, e.g., `(x: => Int)`. The argument corresponding to a by-name parameter is evaluated not before the method is invoked, but each time the parameter is referenced by name inside the method. If a parameter is not by-name, it is by-value. -* #### by-value parameter +* ### by-value parameter A parameter that is not marked with a `=>` in front of the parameter type, e.g., `(x: Int)`. The argument corresponding to a by-value parameter is evaluated before the method is invoked. By-value parameters contrast with by-name parameters. -* #### class +* ### class Defined with the `class` keyword, a _class_ may either be abstract or concrete, and may be parameterized with types and values when instantiated. In `new Array[String](2)`, the class being instantiated is `Array` and the type of the value that results is `Array[String]`. A class that takes type parameters is called a _type constructor_. A type can be said to have a class as well, as in: the class of type `Array[String]` is `Array`. -* #### closure +* ### closure A function object that captures free variables, and is said to be “closed” over the variables visible at the time it is created. -* #### companion class +* ### companion class A class that shares the same name with a singleton object defined in the same source file. The class is the singleton object’s companion class. -* #### companion object +* ### companion object A singleton object that shares the same name with a class defined in the same source file. Companion objects and classes have access to each other’s private members. In addition, any implicit conversions defined in the companion object will be in scope anywhere the class is used. -* #### contravariant +* ### contravariant A _contravariant_ annotation can be applied to a type parameter of a class or trait by putting a minus sign (-) before the type parameter. The class or trait then subtypes contravariantly with—in the opposite direction as—the type annotated parameter. For example, `Function1` is contravariant in its first type parameter, and so `Function1[Any, Any]` is a subtype of `Function1[String, Any]`. -* #### covariant +* ### covariant A _covariant_ annotation can be applied to a type parameter of a class or trait by putting a plus sign (+) before the type parameter. The class or trait then subtypes covariantly with—in the same direction as—the type annotated parameter. For example, `List` is covariant in its type parameter, so `List[String]` is a subtype of `List[Any]`. -* #### currying +* ### currying A way to write functions with multiple parameter lists. For instance `def f(x: Int)(y: Int)` is a curried function with two parameter lists. A curried function is applied by passing several arguments lists, as in: `f(3)(4)`. However, it is also possible to write a _partial application_ of a curried function, such as `f(3)`. -* #### declare +* ### declare You can _declare_ an abstract field, method, or type, which gives an entity a name but not an implementation. The key difference between declarations and definitions is that definitions establish an implementation for the named entity, declarations do not. -* #### define +* ### define To _define_ something in a Scala program is to give it a name and an implementation. You can define classes, traits, singleton objects, fields, methods, local functions, local variables, _etc_. Because definitions always involve some kind of implementation, abstract members are declared not defined. -* #### direct subclass +* ### direct subclass A class is a _direct subclass_ of its direct superclass. -* #### direct superclass +* ### direct superclass The class from which a class or trait is immediately derived, the nearest class above it in its inheritance hierarchy. If a class `Parent` is mentioned in a class `Child`’s optional extends clause, then `Parent` is the direct superclass of `Child`. If a trait is mentioned in `Child`’s extends clause, the trait’s direct superclass is the `Child`’s direct superclass. If `Child` has no extends clause, then `AnyRef` is the direct superclass of `Child`. If a class’s direct superclass takes type parameters, for example class `Child` extends `Parent[String]`, the direct superclass of `Child` is still `Parent`, not `Parent[String]`. On the other hand, `Parent[String]` would be the direct supertype of `Child`. See [supertype](#supertype) for more discussion of the distinction between class and type. -* #### equality +* ### equality When used without qualification, _equality_ is the relation between values expressed by `==`. See also [reference equality](#reference-equality). -* #### existential type +* ### existential type An existential type includes references to type variables that are unknown. For example, `Array[T] forSome { type T }` is an existential type. It is an array of `T`, where `T` is some completely unknown type. All that is assumed about `T` is that it exists at all. This assumption is weak, but it means at least that an `Array[T] forSome { type T }` is indeed an array and not a banana. -* #### expression +* ### expression Any bit of Scala code that yields a result. You can also say that an expression _evaluates_ to a result or _results_ in a value. -* #### filter +* ### filter An `if` followed by a boolean expression in a [for expression](#for-expression). In `for(i <- 1 to 10; if i % 2 == 0)`, the filter is “`if i % 2 == 0`”. The value to the right of the `if` is the [filter expression](#filter-expression). Also known as a guard. -* #### filter expression +* ### filter expression A _filter expression_ is the boolean expression following an `if` in a [for expression](#for-expression). In `for( i <- 1 to 10 ; if i % 2 == 0)`,the filter expression is “`i % 2 == 0`”. -* #### first-class function +* ### first-class function Scala supports _first-class functions_, which means you can express functions in function literal syntax, i.e., `(x: Int) => x + 1`, and that functions can be represented by objects, which are called [function values](#function-value). -* #### for comprehension +* ### for comprehension A _for comprehension_ is a type of [for expression](#for-expression) that creates a new collection. For each iteration of the `for` comprehension, the [yield](#yield) clause defines an element of the new collection. For example, `for (i <- (0 until 2); j <- (2 until 4)) yield (i, j)` returns the collection `Vector((0,2), (0,3), (1,2), (1,3))`. -* #### for expression +* ### for expression A _for expression_ is either a [for loop](#for-loop), which iterates over one or more collections, or a [for comprehension](#for-comprehension), which builds a new collection from the elements of one or more collections. A `for` expression is built up of [generators](#generator), [filters](#filter), variable definitions, and (in the case of [for comprehensions](#for-comprehension)) a [yield](#yield) clause. -* #### for loop +* ### for loop A _for loop_ is a type of [for expression](#for-expression) that loops over one or more collections. Since `for` loops return unit, they usually produce side-effects. For example, `for (i <- 0 until 100) println(i)` prints the numbers 0 through 99. -* #### free variable +* ### free variable A _free variable_ of an expression is a variable that’s used inside the expression but not defined inside the expression. For instance, in the function literal expression `(x: Int) => (x, y)`, both variables `x` and `y` are used, but only `y` is a free variable, because it is not defined inside the expression. -* #### function +* ### function A _function_ can be [invoked](#invoke) with a list of arguments to produce a result. A function has a parameter list, a body, and a result type. Functions that are members of a class, trait, or singleton object are called [methods](#method). Functions defined inside other functions are called [local functions](#local-function). Functions with the result type of `Unit` are called [procedures](#procedure). Anonymous functions in source code are called [function literals](#function-literal). At run time, function literals are instantiated into objects called [function values](#function-value). -* #### function literal +* ### function literal A function with no name in Scala source code, specified with _function literal_ syntax. For example, `(x: Int, y: Int) => x + y`. -* #### function value +* ### function value A function object that can be invoked just like any other function. A _function value_’s class extends one of the `FunctionN` traits (e.g., `Function0`, `Function1`) from package `scala`, and is usually expressed in source code via [function literal](#function-literal) syntax. A function value is “invoked” when its apply method is called. A function value that captures free variables is a [closure](#closure). -* #### functional style +* ### functional style The _functional style_ of programming emphasizes functions and evaluation results and deemphasizes the order in which operations occur. The style is characterized by passing function values into looping methods, immutable data, methods with no side effects. It is the dominant paradigm of languages such as Haskell and Erlang, and contrasts with the [imperative style](#imperative-style). -* #### generator +* ### generator A _generator_ defines a named val and assigns to it a series of values in a [for expression](#for-expression). For example, in `for(i <- 1 to 10)`, the generator is “`i <- 1 to 10`”. The value to the right of the `<-` is the [generator expression](#generator-expression). -* #### generator expression +* ### generator expression A _generator expression_ generates a series of values in a [for expression](#for-expression). For example, in `for(i <- 1 to 10)`, the generator expression is “`1 to 10`”. -* #### generic class +* ### generic class A class that takes type parameters. For example, because `scala.List` takes a type parameter, `scala.List` is a _generic class_. -* #### generic trait +* ### generic trait A trait that takes type parameters. For example, because trait `scala.collection.Set` takes a type parameter, it is a _generic trait_. -* #### guard +* ### guard See [filter](#filter). -* #### helper function +* ### helper function A function whose purpose is to provide a service to one or more other functions nearby. Helper functions are often implemented as local functions. -* #### helper method +* ### helper method A [helper function](#helper-function) that’s a member of a class. Helper methods are often private. -* #### immutable +* ### immutable An object is _immutable_ if its value cannot be changed after it is created in any way visible to clients. Objects may or may not be immutable. -* #### imperative style +* ### imperative style The _imperative style_ of programming emphasizes careful sequencing of operations so that their effects happen in the right order. The style is characterized by iteration with loops, mutating data in place, and methods with side effects. It is the dominant paradigm of languages such as C, C++, C# and Java, and contrasts with the [functional style](#functional-style). -* #### initialize +* ### initialize When a variable is defined in Scala source code, you must _initialize_ it with an object. -* #### instance +* ### instance An _instance_, or class instance, is an object, a concept that exists only at run time. -* #### instantiate +* ### instantiate To _instantiate_ a class is to make a new object from the class, an action that happens only at run time. -* #### invariant +* ### invariant _Invariant_ is used in two ways. It can mean a property that always holds true when a data structure is well-formed. For example, it is an invariant of a sorted binary tree that each node is ordered before its right subnode, if it has a right subnode. Invariant is also sometimes used as a synonym for nonvariant: “class `Array` is invariant in its type parameter.” -* #### invoke +* ### invoke You can _invoke_ a method, function, or closure _on_ arguments, meaning its body will be executed with the specified arguments. -* #### JVM +* ### JVM The _JVM_ is the Java Virtual Machine, or [runtime](#runtime), that hosts a running Scala program. -* #### literal +* ### literal `1`, `"One"`, and `(x: Int) => x + 1` are examples of _literals_. A literal is a shorthand way to describe an object, where the shorthand exactly mirrors the structure of the created object. -* #### local function +* ### local function A _local function_ is a `def` defined inside a block. To contrast, a `def` defined as a member of a class, trait, or singleton object is called a [method](#method). -* #### local variable +* ### local variable A _local variable_ is a `val` or `var` defined inside a block. Although similar to [local variables](#local-variable), parameters to functions are not referred to as local variables, but simply as parameters or “variables” without the “local.” -* #### member +* ### member A _member_ is any named element of the template of a class, trait, or singleton object. A member may be accessed with the name of its owner, a dot, and its simple name. For example, top-level fields and methods defined in a class are members of that class. A trait defined inside a class is a member of its enclosing class. A type defined with the type keyword in a class is a member of that class. A class is a member of the package in which is it defined. By contrast, a local variable or local function is not a member of its surrounding block. -* #### message +* ### message Actors communicate with each other by sending each other _messages_. Sending a message does not interrupt what the receiver is doing. The receiver can wait until it has finished its current activity and its invariants have been reestablished. -* #### meta-programming +* ### meta-programming Meta-programming software is software whose input is itself software. Compilers are meta-programs, as are tools like `scaladoc`. Meta-programming software is required in order to do anything with an annotation. -* #### method +* ### method A _method_ is a function that is a member of some class, trait, or singleton object. -* #### mixin +* ### mixin _Mixin_ is what a trait is called when it is being used in a mixin composition. In other words, in “`trait Hat`,” `Hat` is just a trait, but in “`new Cat extends AnyRef with Hat`,” `Hat` can be called a mixin. When used as a verb, “mix in” is two words. For example, you can _mix_ traits _in_ to classes or other traits. -* #### mixin composition +* ### mixin composition The process of mixing traits into classes or other traits. _Mixin composition_ differs from traditional multiple inheritance in that the type of the super reference is not known at the point the trait is defined, but rather is determined anew each time the trait is mixed into a class or other trait. -* #### modifier +* ### modifier A keyword that qualifies a class, trait, field, or method definition in some way. For example, the `private` modifier indicates that a class, trait, field, or method being defined is private. -* #### multiple definitions +* ### multiple definitions The same expression can be assigned in _multiple definitions_ if you use the syntax `val v1, v2, v3 = exp`. -* #### nonvariant +* ### nonvariant A type parameter of a class or trait is by default _nonvariant_. The class or trait then does not subtype when that parameter changes. For example, because class `Array` is nonvariant in its type parameter, `Array[String]` is neither a subtype nor a supertype of `Array[Any]`. -* #### operation +* ### operation In Scala, every _operation_ is a method call. Methods may be invoked in _operator notation_, such as `b + 2`, and when in that notation, `+` is an _operator_. -* #### parameter +* ### parameter Functions may take zero to many _parameters_. Each parameter has a name and a type. The distinction between parameters and arguments is that arguments refer to the actual objects passed when a function is invoked. Parameters are the variables that refer to those passed arguments. -* #### parameterless function +* ### parameterless function A function that takes no parameters, which is defined without any empty parentheses. Invocations of parameterless functions may not supply parentheses. This supports the [uniform access principle](#uniform-access-principle), which enables the `def` to be changed into a `val` without requiring a change to client code. -* #### parameterless method +* ### parameterless method A _parameterless method_ is a parameterless function that is a member of a class, trait, or singleton object. -* #### parametric field +* ### parametric field A field defined as a class parameter. -* #### partially applied function +* ### partially applied function A function that’s used in an expression and that misses some of its arguments. For instance, if function `f` has type `Int => Int => Int`, then `f` and `f(1)` are _partially applied functions_. -* #### path-dependent type +* ### path-dependent type A type like `swiss.cow.Food`. The `swiss.cow` part is a path that forms a reference to an object. The meaning of the type is sensitive to the path you use to access it. The types `swiss.cow.Food` and `fish.Food`, for example, are different types. -* #### pattern +* ### pattern In a `match` expression alternative, a _pattern_ follows each `case` keyword and precedes either a _pattern guard_ or the `=>` symbol. -* #### pattern guard +* ### pattern guard In a `match` expression alternative, a _pattern guard_ can follow a [pattern](#pattern). For example, in “`case x if x % 2 == 0 => x + 1`”, the pattern guard is “`if x % 2 == 0`”. A case with a pattern guard will only be selected if the pattern matches and the pattern guard yields true. -* #### predicate +* ### predicate A _predicate_ is a function with a `Boolean` result type. -* #### primary constructor +* ### primary constructor The main constructor of a class, which invokes a superclass constructor, if necessary, initializes fields to passed values, and executes any top-level code defined between the curly braces of the class. Fields are initialized only for value parameters not passed to the superclass constructor, except for any that are not used in the body of the class and can therefore be optimized away. -* #### procedure +* ### procedure A _procedure_ is a function with result type of `Unit`, which is therefore executed solely for its side effects. -* #### reassignable +* ### reassignable A variable may or may not be _reassignable_. A `var` is reassignable while a `val` is not. -* #### recursive +* ### recursive A function is _recursive_ if it calls itself. If the only place the function calls itself is the last expression of the function, then the function is [tail recursive](#tail-recursive). -* #### reference +* ### reference A _reference_ is the Java abstraction of a pointer, which uniquely identifies an object that resides on the JVM’s heap. Reference type variables hold references to objects, because reference types (instances of `AnyRef`) are implemented as Java objects that reside on the JVM’s heap. Value type variables, by contrast, may sometimes hold a reference (to a boxed wrapper type) and sometimes not (when the object is being represented as a primitive value). Speaking generally, a Scala variable [refers](#refers) to an object. The term “refers” is more abstract than “holds a reference.” If a variable of type `scala.Int` is currently represented as a primitive Java `int` value, then that variable still refers to the `Int` object, but no reference is involved. -* #### reference equality +* ### reference equality _Reference equality_ means that two references identify the very same Java object. Reference equality can be determined, for reference types only, by calling `eq` in `AnyRef`. (In Java programs, reference equality can be determined using `==` on Java [reference types](#reference-type).) -* #### reference type +* ### reference type A _reference type_ is a subclass of `AnyRef`. Instances of reference types always reside on the JVM’s heap at run time. -* #### referential transparency +* ### referential transparency A property of functions that are independent of temporal context and have no side effects. For a particular input, an invocation of a referentially transparent function can be replaced by its result without changing the program semantics. -* #### refers +* ### refers A variable in a running Scala program always _refers_ to some object. Even if that variable is assigned to `null`, it conceptually refers to the `Null` object. At runtime, an object may be implemented by a Java object or a value of a primitive type, but Scala allows programmers to think at a higher level of abstraction about their code as they imagine it running. See also [reference](#reference). -* #### refinement type +* ### refinement type A type formed by supplying a base type with a number of members inside curly braces. The members in the curly braces refine the types that are present in the base type. For example, the type of “animal that eats grass” is `Animal { type SuitableFood = Grass }`. -* #### result +* ### result An expression in a Scala program yields a _result_. The result of every expression in Scala is an object. -* #### result type +* ### result type A method’s _result type_ is the type of the value that results from calling the method. (In Java, this concept is called the return type.) -* #### return +* ### return A function in a Scala program _returns_ a value. You can call this value the [result](#result) of the function. You can also say the function _results in_ the value. The result of every function in Scala is an object. -* #### runtime +* ### runtime The Java Virtual Machine, or [JVM](#jvm), that hosts a running Scala program. Runtime encompasses both the virtual machine, as defined by the Java Virtual Machine Specification, and the runtime libraries of the Java API and the standard Scala API. The phrase at run time (with a space between run and time) means when the program is running, and contrasts with compile time. -* #### runtime type +* ### runtime type The type of an object at run time. To contrast, a [static type](#static-type) is the type of an expression at compile time. Most runtime types are simply bare classes with no type parameters. For example, the runtime type of `"Hi"` is `String`, and the runtime type of `(x: Int) => x + 1` is `Function1`. Runtime types can be tested with `isInstanceOf`. -* #### script +* ### script A file containing top level definitions and statements, which can be run directly with `scala` without explicitly compiling. A script must end in an expression, not a definition. -* #### selector +* ### selector The value being matched on in a `match` expression. For example, in “`s match { case _ => }`”, the selector is `s`. -* #### self type +* ### self type A _self type_ of a trait is the assumed type of `this`, the receiver, to be used within the trait. Any concrete class that mixes in the trait must ensure that its type conforms to the trait’s self type. The most common use of self types is for dividing a large class into several traits (as described in Chapter 29 of [Programming in Scala](https://www.artima.com/shop/programming_in_scala)). -* #### semi-structured data +* ### semi-structured data XML data is semi-structured. It is more structured than a flat binary file or text file, but it does not have the full structure of a programming language’s data structures. -* #### serialization -You can _serialize_ an object into a byte stream which can then be saved to files or transmitted over the network. You can later _deserialize_ the byte stream, even on different computer, and obtain an object that is the same as the original serialized object. +* ### serialization +You can _serialize_ an object into a byte stream which can then be saved to a file or transmitted over the network. You can later _deserialize_ the byte stream, even on different computer, and obtain an object that is the same as the original serialized object. -* #### shadow +* ### shadow A new declaration of a local variable _shadows_ one of the same name in an enclosing scope. -* #### signature +* ### signature _Signature_ is short for [type signature](#type-signature). -* #### singleton object +* ### singleton object An object defined with the object keyword. Each singleton object has one and only one instance. A singleton object that shares its name with a class, and is defined in the same source file as that class, is that class’s [companion object](#companion-object). The class is its [companion class](#companion-class). A singleton object that doesn’t have a companion class is a [standalone object](#standalone-object). -* #### standalone object +* ### standalone object A [singleton object](#singleton-object) that has no [companion class](#companion-class). -* #### statement +* ### statement An expression, definition, or import, _i.e._, things that can go into a template or a block in Scala source code. -* #### static type +* ### static type See [type](#type). -* #### structural type +* ### structural type A [refinement type](#refinement-type) where the refinements are for members not in the base type. For example, `{ def close(): Unit }` is a structural type, because the base type is `AnyRef`, and `AnyRef` does not have a member named `close`. -* #### subclass +* ### subclass A class is a _subclass_ of all of its [superclasses](#superclass) and [supertraits](#supertrait). -* #### subtrait +* ### subtrait A trait is a _subtrait_ of all of its [supertraits](#supertrait). -* #### subtype +* ### subtype The Scala compiler will allow any of a type’s _subtypes_ to be used as a substitute wherever that type is required. For classes and traits that take no type parameters, the subtype relationship mirrors the subclass relationship. For example, if class `Cat` is a subclass of abstract class `Animal`, and neither takes type parameters, type `Cat` is a subtype of type `Animal`. Likewise, if trait `Apple` is a subtrait of trait `Fruit`, and neither takes type parameters, type `Apple` is a subtype of type `Fruit`. For classes and traits that take type parameters, however, variance comes into play. For example, because abstract class `List` is declared to be covariant in its lone type parameter (i.e., `List` is declared `List[+A]`), `List[Cat]` is a subtype of `List[Animal]`, and `List[Apple]` a subtype of `List[Fruit]`. These subtype relationships exist even though the class of each of these types is `List`. By contrast, because `Set` is not declared to be covariant in its type parameter (i.e., `Set` is declared `Set[A]` with no plus sign), `Set[Cat]` is not a subtype of `Set[Animal]`. A subtype should correctly implement the contracts of its supertypes, so that the Liskov Substitution Principle applies, but the compiler only verifies this property at the level of type checking. -* #### superclass +* ### superclass A class’s _superclasses_ include its direct superclass, its direct superclass’s direct superclass, and so on, all the way up to `Any`. -* #### supertrait +* ### supertrait A class’s or trait’s _supertraits_, if any, include all traits directly mixed into the class or trait or any of its superclasses, plus any supertraits of those traits. -* #### supertype +* ### supertype A type is a _supertype_ of all of its subtypes. -* #### synthetic class +* ### synthetic class A synthetic class is generated automatically by the compiler rather than being written by hand by the programmer. -* #### tail recursive +* ### tail recursive A function is _tail recursive_ if the only place the function calls itself is the last operation of the function. -* #### target typing +* ### target typing _Target typing_ is a form of type inference that takes into account the type that’s expected. In `nums.filter((x) => x > 0)`, for example, the Scala compiler infers type of `x` to be the element type of `nums`, because the `filter` method invokes the function on each element of `nums`. -* #### template +* ### template A _template_ is the body of a class, trait, or singleton object definition. It defines the type signature, behavior and initial state of the class, trait, or object. -* #### trait +* ### trait A _trait_, which is defined with the `trait` keyword, is like an abstract class that cannot take any value parameters and can be “mixed into” classes or other traits via the process known as [mixin composition](#mixin-composition). When a trait is being mixed into a class or trait, it is called a [mixin](#mixin). A trait may be parameterized with one or more types. When parameterized with types, the trait constructs a type. For example, `Set` is a trait that takes a single type parameter, whereas `Set[Int]` is a type. Also, `Set` is said to be “the trait of” type `Set[Int]`. -* #### type +* ### type Every variable and expression in a Scala program has a _type_ that is known at compile time. A type restricts the possible values to which a variable can refer, or an expression can produce, at run time. A variable or expression’s type can also be referred to as a _static type_ if necessary to differentiate it from an object’s [runtime type](#runtime-type). In other words, “type” by itself means static type. Type is distinct from class because a class that takes type parameters can construct many types. For example, `List` is a class, but not a type. `List[T]` is a type with a free type parameter. `List[Int]` and `List[String]` are also types (called ground types because they have no free type parameters). A type can have a “[class](#class)” or “[trait](#trait).” For example, the class of type `List[Int]` is `List`. The trait of type `Set[String]` is `Set`. -* #### type constraint +* ### type constraint Some [annotations](#annotation) are _type constraints_, meaning that they add additional limits, or constraints, on what values the type includes. For example, `@positive` could be a type constraint on the type `Int`, limiting the type of 32-bit integers down to those that are positive. Type constraints are not checked by the standard Scala compiler, but must instead be checked by an extra tool or by a compiler plugin. -* #### type constructor +* ### type constructor A class or trait that takes type parameters. -* #### type parameter +* ### type parameter A parameter to a generic class or generic method that must be filled in by a type. For example, class `List` is defined as “`class List[T] { . . . `”, and method `identity`, a member of object `Predef`, is defined as “`def identity[T](x:T) = x`”. The `T` in both cases is a type parameter. -* #### type signature +* ### type signature A method’s _type signature_ comprises its name, the number, order, and types of its parameters, if any, and its result type. The type signature of a class, trait, or singleton object comprises its name, the type signatures of all of its members and constructors, and its declared inheritance and mixin relations. -* #### uniform access principle +* ### uniform access principle The _uniform access principle_ states that variables and parameterless functions should be accessed using the same syntax. Scala supports this principle by not allowing parentheses to be placed at call sites of parameterless functions. As a result, a parameterless function definition can be changed to a `val`, or _vice versa_, without affecting client code. -* #### unreachable +* ### unreachable At the Scala level, objects can become _unreachable_, at which point the memory they occupy may be reclaimed by the runtime. Unreachable does not necessarily mean unreferenced. Reference types (instances of `AnyRef`) are implemented as objects that reside on the JVM’s heap. When an instance of a reference type becomes unreachable, it indeed becomes unreferenced, and is available for garbage collection. Value types (instances of `AnyVal`) are implemented as both primitive type values and as instances of Java wrapper types (such as `java.lang.Integer`), which reside on the heap. Value type instances can be boxed (converted from a primitive value to a wrapper object) and unboxed (converted from a wrapper object to a primitive value) throughout the lifetime of the variables that refer to them. If a value type instance currently represented as a wrapper object on the JVM’s heap becomes unreachable, it indeed becomes unreferenced, and is available for garbage collection. But if a value type currently represented as a primitive value becomes unreachable, then it does not become unreferenced, because it does not exist as an object on the JVM’s heap at that point of time. The runtime may reclaim memory occupied by unreachable objects, but if an Int, for example, is implemented at run time by a primitive Java int that occupies some memory in the stack frame of an executing method, then the memory for that object is “reclaimed” when the stack frame is popped as the method completes. Memory for reference types, such as `Strings`, may be reclaimed by the JVM’s garbage collector after they become unreachable. -* #### unreferenced +* ### unreferenced See [unreachable](#unreachable). -* #### value +* ### value The result of any computation or expression in Scala is a _value_, and in Scala, every value is an object. The term value essentially means the image of an object in memory (on the JVM’s heap or stack). -* #### value type +* ### value type A _value type_ is any subclass of `AnyVal`, such as `Int`, `Double`, or `Unit`. This term has meaning at the level of Scala source code. At runtime, instances of value types that correspond to Java primitive types may be implemented in terms of primitive type values or instances of wrapper types, such as `java.lang.Integer`. Over the lifetime of a value type instance, the runtime may transform it back and forth between primitive and wrapper types (_i.e._, to box and unbox it). -* #### variable +* ### variable A named entity that refers to an object. A variable is either a `val` or a `var`. Both `val`s and `var`s must be initialized when defined, but only `var`s can be later reassigned to refer to a different object. -* #### variance +* ### variance A type parameter of a class or trait can be marked with a _variance_ annotation, either [covariant](#covariant) (+) or [contravariant](#contravariant) (-). Such variance annotations indicate how subtyping works for a generic class or trait. For example, the generic class `List` is covariant in its type parameter, and thus `List[String]` is a subtype of `List[Any]`. By default, _i.e._, absent a `+` or `-` annotation, type parameters are [nonvariant](#nonvariant). -* #### yield +* ### yield An expression can _yield_ a result. The `yield` keyword designates the result of a [for comprehension](#for-comprehension). diff --git a/_includes/_markdown/_ru/install-cask.md b/_includes/_markdown/_ru/install-cask.md new file mode 100644 index 0000000000..1cac104c20 --- /dev/null +++ b/_includes/_markdown/_ru/install-cask.md @@ -0,0 +1,45 @@ +{% altDetails require-info-box 'Установка Cask' %} + +{% tabs cask-install class=tabs-build-tool %} + +{% tab 'Scala CLI' %} + +Вы можете объявить зависимость от Cask с помощью следующей директивы `using`: + +```scala +//> using dep com.lihaoyi::cask::0.10.2 +``` + +{% endtab %} + +{% tab 'sbt' %} + +В файле `build.sbt` вы можете добавить зависимость от Cask: + +```scala +lazy val example = project.in(file("example")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "com.lihaoyi" %% "cask" % "0.10.2", + fork := true + ) +``` + +{% endtab %} + +{% tab 'Mill' %} + +В файле `build.sc` вы можете добавить зависимость от Cask: + +```scala +object example extends RootModule with ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = Agg( + ivy"com.lihaoyi::cask::0.10.2" + ) +} +``` +{% endtab %} + +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/_ru/install-munit.md b/_includes/_markdown/_ru/install-munit.md new file mode 100644 index 0000000000..aa15142558 --- /dev/null +++ b/_includes/_markdown/_ru/install-munit.md @@ -0,0 +1,68 @@ +{% altDetails install-info-box 'Установка MUnit' %} + +{% tabs munit-unit-test-1 class=tabs-build-tool %} +{% tab 'Scala CLI' %} + +Вы можете запросить весь набор инструментов одной командой: + +```scala +//> using toolkit latest +``` + +MUnit, будучи тестовым фреймворком, доступен только в тестовых файлах: +файлах в каталоге `test` или тех, которые имеют расширение `.test.scala`. +Подробнее о тестовой области (test scope) см. [в документации Scala CLI](https://scala-cli.virtuslab.org/docs/commands/test/). + +В качестве альтернативы вы можете запросить только определенную версию MUnit: + +```scala +//> using dep org.scalameta::munit:1.1.0 +``` + +{% endtab %} + +{% tab 'sbt' %} + +В файле `build.sbt` вы можете добавить зависимость от toolkit-test: + +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.7.0" % Test + ) +``` + +Здесь конфигурация `Test` означает, что зависимость используется только исходными файлами в `src/test`. + +В качестве альтернативы вы можете запросить только определенную версию MUnit: + +```scala +libraryDependencies += "org.scalameta" %% "munit" % "1.1.0" % Test +``` +{% endtab %} + +{% tab 'Mill' %} + +В файле `build.sc` вы можете добавить объект `test`, расширяющий `Tests` и `TestModule.Munit`: + +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + object test extends Tests with TestModule.Munit { + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit-test:0.7.0" + ) + } +} +``` + +В качестве альтернативы вы можете запросить только определенную версию MUnit: + +```scala +ivy"org.scalameta::munit:1.1.0" +``` +{% endtab %} +{% endtabs %} +{% endaltDetails %} \ No newline at end of file diff --git a/_includes/_markdown/_ru/install-os-lib.md b/_includes/_markdown/_ru/install-os-lib.md new file mode 100644 index 0000000000..f010d1f7fd --- /dev/null +++ b/_includes/_markdown/_ru/install-os-lib.md @@ -0,0 +1,64 @@ +{% altDetails require-info-box 'Установка OS-Lib' %} + +{% tabs oslib-install class=tabs-build-tool %} + +{% tab 'Scala CLI' %} + +Вы можете запросить весь набор инструментов одной командой: + +```scala +//> using toolkit latest +``` + +В качестве альтернативы вы можете запросить только определенную версию OS-Lib: + +```scala +//> using dep com.lihaoyi::os-lib:0.11.3 +``` + +{% endtab %} + +{% tab 'sbt' %} + +В файле `build.sbt` вы можете добавить зависимость от `toolkit`: + +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` + +В качестве альтернативы вы можете запросить только определенную версию OS-Lib: + +```scala +libraryDependencies += "com.lihaoyi" %% "os-lib" % "0.11.3" +``` + +{% endtab %} + +{% tab 'Mill' %} + +В файле `build.sc` вы можете добавить зависимость от `toolkit`: + +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` + +В качестве альтернативы вы можете запросить только определенную версию OS-Lib: + +```scala +ivy"com.lihaoyi::os-lib:0.11.3" +``` + +{% endtab %} + +{% endtabs %} +{% endaltDetails %} \ No newline at end of file diff --git a/_includes/_markdown/_ru/install-sttp.md b/_includes/_markdown/_ru/install-sttp.md new file mode 100644 index 0000000000..fec7938cea --- /dev/null +++ b/_includes/_markdown/_ru/install-sttp.md @@ -0,0 +1,64 @@ + +{% altDetails install-info-box 'Установка sttp' %} + +{% tabs sttp-install-methods class=tabs-build-tool%} + +{% tab 'Scala CLI' %} + +Вы можете запросить весь набор инструментов одной командой: + +```scala +//> using toolkit latest +``` + +В качестве альтернативы вы можете запросить только определенную версию sttp: + +```scala +//> using dep com.softwaremill.sttp.client4::core:4.0.0-RC1 +``` + +{% endtab %} + +{% tab 'sbt' %} + +В файле `build.sbt` вы можете добавить зависимость от `toolkit`: + +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` + +В качестве альтернативы вы можете запросить только определенную версию sttp: + +```scala +libraryDependencies += "com.softwaremill.sttp.client4" %% "core" % "4.0.0-RC1" +``` + +{% endtab %} + +{% tab 'Mill' %} + +В файле `build.sc` вы можете добавить зависимость от `toolkit`: + +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` + +В качестве альтернативы вы можете запросить только определенную версию sttp: + +```scala +ivy"com.softwaremill.sttp.client4::core:4.0.0-RC1" +``` + +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/_ru/install-upickle.md b/_includes/_markdown/_ru/install-upickle.md new file mode 100644 index 0000000000..83880a91a8 --- /dev/null +++ b/_includes/_markdown/_ru/install-upickle.md @@ -0,0 +1,64 @@ + +{% altDetails install-info-box 'Установка upickle' %} + +{% tabs upickle-install-methods class=tabs-build-tool %} + +{% tab 'Scala CLI' %} + +Вы можете запросить весь набор инструментов одной командой: + +```scala +//> using toolkit latest +``` + +В качестве альтернативы вы можете запросить только определенную версию UPickle: + +```scala +//> using dep com.lihaoyi::upickle:4.1.0 +``` + +{% endtab %} + +{% tab 'sbt' %} + +В файле `build.sbt` вы можете добавить зависимость от `toolkit`: + +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` + +В качестве альтернативы вы можете запросить только определенную версию UPickle: + +```scala +libraryDependencies += "com.lihaoyi" %% "upickle" % "4.1.0" +``` + +{% endtab %} + +{% tab 'Mill' %} + +В файле `build.sc` вы можете добавить зависимость от `toolkit`: + +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` + +В качестве альтернативы вы можете запросить только определенную версию UPickle: + +```scala +ivy"com.lihaoyi::upickle:4.1.0" +``` + +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/courses-coursera.md b/_includes/_markdown/courses-coursera.md new file mode 100644 index 0000000000..403c5e3100 --- /dev/null +++ b/_includes/_markdown/courses-coursera.md @@ -0,0 +1,18 @@ +## Scala Courses on Coursera by EPFL + +The [Scala Center](https://scala.epfl.ch) at EPFL offers free online courses of various levels, from beginner to advanced. + +For beginners: + +- [Effective Programming in Scala](https://www.coursera.org/learn/effective-scala): a practical introduction to Scala for professional developers +- [Functional Programming Principles in Scala](https://www.coursera.org/learn/scala-functional-programming): the foundational course by Martin Odersky, Scala's creator + +More advanced topics: + +- [Functional Program Design in Scala](https://www.coursera.org/learn/scala-functional-program-design): builds on functional principles with more advanced concepts +- [Parallel Programming](https://www.coursera.org/learn/scala-parallel-programming) +- [Big Data Analysis with Scala and Spark](https://www.coursera.org/learn/scala-spark-big-data) +- [Programming Reactive Systems](https://www.coursera.org/learn/scala-akka-reactive): introduces Akka, actors and reactive streams + +All courses are free to audit, with an option to pay for a certificate, to showcase your skills on your resume or LinkedIn. +For more on Scala Center's online courses, visit [this page](https://docs.scala-lang.org/online-courses.html#learning-platforms). diff --git a/_includes/_markdown/courses-extension-school.md b/_includes/_markdown/courses-extension-school.md new file mode 100644 index 0000000000..003c42a4f2 --- /dev/null +++ b/_includes/_markdown/courses-extension-school.md @@ -0,0 +1,9 @@ +## EPFL Extension School: Effective Programming in Scala + +Subscribing to [Effective programming in Scala](https://www.epfl.ch/education/continuing-education/effective-programming-in-scala/) on the EPFL Extension School offers: + +- Regular Q&A sessions and code reviews with experts from the Scala team +- An [Extension School certificate](https://www.epfl.ch/education/continuing-education/certifications/) upon completion + +This course combines video lessons, written content and hands-on exercise focused on practical aspects, including business domain modeling, error handling, data manipulation, and task parallelization. +For more on Scala Center's online courses, visit [this page](https://docs.scala-lang.org/online-courses.html#learning-platforms). diff --git a/_includes/_markdown/courses-rock-the-jvm.md b/_includes/_markdown/courses-rock-the-jvm.md new file mode 100644 index 0000000000..0b0db4f9f1 --- /dev/null +++ b/_includes/_markdown/courses-rock-the-jvm.md @@ -0,0 +1,17 @@ +## Rock the JVM Courses + +_As part of a partnership with the Scala Center, Rock the JVM donates 30% of the revenue from any courses purchased through the links in this section to support the Scala Center._ + +[Rock the JVM](https://rockthejvm.com?affcode=256201_r93i1xuv) is a learning platform with free and premium courses on the Scala language, and all major libraries and tools in the Scala ecosystem: Typelevel, Zio, Akka/Pekko, Spark, and others. +Its main Scala courses are: + +- [Scala at Light Speed](https://rockthejvm.com/courses/scala-at-light-speed?affcode=256201_r93i1xuv) (free) +- [Scala & Functional Programming Essentials](https://rockthejvm.com/courses/scala-essentials?affcode=256201_r93i1xuv) (premium) +- [Advanced Scala and Functional Programming](https://rockthejvm.com/courses/advanced-scala?affcode=256201_r93i1xuv) (premium) +- [Scala Macros & Metaprogramming](https://rockthejvm.com/courses/scala-macros-and-metaprogramming?affcode=256201_r93i1xuv) (premium) + +Other courses teach how to build full-stack Scala applications, using [Typelevel](https://rockthejvm.com/courses/typelevel-rite-of-passage?affcode=256201_r93i1xuv) or [ZIO](https://rockthejvm.com/courses/zio-rite-of-passage?affcode=256201_r93i1xuv) ecosystems. + + + +Explore more premium [courses](https://rockthejvm.com/courses?affcode=256201_r93i1xuv) or check out [free video tutorials](https://youtube.com/rockthejvm?affcode=256201_r93i1xuv) and [free articles](https://rockthejvm.com/articles?affcode=256201_r93i1xuv). diff --git a/_includes/_markdown/install-cask.md b/_includes/_markdown/install-cask.md new file mode 100644 index 0000000000..3637ddfac9 --- /dev/null +++ b/_includes/_markdown/install-cask.md @@ -0,0 +1,37 @@ +{% altDetails require-info-box 'Getting Cask' %} + +{% tabs cask-install class=tabs-build-tool %} + +{% tab 'Scala CLI' %} +You can declare a dependency on Cask with the following `using` directive: +```scala +//> using dep com.lihaoyi::cask::0.10.2 +``` +{% endtab %} + +{% tab 'sbt' %} +In your `build.sbt`, you can add a dependency on Cask: +```scala +lazy val example = project.in(file("example")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "com.lihaoyi" %% "cask" % "0.10.2", + fork := true + ) +``` +{% endtab %} + +{% tab 'Mill' %} +In your `build.sc`, you can add a dependency on Cask: +```scala +object example extends RootModule with ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = Agg( + ivy"com.lihaoyi::cask::0.10.2" + ) +} +``` +{% endtab %} + +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/install-munit.md b/_includes/_markdown/install-munit.md new file mode 100644 index 0000000000..47eeb1509f --- /dev/null +++ b/_includes/_markdown/install-munit.md @@ -0,0 +1,53 @@ +{% altDetails install-info-box 'Getting MUnit' %} + +{% tabs munit-unit-test-1 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +You can require the entire toolkit in a single line: +```scala +//> using toolkit latest +``` +MUnit, being a testing framework, is only available in test files: files in a `test` directory or ones that have the `.test.scala` extension. Refer to the [Scala CLI documentation](https://scala-cli.virtuslab.org/docs/commands/test/) to learn more about the test scope. + +Alternatively, you can require just a specific version of MUnit: +```scala +//> using dep org.scalameta::munit:1.1.0 +``` +{% endtab %} +{% tab 'sbt' %} +In your build.sbt file, you can add the dependency on toolkit-test: +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.7.0" % Test + ) +``` + +Here the `Test` configuration means that the dependency is only used by the source files in `src/test`. + +Alternatively, you can require just a specific version of MUnit: +```scala +libraryDependencies += "org.scalameta" %% "munit" % "1.1.0" % Test +``` +{% endtab %} +{% tab 'Mill' %} +In your build.sc file, you can add a `test` object extending `Tests` and `TestModule.Munit`: +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + object test extends Tests with TestModule.Munit { + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit-test:0.7.0" + ) + } +} +``` + +Alternatively, you can require just a specific version of MUnit: +```scala +ivy"org.scalameta::munit:1.1.0" +``` +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/install-os-lib.md b/_includes/_markdown/install-os-lib.md new file mode 100644 index 0000000000..ae254d9d71 --- /dev/null +++ b/_includes/_markdown/install-os-lib.md @@ -0,0 +1,46 @@ +{% altDetails require-info-box 'Getting OS-Lib' %} + +{% tabs oslib-install class=tabs-build-tool %} +{% tab 'Scala CLI' %} +You can require the entire toolkit in a single line: +```scala +//> using toolkit latest +``` + +Alternatively, you can require just a specific version of OS-Lib: +```scala +//> using dep com.lihaoyi::os-lib:0.11.3 +``` +{% endtab %} +{% tab 'sbt' %} +In your `build.sbt`, you can add a dependency on the toolkit: +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` +Alternatively, you can require just a specific version of OS-Lib: +```scala +libraryDependencies += "com.lihaoyi" %% "os-lib" % "0.11.3" +``` +{% endtab %} +{% tab 'Mill' %} +In your `build.sc` file, you can add a dependency on the Toolkit: +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` +Alternatively, you can require just a specific version of OS-Lib: +```scala +ivy"com.lihaoyi::os-lib:0.11.3" +``` +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/install-sttp.md b/_includes/_markdown/install-sttp.md new file mode 100644 index 0000000000..0173ec47e1 --- /dev/null +++ b/_includes/_markdown/install-sttp.md @@ -0,0 +1,47 @@ +{% altDetails install-info-box 'Getting sttp' %} + +{% tabs sttp-install-methods class=tabs-build-tool%} +{% tab 'Scala CLI' %} +You can require the entire toolkit in a single line: +```scala +//> using toolkit latest +``` + +Alternatively, you can require just a specific version of sttp: +```scala +//> using dep com.softwaremill.sttp.client4::core:4.0.0-RC1 +``` +{% endtab %} +{% tab 'sbt' %} +In your build.sbt file, you can add a dependency on the Toolkit: +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` + +Alternatively, you can require just a specific version of sttp: +```scala +libraryDependencies += "com.softwaremill.sttp.client4" %% "core" % "4.0.0-RC1" +``` +{% endtab %} +{% tab 'Mill' %} +In your build.sc file, you can add a dependency on the Toolkit: +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` +Alternatively, you can require just a specific version of sttp: +```scala +ivy"com.softwaremill.sttp.client4::core:4.0.0-RC1" +``` +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/_markdown/install-upickle.md b/_includes/_markdown/install-upickle.md new file mode 100644 index 0000000000..9f9cff8a62 --- /dev/null +++ b/_includes/_markdown/install-upickle.md @@ -0,0 +1,46 @@ +{% altDetails install-info-box 'Getting upickle' %} + +{% tabs upickle-install-methods class=tabs-build-tool %} +{% tab 'Scala CLI' %} +Using Scala CLI, you can require the entire toolkit in a single line: +```scala +//> using toolkit latest +``` + +Alternatively, you can require just a specific version of UPickle: +```scala +//> using dep com.lihaoyi::upickle:4.1.0 +``` +{% endtab %} +{% tab 'sbt' %} +In your build.sbt file, you can add the dependency on the Toolkit: +```scala +lazy val example = project.in(file(".")) + .settings( + scalaVersion := "3.4.2", + libraryDependencies += "org.scala-lang" %% "toolkit" % "0.7.0" + ) +``` +Alternatively, you can require just a specific version of UPickle: +```scala +libraryDependencies += "com.lihaoyi" %% "upickle" % "4.1.0" +``` +{% endtab %} +{% tab 'Mill' %} +In your build.sc file, you can add the dependency to the upickle library: +```scala +object example extends ScalaModule { + def scalaVersion = "3.4.2" + def ivyDeps = + Agg( + ivy"org.scala-lang::toolkit:0.7.0" + ) +} +``` +Alternatively, you can require just a specific version of UPickle: +```scala +ivy"com.lihaoyi::upickle:4.1.0" +``` +{% endtab %} +{% endtabs %} +{% endaltDetails %} diff --git a/_includes/alert-banner.html b/_includes/alert-banner.html new file mode 100644 index 0000000000..94c5ac1273 --- /dev/null +++ b/_includes/alert-banner.html @@ -0,0 +1,10 @@ +{% comment %}use the variable 'message' to include markdown text to display in the alert.{% endcomment %} + +{% unless include.message_id == 'disabled' %} + +{% endunless %} diff --git a/_includes/code-snippet.html b/_includes/code-snippet.html new file mode 100644 index 0000000000..3af87d0f97 --- /dev/null +++ b/_includes/code-snippet.html @@ -0,0 +1,8 @@ +
+ {% unless include.nocopy %} +
+ +
+ {% endunless %} +
{{include.codeSnippet}}
+
diff --git a/_includes/column-list-of-items.html b/_includes/column-list-of-items.html deleted file mode 100644 index 2ce303e678..0000000000 --- a/_includes/column-list-of-items.html +++ /dev/null @@ -1,18 +0,0 @@ -{% comment %} - Layouts using this include should pass an include variable called 'collection' referencing a collection carrying the data (i.e.: contribute_community_tickets, contribute_resources...) -{% endcomment %} -
- {% for item in include.collection %} -
- -
- {{item.content}} -
-
- {% endfor %} -
diff --git a/_includes/documentation-sections.html b/_includes/documentation-sections.html index 4ca75f459d..cac3c2d21b 100644 --- a/_includes/documentation-sections.html +++ b/_includes/documentation-sections.html @@ -3,7 +3,7 @@

{{ section.title }}

{% for link in section.links %} - + -
  - {% if section.more-resources %} - {{ page.more-resources-label }}: - - {% endif %} -
{% endfor %}
diff --git a/_includes/footer.html b/_includes/footer.html index a9e18083ff..82d0ba252d 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -39,9 +39,9 @@ - - - + + + @@ -53,6 +53,9 @@ + + + @@ -106,7 +109,7 @@ apiKey: 'fbc439670f5d4e3730cdcb715c359391', indexName: 'scala-lang', inputSelector: '#doc-search-bar', - algoliaOptions: { 'facetFilters': ["language:en"] }, + algoliaOptions: { 'facetFilters': ["language:en"], 'hitsPerPage': 6, 'distinct': 1}, debug: false // Set debug to true if you want to inspect the dropdown }); } diff --git a/_includes/getting-started.md b/_includes/getting-started.md deleted file mode 100644 index 83c1de45fe..0000000000 --- a/_includes/getting-started.md +++ /dev/null @@ -1,193 +0,0 @@ -The instructions below cover both Scala 2 and Scala 3. - -## Try Scala without installing anything - -To start experimenting with Scala right away, use “Scastie” in your browser. -_Scastie_ is an online “playground” where you can experiment with Scala examples to see how things work, with access to all Scala compilers and published libraries. - -> Scastie supports both Scala 2 and Scala 3, but it defaults -> to Scala 3. If you are looking for a Scala 2 snippet to play with, -> [click here](https://scastie.scala-lang.org/MHc7C9iiTbGfeSAvg8CKAA). - -## Install Scala on your computer - -Installing Scala means installing various command-line tools such as the Scala compiler and build tools. -We recommend using the Scala installer tool "Coursier" that automatically installs all the requirements, but you can still manually install each tool. - -### Using the Scala Installer (recommended way) - -The Scala installer is a tool named [Coursier](https://get-coursier.io/docs/cli-overview), whose main command is named `cs`. -It ensures that a JVM and standard Scala tools are installed on your system. -Install it on your system with the following instructions. - -
-
-

Follow the instructions to install the cs launcher then run:

-

$ ./cs setup

-
-
- - -Along with managing JVMs, `cs setup` also installs useful command-line tools: - -- A JDK (if you don't have one already) -- The [sbt](https://www.scala-sbt.org/) build tool -- [Ammonite](https://ammonite.io/), an enhanced REPL -- [scalafmt](https://scalameta.org/scalafmt/), the Scala code formatter -- `scalac` (the Scala compiler) -- `scala` (the Scala REPL and script runner). - -For more information about `cs`, read -[coursier-cli documentation](https://get-coursier.io/docs/cli-overview). - -> `cs setup` installs the Scala 3 compiler and runner by default (the `scalac` and -> `scala` commands, respectively). Whether you intend to use Scala 2 or 3, -> this is usually not an issue because most projects use a build tool that will -> use the correct version of Scala irrespective of the one installed "globally". -> Nevertheless, you can always launch a specific version of Scala using -> ``` -> $ cs launch scala:{{ site.scala-version }} -> $ cs launch scalac:{{ site.scala-version }} -> ``` -> If you prefer Scala 2 to be run by default, you can force that version to be installed with: -> ``` -> $ cs install scala:{{ site.scala-version }} scalac:{{ site.scala-version }} -> ``` - -### ...or manually - -You only need two tools to compile, run, test, and package a Scala project: Java 8 or 11, -and sbt. -To install them manually: - -1. if you don't have Java 8 or 11 installed, download - Java from [Oracle Java 8](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html), [Oracle Java 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), - or [AdoptOpenJDK 8/11](https://adoptopenjdk.net/). Refer to [JDK Compatibility](/overviews/jdk-compatibility/overview.html) for Scala/Java compatibility detail. -1. Install [sbt](https://www.scala-sbt.org/download.html) - -## Create a "Hello World" project with sbt - -Once you have installed sbt, you are ready to create a Scala project, which -is explained in the following sections. - -To create a project, you can either use the command line or an IDE. -If you are familiar with the command line, we recommend that approach. - -### Using the command line - -sbt is a build tool for Scala. sbt compiles, runs, -and tests your Scala code. (It can also publish libraries and do many other tasks.) - -To create a new Scala project with sbt: - -1. `cd` to an empty folder. -1. Run the command `sbt new scala/scala3.g8` to create a Scala 3 project, or `sbt new scala/hello-world.g8` to create a Scala 2 project. - This pulls a project 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 for its own files) - - build.properties - - build.sbt (sbt's build definition file) - - src - - main - - scala (all of your Scala code goes here) - - Main.scala (Entry point of program) <-- this is all we need for now -``` - -More documentation about sbt can be found in the [Scala Book](/scala3/book/tools-sbt.html) (see [here](/overviews/scala-book/scala-build-tool-sbt.html) for the Scala 2 version) -and in the official sbt [documentation](https://www.scala-sbt.org/1.x/docs/index.html) - -### With an IDE - -You can skip the rest of this page and go directly to [Building a Scala Project with IntelliJ and sbt](/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.html) - - -## Open hello-world project - -Let's use an IDE to open the project. The most popular ones are IntelliJ and VSCode. -They both offer rich IDE features, but you can still use [many other editors.](https://scalameta.org/metals/docs/editors/overview.html) - -### Using IntelliJ - -1. Download and install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) -1. Install the Scala plugin by following [the instructions on how to install IntelliJ plugins](https://www.jetbrains.com/help/idea/managing-plugins.html) -1. Open the `build.sbt` file then choose *Open as a project* - -### Using VSCode with metals - -1. Download [VSCode](https://code.visualstudio.com/Download) -1. Install the Metals extension from [the Marketplace](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) -1. Next, open the directory containing a `build.sbt` file (this should be the directory `hello-world` if you followed the previous instructions). When prompted to do so, select *Import build*. - ->[Metals](https://scalameta.org/metals) is a “Scala language server” that provides support for writing Scala code in VS Code and other editors like [Atom, Sublime Text, and more](https://scalameta.org/metals/docs/editors/overview.html), using the Language Server Protocol. -> -> Under the hood, Metals communicates with the build tool by using -> the [Build Server Protocol (BSP)](https://build-server-protocol.github.io/). For details on how Metals works, see, [“Write Scala in VS Code, Vim, Emacs, Atom and Sublime Text with Metals”](https://www.scala-lang.org/2019/04/16/metals.html). - -### Play with the source code - -View these two files in your IDE: - -- _build.sbt_ -- _src/main/scala/Main.scala_ - -When you run your project in the next step, the configuration in _build.sbt_ will be used to run the code in _src/main/scala/Main.scala_. - -## Run Hello World - -If you’re comfortable using your IDE, you can run the code in _Main.scala_ from your IDE. - -Otherwise, you can run the application from a terminal with these steps: - -1. `cd` into `hello-world`. -1. Run `sbt`. This opens 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. - -When you’re finished experimenting with this project, press `[Enter]` to interrupt the `run` command. -Then type `exit` or press `[Ctrl+D]` to exit sbt and return to your command line prompt. - -## Next Steps - -Once you've finished the above tutorials, consider checking out: - -* [The Scala Book](/scala3/book/introduction.html) (see the Scala 2 version [here](/overviews/scala-book/introduction.html)), which provides a set of short lessons introducing Scala’s main features. -* [The Tour of Scala](/tour/tour-of-scala.html) for bite-sized introductions to Scala's features. -* [Learning Resources](/learn.html), which includes online interactive tutorials and courses. -* [Our list of some popular Scala books](/books.html). -* [The migration guide](/scala3/guides/migration/compatibility-intro.html) helps you to migrate your existing Scala 2 code base to Scala 3. - -## Getting Help -There are a multitude of mailing lists and real-time chat rooms in case you want to quickly connect with other Scala users. Check out our [community](https://scala-lang.org/community/) page for a list of these resources, and for where to reach out for help. - - - - - - - - - diff --git a/_includes/headertop.html b/_includes/headertop.html index 1c68eeed39..19440aff66 100644 --- a/_includes/headertop.html +++ b/_includes/headertop.html @@ -1,8 +1,15 @@ -{% if page.scala3 %} - +{% if page.scala3 or page.new-version %} + {% assign classes='' %} + {% if page.scala3 %} + {% assign classes= classes | append: ' scala3' %} + {% endif %} + {% if page.new-version %} + {% assign classes= classes | append: ' outdated-page' %} + {% endif %} + {% else %} - + {% endif %} @@ -39,7 +46,7 @@ <meta name="msapplication-TileColor" content="#15a9ce"> <meta name="theme-color" content="#ffffff"> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <!-- Custom stylesheet --> <link href="{{ site.baseurl }}/resources/css/unslider-dots.css" rel="stylesheet" type="text/css"> @@ -49,8 +56,7 @@ <link rel="stylesheet" href="{{ site.baseurl }}/resources/css/monospace.css" type="text/css" /> <!-- Atom feeds --> - <link rel="alternate" type="application/atom+xml" title="News Feed" href="http://scala-lang.org/feed/index.xml" /> - <link rel="alternate" type="application/atom+xml" title="Blog Feed" href="http://scala-lang.org/feed/blog.xml" /> + <link rel="alternate" type="application/atom+xml" title="News Feed" href="https://scala-lang.org/feed/index.xml" /> <!-- Algolia stylesheet --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" /> diff --git a/_includes/inner-documentation-sections.html b/_includes/inner-documentation-sections.html new file mode 100644 index 0000000000..944845c156 --- /dev/null +++ b/_includes/inner-documentation-sections.html @@ -0,0 +1,29 @@ +{% comment %} + Layouts using this include should pass an include variable called 'links' referencing a list carrying the data +{% endcomment %} + +<div class="documentation"> + {% for link in include.links %} + + <!-- check if link starts with '#' --> + {% assign first_char = link.link | slice: 0 %} + {% if first_char == '#' %} + {% assign is_url = false %} + {% else %} + {% assign is_url = true %} + {% endif %} + + <a href="{% if link.link contains '://' %}{{link.link}}{% else %}{{site.baseurl}}{{link.link}}{% endif %}" + class="doc-item doc-item-link" + {% if is_url %}target="_blank" rel="noopener noreferrer"{% endif %} + > + <div class="doc-item-header"> + <i class="{{link.icon}}"></i> + <h4>{{link.title}}</h4> + </div> + <div class="doc-item-main"> + <p>{{link.description}}</p> + </div> + </a> + {% endfor %} +</div> diff --git a/_includes/inner-page-main-content.html b/_includes/inner-page-main-content.html deleted file mode 100644 index ba5f15e916..0000000000 --- a/_includes/inner-page-main-content.html +++ /dev/null @@ -1,16 +0,0 @@ -<section class="content"> - <div class="wrap"> - <div class="content-primary"> - <div class="inner-box"> - <div class="toc-context"> - {{content}} - </div> - - {% include contributors-list.html %} - </div> - </div> - - <!-- TOC --> - {% include sidebar-toc.html %} - </div> -</section> diff --git a/_includes/markdown.html b/_includes/markdown.html new file mode 100644 index 0000000000..886b1560cd --- /dev/null +++ b/_includes/markdown.html @@ -0,0 +1 @@ +{% capture markdown %}{% include {{include.path}} %}{% endcapture %}{{ markdown | markdownify }} diff --git a/_includes/navbar-inner.html b/_includes/navbar-inner.html index 171d00924e..43de59b7e4 100644 --- a/_includes/navbar-inner.html +++ b/_includes/navbar-inner.html @@ -1,8 +1,4 @@ -{% if page.scala3 %} - {% assign navdata = site.data.scala3-doc-nav-header %} -{% else %} - {% assign navdata = site.data.doc-nav-header %} -{% endif %} +{% assign navdata = site.data.doc-nav-header %} {% include site-header.html %} @@ -18,9 +14,6 @@ <a href="{{ site.baseurl }}/{{ docsRootTranslated.language }}"> <img src="{{ site.baseurl }}/resources/img/documentation-logo@2x.png" alt="docs"> </a> - <span class="doc-language-version"> - — Scala {% if page.scala3 %} 3 {% else %} 2 {% endif %} - </span> </div> <div class="navigation-ellipsis"> <i class="fa fa-ellipsis-v"></i> @@ -30,7 +23,7 @@ <li class="navigation-menu-item"> {% capture translatedPageId %}/{{page.language}}{{navItem.url | remove_first: '.html' }}{% endcapture %} {% assign navItemTranslated = site.documents | where: 'id', translatedPageId | first %} - <a href="{% if navItemTranslated.url %}{{navItemTranslated.url}}{% elsif navItem.url contains '://' or navItem.url == '#' %}{{navItem.url}}{% else %}{{site.baseurl}}{{navItem.url}}{% endif %}" id="{{ navItem.title | downcase | strip }}" {% if page.url contains navItem.url %}class="active"{% endif %}>{{navItem.title}}</a> + <a href="{% if navItemTranslated.url %}{{navItemTranslated.url}}{% elsif navItem.url contains '://' or navItem.url == '#' %}{{navItem.url}}{% else %}{{site.baseurl}}{{navItem.url}}{% endif %}" id="{{ navItem.title | downcase | strip | remove: ' ' }}" {% if page.url contains navItem.url %}class="active"{% endif %}>{{navItem.title}}</a> {% if navItem.submenu %} <ul class="navigation-dropdown"> {% for subItem in navItem.submenu %} @@ -49,7 +42,7 @@ <nav class="doc-navigation-submenus"> {% for navItem in navdata %} {% if navItem.submenu %} - <ul class="navigation-submenu" id="{{ navItem.title | downcase | strip }}" style="display: none;"> + <ul class="navigation-submenu" id="{{ navItem.title | downcase | strip | remove: ' ' }}" style="display: none;"> {% for subItem in navItem.submenu %} <li> <a href="{% if subItem.url contains '://' %}{{subItem.url}}{% else %}{{site.baseurl}}{{subItem.url}}{% endif %}">{{ subItem.title }}</a> diff --git a/_includes/online-courses-box.html b/_includes/online-courses-box.html new file mode 100644 index 0000000000..b7cf299928 --- /dev/null +++ b/_includes/online-courses-box.html @@ -0,0 +1,12 @@ +<div class="inner-box"> + <div class="online-courses-wrapper"> + <div class="online-courses-image"> + <a href="{{ include.link }}" > + <img src="{{ site.baseurl }}/resources/images/online-courses/{{ include.image }}" alt="{{ include.image }}"> + </a> + </div> + <div class="online-courses-content"> + {% include markdown.html path=include.path %} + </div> + </div> +</div> diff --git a/_includes/online-courses.html b/_includes/online-courses.html deleted file mode 100644 index 73fba6a50c..0000000000 --- a/_includes/online-courses.html +++ /dev/null @@ -1,91 +0,0 @@ -<!-- Online --> -<div class="online-courses"> - <div class="heading-line"> - <h2><span>Online Courses</span></h2> - </div> - - {% 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 %} - - <div class="course-items-list"> - {% for course in courses %} - <a href="{{course.url}}" class="course-item card"> - <img src= - {% assign platform = course.platform | downcase %} - {% case platform %} - {% when "coursera" %} - "{{ site.baseurl }}/resources/img/frontpage/coursera-icon.png" - {% when "edx" %} - "{{ site.baseurl }}/resources/img/frontpage/edx-icon.png" - {% endcase %} - alt=""> - <div class="card-text"> - <h4>{{course.title}}</h4> - <ul> - <li class="online-courses-price"> - {% if course.paid == true %} - {{site.data.common.texts.onlineCoursesPaid}} - {% else %} - {{site.data.common.texts.onlineCoursesFree}} - {% endif %} - </li> - <li class="dot">•</li> - <li class="online-courses-date"> - {% if course.showDate == true %} - {{course.date | date_to_string}} - {% else %} - {{site.data.common.texts.courseraLaunchPeriod}} - {% endif %} - </li> - </ul> - </div> - </a> - {% endfor %} - </div> - <div class="call-to-action action-medium"> - <p class="align-top">Visit all the <a href="{{site.data.common.courseraMoocsUrl}}">Online Courses</a> courses</p> - </div> -</div> \ No newline at end of file diff --git a/_includes/outdated-notice.html b/_includes/outdated-notice.html new file mode 100644 index 0000000000..6248ee33ef --- /dev/null +++ b/_includes/outdated-notice.html @@ -0,0 +1,7 @@ +<div class="inline-sticky-top inline-sticky-top-higher"> + <div class="wip-notice"> + <h4>Outdated Notice</h4> + <p><a class="new-version-notice" href="{{ include.new-version }}"><i class="fa fa-info"></i><span>   + This page has a new version.</span></a></p> + </div> +</div> diff --git a/_includes/sidebar-toc-multipage-overview.html b/_includes/sidebar-toc-multipage-overview.html index 168f4b5950..4f9d7cb75c 100644 --- a/_includes/sidebar-toc-multipage-overview.html +++ b/_includes/sidebar-toc-multipage-overview.html @@ -1,30 +1,31 @@ +{% assign pagetype = page.type %} <div class="content-nav"> - <div class="inner-box sidebar-toc-wrapper" style=""> - <h5 class="contents">Contents</h5> - {% assign type = page.type %} - {% if type %} - <div class="inner-toc book" id="sidebar-toc"> - {% else %} - <div class="inner-toc" id="sidebar-toc"> - {% endif %} - + <div class="inner-box sidebar-toc-wrapper" style=""> + <h5 class="contents">Contents</h5> + {% if pagetype %} + <div class="inner-toc book" id="sidebar-toc"> + {% else %} + <div class="inner-toc" id="sidebar-toc"> + {% endif %} <ul> {% assign sorted = site[page.collection] | sort: 'num' %} {% for pg in sorted %} - {% if pg.num == page.num %} - {% capture toc %} - <div id="toc"></div> - {% endcapture %} - {% endif %} + {% capture toc %} + {% if pg.num == page.num %} + <div id="toc"></div> + {% endif %} + {% endcapture %} {% if pg.num and (page.partof == pg.partof) %} {% if page.language %} <!-- if page is a translation, get the translated title --> - {% assign prefix = page.language | prepend: '/' %} - {% assign localizedId = pg.id | prepend: prefix %} + {% assign localizedId = pg.id %} {% for lpg in site.[page.language] %} {% if lpg.id == localizedId %} - <li><a {% if page.title == lpg.title %}class="active"{% endif %} href="/{{ site.baseurl }}{{ page.language }}{{ pg.url }}">{{ lpg.title }}</a> - {{ toc }}</li> + {% if pg.type %} <!-- if a type is set in a document, we add it as a class. Used in Scala book to diff between chapter and section --> + <li class="type-{{ pg.type }}"><a {% if page.num == pg.num %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ lpg.title }}</a>{{ toc }}</li> + {% else %} + <li><a {% if page.num == pg.num %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ lpg.title }}</a>{{ toc }}</li> + {% endif %} {% endif %} {% endfor %} {% else %} <!-- this must be English, so get the other documents' titles --> @@ -39,19 +40,24 @@ <h5 class="contents">Contents</h5> {% endfor %} </ul> + {% assign orphanTranslation = page.orphanTranslation | default: false %} {% if page.languages %} <ul id="available-languages" style="display: none;"> <li><a href="{{ site.baseurl }}{{ page.url }}">English</a></li> {% for l in page.languages %} {% capture intermediate %}{{ page.url }}{% endcapture %} {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% assign lang = site.data.languages[l] %} - <li><a href="{{ site.baseurl }}/{{ l }}/{{ rootTutorialURL }}" class="lang">{{ lang.name }}</a></li> + {% assign lang = site.data.languages[l] %} + <li><a href="{{ site.baseurl }}/{{ l }}/{{ rootTutorialURL }}" class="lang">{{ lang.name }}</a></li> {% endfor %} </ul> - {% elsif page.language %} - {% assign engPath = page.id | remove_first: "/" | remove_first: page.language | append: '.html' %} - {% assign engPg = site.overviews | where: 'partof', page.partof | first %} + {% elsif page.language and orphanTranslation == false %} + {% comment %} + <!-- i.e. some pages like '/zh-cn/thanks.html' have no english version --> + {% endcomment %} + {% assign engId = page.id | remove_first: "/" | remove_first: page.language %} + {% assign engPath = engId | append: '.html' %} + {% assign engPg = site.overviews | where: 'partof', page.partof | find: 'id', engId %} <ul id="available-languages" style="display: none;"> <li><a href="{{ site.baseurl }}{{ engPath }}">English</a></li> {% for l in engPg.languages %} @@ -60,8 +66,8 @@ <h5 class="contents">Contents</h5> {% endfor %} </ul> {% endif %} - </div> - <hr> - <div class="help-us"><a href="https://github.com/scala/docs.scala-lang/blob/main/{% if page.collection %}{{ page.relative_path }}{% else %}{{ page.path }}{% endif %}"><i class="fa fa-pencil" aria-hidden="true"></i> Problem with this page?<br>     Please help us fix it!</a></div> - </div> + </div> + <hr> + <div class="help-us"><a href="https://github.com/scala/docs.scala-lang/blob/main/{% if page.collection %}{{ page.relative_path }}{% else %}{{ page.path }}{% endif %}"><i class="fa fa-pencil" aria-hidden="true"></i> Problem with this page?<br>     Please help us fix it!</a></div> + </div> </div> diff --git a/_includes/sidebar-toc-singlepage-overview.html b/_includes/sidebar-toc-singlepage-overview.html index 7b2e68e995..cead129be1 100644 --- a/_includes/sidebar-toc-singlepage-overview.html +++ b/_includes/sidebar-toc-singlepage-overview.html @@ -3,19 +3,21 @@ <h5 class="contents">Contents</h5> <div class="inner-toc" id="sidebar-toc"> <div id="toc"></div> + {% assign orphanTranslation = page.orphanTranslation | default: false %} {% if page.languages %} <ul id="available-languages" style="display: none;"> <li><a href="{{ site.baseurl }}{{ page.url }}">English</a></li> {% for l in page.languages %} {% capture intermediate %}{{ page.url }}{% endcapture %} {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% assign lang = site.data.languages[l] %} - <li><a href="{{ site.baseurl }}/{{ l }}/{{ rootTutorialURL }}" class="lang">{{ lang.name }}</a></li> + {% assign lang = site.data.languages[l] %} + <li><a href="{{ site.baseurl }}/{{ l }}/{{ rootTutorialURL }}" class="lang">{{ lang.name }}</a></li> {% endfor %} - </ul> - {% elsif page.language %} - {% assign engPath = page.id | remove_first: "/" | remove_first: page.language | append: '.html' %} - {% assign engPg = site.documents | where: 'partof', page.partof | first %} + </ul> + {% elsif page.language and orphanTranslation == false %} + <!-- i.e. some pages like '/zh-cn/thanks.html' have no english version --> + {% assign engPath = page.id | remove_first: "/" | remove_first: page.language | append: '.html' %} + {% assign engPg = site.overviews | where: 'partof', page.partof | first %} <ul id="available-languages" style="display: none;"> <li><a href="{{ site.baseurl }}{{ engPath }}">English</a></li> {% for l in engPg.languages %} diff --git a/_includes/version-specific-notice.html b/_includes/version-specific-notice.html new file mode 100644 index 0000000000..4a92f84a6d --- /dev/null +++ b/_includes/version-specific-notice.html @@ -0,0 +1,31 @@ +{% if include.language %} +<blockquote class="help-info"> + <i class="fa fa-info"></i><span style="margin-left: 0.5rem"> + {% if include.language == 'scala3' %} + {% if include.page-language == 'ru' %} + Эта страница документа относится к Scala 3 и + может охватывать новые концепции, недоступные в Scala 2. + Если не указано явно, все примеры кода на этой странице + предполагают, что вы используете Scala 3. + {% else %} + This doc page is specific to Scala 3, + and may cover new concepts not available in Scala 2. Unless + otherwise stated, all the code examples in this page assume + you are using Scala 3. + {% endif %} + {% else if include.language == 'scala2' %} + {% if include.page-language == 'ru' %} + Эта страница документа относится к функциям, представленным в Scala 2, + которые либо были удалены в Scala 3, либо заменены альтернативными. + Если не указано явно, все примеры кода на этой странице предполагают, + что вы используете Scala 2. + {% else %} + This doc page is specific to features shipped in Scala 2, + which have either been removed in Scala 3 or replaced by an + alternative. Unless otherwise stated, all the code examples + in this page assume you are using Scala 2. + {% endif %} + {% endif %} + </span> +</blockquote> +{% endif %} diff --git a/_it/tutorials/scala-for-java-programmers.md b/_it/tutorials/scala-for-java-programmers.md index 7a60a2d6c6..180e5795bb 100644 --- a/_it/tutorials/scala-for-java-programmers.md +++ b/_it/tutorials/scala-for-java-programmers.md @@ -26,7 +26,7 @@ Scala senza richiedere troppe conoscenze del linguaggio stesso. Ecco come appeare il codice: object HelloWorld { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { println("Hello, world!") } } @@ -105,7 +105,7 @@ semplicemente importare le classi dei corrispondenti package Java: import java.text.DateFormat._ object FrenchDate { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format now) @@ -204,7 +204,7 @@ frase “time flies like an arrow” ogni secondo. def timeFlies() { println("time flies like an arrow...") } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } } @@ -228,7 +228,7 @@ invece di *timeFlies* e appare come di seguito: def oncePerSecond(callback: () => Unit) { while (true) { callback(); Thread sleep 1000 } } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(() => println("time flies like an arrow...")) } @@ -262,7 +262,7 @@ 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. +Da notare che il tipo di ritorno dei due metodi non è specificato esplicitamente. 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`. @@ -283,7 +283,7 @@ 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]) { + def main(args: Array[String]): Unit = { val c = new Complex(1.2, 3.4) println("imaginary part: " + c.im()) } @@ -419,7 +419,7 @@ detto in Scala non è difficile: } Questa funzione di valutazione lavora effettuando un *pattern matching* -sull’albero `t`. Intuitivamente il significato della definizione precendente +sull’albero `t`. Intuitivamente il significato della definizione precedente dovrebbe esser chiaro: 1. prima controlla se l’albero `t` è un `Sum`; se lo è, esegue il bind del @@ -499,7 +499,7 @@ 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]) { + def main(args: Array[String]): Unit = { 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) @@ -557,7 +557,7 @@ dichiarazione di un trait: } Questa definizione crea un nuovo tipo chiamato `Ord` che ha lo stesso -ruolo dell’interfaccia `Comparable` in Java e, fornisce l’implementazione +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. @@ -646,7 +646,7 @@ 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 +ecc.) ed implica che molto type cast dinamico deve esser fatto dal programmatore. Scala rende possibile la definizione delle classi generiche (e metodi) per @@ -678,7 +678,7 @@ per creare ed usare una cella che contiene un intero si potrebbe scrivere il seguente codice: object IntegerReference { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) diff --git a/_ja/cheatsheets/index.md b/_ja/cheatsheets/index.md index 9038a75a42..ac3551736c 100644 --- a/_ja/cheatsheets/index.md +++ b/_ja/cheatsheets/index.md @@ -260,7 +260,7 @@ breakable { val div = x / y.toFloat println("%d/%d = %.1f".format(x, y, div)) }</code></pre></td> - <td>for 内包表記: 命令型の記述<br /><a href="https://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax">sprintf-style</a></td> + <td>for 内包表記: 命令型の記述<br /><a href="https://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax"><code>sprintf</code>-style</a></td> </tr> <tr> <td><pre class="highlight"><code>for (i <- 1 to 5) { diff --git a/_ja/getting-started/index.md b/_ja/getting-started/index.md deleted file mode 100644 index 3a20db86c6..0000000000 --- a/_ja/getting-started/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: singlepage-overview -title: 入門 -partof: getting-started -language: ja -includeTOC: true ---- - -{% include _ja/getting-started.md %} diff --git a/_includes/_ja/getting-started.md b/_ja/getting-started/install-scala.md similarity index 82% rename from _includes/_ja/getting-started.md rename to _ja/getting-started/install-scala.md index 1df3b33933..a85f87263c 100644 --- a/_includes/_ja/getting-started.md +++ b/_ja/getting-started/install-scala.md @@ -1,3 +1,13 @@ +--- +layout: singlepage-overview +title: 入門 +partof: getting-started +language: ja +includeTOC: true +redirect_from: + - /ja/scala3/getting-started.html # we deleted the scala 3 version of this page +--- + このページは Scala 2と Scala 3の両方に対応しています。 ## インストールなしで今すぐ Scala を試す! @@ -17,13 +27,41 @@ Scala をインストールすると、コンパイラやビルドツールな Scala のインストーラーは[Coursier](https://get-coursier.io/docs/cli-overview)というツールで、コマンドは`cs`です。このツールを使うと、JVM と標準 Scala ツールがシステムにインストールされます。 以下の手順でお使いのシステムにインストールしてください。 -<div class="main-download"> - <div id="download-step-one"> - <p>Follow <a href="https://get-coursier.io/docs/cli-installation.html#native-launcher" target="_blank">the instructions to install the <code>cs</code> launcher</a> then run:</p> - <p><code>$ ./cs setup</code></p> - </div> -</div> - +<!-- Display tabs for each OS --> +{% tabs install-cs-setup-tabs class=platform-os-options %} + +<!-- macOS --> +{% tab macOS for=install-cs-setup-tabs %} +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-brew %} +{% altDetails cs-setup-macos-nobrew "または、Homebrewを使用しない場合は" %} + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-x86-64 %} +{% endaltDetails %} +{% endtab %} +<!-- end macOS --> + +<!-- Linux --> +{% tab Linux for=install-cs-setup-tabs %} + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-x86-64 %} +{% endtab %} +<!-- end Linux --> + +<!-- Windows --> +{% tab Windows for=install-cs-setup-tabs %} + [the Scala installer for Windows]({{site.data.setup-scala.windows-link}})を、ダウンロードして実行してください。 +{% endtab %} +<!-- end Windows --> + +<!-- Other --> +{% tab Other for=install-cs-setup-tabs defaultTab %} + <noscript> + <p><span style="font-style:italic;">JavaScript is disabled, click the tab relevant for your OS.</span></p> + </noscript> + [手順に従って `cs` ランチャーをインストール](https://get-coursier.io/docs/cli-installation)し、その次に以下を実行します。`./cs setup` +{% endtab %} +<!-- end Other --> + +{% endtabs %} +<!-- End tabs --> `cs setup` は JVM の管理だけでなく、便利なコマンドラインツールもインストールします: @@ -112,11 +150,11 @@ IDE を使ってプロジェクトを開いてみましょう。最もポピュ - _build.sbt_ - _src/main/scala/Main.scala_ -次のステップでプロジェクトを実行すると、_src/main/scala/Main.scala_のコードを実行するために、_build.sbt_の設定が使われます。 +次のステップでプロジェクトを実行すると、 _src/main/scala/Main.scala_ のコードを実行するために、 _build.sbt_ の設定が使われます。 ## Hello World の実行 -IDE の使用に慣れている場合は、IDE から_Main.scala_のコードを実行することができます。 +IDE の使用に慣れている場合は、IDE から _Main.scala_ のコードを実行することができます。 または、以下の手順でターミナルからアプリケーションを実行できます: @@ -132,7 +170,7 @@ IDE の使用に慣れている場合は、IDE から_Main.scala_のコードを * [The Scala Book](/scala3/book/introduction.html) (Scala 2版は[こちら](/overviews/scala-book/introduction.html))はScalaの主な機能を紹介する短いレッスンのセットを提供します。 * [The Tour of Scala](/tour/tour-of-scala.html) Scalaの機能を一口サイズで紹介します。 -* [Learning Resources](/learn.html) オンラインのインタラクティブなチュートリアルやコースです。 +* [Learning Courses](/online-courses.html) オンラインのインタラクティブなチュートリアルやコースです。 * [books](/books.html) 人気のある Scalaの 書籍を紹介します * [The migration guide](/scala3/guides/migration/compatibility-intro.html) 既存の Scala 2コードベースを Scala 3に移行する際に役立ちます。 @@ -141,29 +179,3 @@ IDE の使用に慣れている場合は、IDE から_Main.scala_のコードを ### (日本語のみ追記) Scala について日本語で質問したい場合、Twitterでつぶやくと気づいた人が教えてくれます。 - -<!-- Hidden elements whose content are used to provide OS-specific download instructions. - -- This is handled in `resources/js/functions.js`. - --> -<div style="display:none" id="stepOne-linux"> - <code class="hljs">$ curl -fL https://github.com/coursier/launchers/raw/master/cs-x86_64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup</code> <br> -</div> - -<div style="display:none" id="stepOne-unix"> - <p><a href="https://get-coursier.io/docs/cli-installation" target="_blank">手順に従って <code>cs</code> ランチャーをインストール</a>し、その次に以下を実行します。</p> - <p><code>$ ./cs setup</code></p> -</div> - -<div style="display:none" id="stepOne-osx"> - <div class="highlight"> - <code class="hljs">$ brew install coursier/formulas/coursier && cs setup </code> <br> - </div> - <p>または、Homebrewを使用しない場合は</p> - <div class="highlight"> - <code class="hljs">$ curl -fL https://github.com/coursier/launchers/raw/master/cs-x86_64-apple-darwin.gz | gzip -d > cs && chmod +x cs && (xattr -d com.apple.quarantine cs || true) && ./cs setup</code> <br> - </div> -</div> - -<div style="display:none" id="stepOne-windows"> - <p><a href="https://github.com/coursier/launchers/raw/master/cs-x86_64-pc-win32.zip">the Scala installer for Windows</a>を、ダウンロードして実行してください。</p> -</div> diff --git a/_ja/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md b/_ja/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md index ae7573edba..e701b837f0 100644 --- a/_ja/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md +++ b/_ja/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md @@ -56,10 +56,9 @@ sbt は、より複雑なプロジェクトを構築すだしたら便利にな 1. クラスのコードを次おように変更します。 ``` -object Main extends App { +@main def run() = val ages = Seq(42, 75, 29, 64) println(s"The oldest person is ${ages.max}") -} ``` 注:Intellij は Scala コンパイラーの独自実装を持っており、コードが間違っていると Intellij が示しても正しい場合がときどきあります。 diff --git a/_ja/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md b/_ja/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md index 62814e914a..815760672c 100644 --- a/_ja/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md +++ b/_ja/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md @@ -16,7 +16,7 @@ Scala には複数のライブラリとテスト方法がありますが、こ 1. ScalaTest への依存を追加します。 1. `build.sbt` ファイルに ScalaTest への依存を追加します。 ``` - libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test ``` 1. `build.sbt was changed` という通知が出たら、**auto-import** を選択します。 1. これらの2つのアクションにより、`sbt` が ScalaTest ライブラリをダウンロードします。 @@ -26,11 +26,9 @@ Scala には複数のライブラリとテスト方法がありますが、こ 1. クラスに `CubeCalculator` と名前をつけて、**Kind** を `object` に変更し、**OK** をクリックします。 1. コードを次の通り置き換えます。 ``` - object CubeCalculator extends App { - def cube(x: Int) = { + object CubeCalculator: + def cube(x: Int) = x * x * x - } - } ``` ## テストを作成 @@ -39,13 +37,12 @@ Scala には複数のライブラリとテスト方法がありますが、こ 1. クラスに `CubeCalculatorTest` と名前を付けて、**OK** をクリックします。 1. コードを次の通り置き換えます。 ``` - import org.scalatest.FunSuite + import org.scalatest.funsuite.AnyFunSuite - class CubeCalculatorTest extends FunSuite { + class CubeCalculatorTest extends AnyFunSuite: test("CubeCalculator.cube") { assert(CubeCalculator.cube(3) === 27) } - } ``` 1. `CubeCalculatorTest` のソースコード内で右クリックし、**Run 'CubeCalculatorTest'** を選択します。 diff --git a/_ja/index.md b/_ja/index.md index a64b13546e..358ef903da 100644 --- a/_ja/index.md +++ b/_ja/index.md @@ -1,6 +1,6 @@ --- -layout: documentation -title: ドキュメント +layout: landing-page +title: Learn Scala language: ja partof: documentation more-resources-label: その他のリソース @@ -8,61 +8,33 @@ more-resources-label: その他のリソース # Content masthead links -scala3-sections: - - title: "First steps" - links: - - title: "Scala 3 の新機能" - description: "Scala 3 で追加されたさまざまな新機能の概要" - icon: "fa fa-star" - link: /ja/scala3/new-in-scala3.html - - title: "Scala 3 をはじめる" - description: "あなたのコンピューターに Scala 3 をインストールしてScalaコードを書きはじめよう!" - icon: "fa fa-rocket" - link: /ja/scala3/getting-started.html - - title: "Scala 3 Book" - description: "主要な言語仕様のイントロダクションをオンラインブックで読む" - icon: "fa fa-book" - link: /scala3/book/introduction.html - - title: "More detailed information" - links: - - title: "Migration Guide" - description: "Scala 2 から Scala 3 へ移行するためのガイド" - icon: "fa fa-suitcase" - link: /scala3/guides/migration/compatibility-intro.html - - title: "Guides" - description: "Scala 3 の言語仕様からピックアップして解説" - icon: "fa fa-map" - link: /ja/scala3/guides.html - - title: "API" - description: "Scala 3 の全バージョンのAPIドキュメント" - icon: "fa fa-file-alt" - link: https://scala-lang.org/api/3.x/ - - title: "Language Reference" - description: "Scala 3 の言語仕様" - icon: "fa fa-book" - link: /scala3/reference/overview.html - -scala2-sections: - +sections: - title: "最初のステップ" links: - title: "入門" description: "あなたのコンピューターに Scala をインストールして、Scala コードを書きはじめよう!" icon: "fa fa-rocket" - link: /ja/getting-started/index.html + link: /ja/getting-started/install-scala.html - title: "Scala ツアー" description: "コア言語機能をひと口大で紹介" icon: "fa fa-flag" link: /ja/tour/tour-of-scala.html - - title: "Java プログラマーのための Scala" - description: "Java 経験者向けのすばやい紹介" - icon: "fa fa-coffee" - link: /ja/tutorials/scala-for-java-programmers.html - more-resources: - - title: オンラインリソース(英語のみ) - url: /learn.html + - title: "Scala 3 Book" + description: "主要な言語仕様のイントロダクションをオンラインブックで読む" + icon: "fa fa-book" + link: /scala3/book/introduction.html + - title: Online Courses + description: "MOOCs to learn Scala, for beginners and experienced programmers." + icon: "fa fa-cloud" + link: /online-courses.html - title: 書籍(英語のみ) - url: /books.html + description: "Printed and digital books about Scala." + icon: "fa fa-book" + link: /books.html + - title: Tutorials + description: "Take you by the hand through a series of steps to create Scala applications." + icon: "fa fa-tasks" + link: /tutorials.html - title: "リピーター向け" links: @@ -91,6 +63,29 @@ scala2-sections: description: "Scala の形式的言語仕様(英語のみ)" icon: "fa fa-book" link: https://scala-lang.org/files/archive/spec/2.13/ + - title: "Language Reference" + description: "Scala 3 の言語仕様" + icon: "fa fa-book" + link: https://docs.scala-lang.org/scala3/reference + + - title: "Explore Scala 3" + links: + - title: "Migration Guide" + description: "Scala 2 から Scala 3 へ移行するためのガイド" + icon: "fa fa-suitcase" + link: /scala3/guides/migration/compatibility-intro.html + - title: "Scala 3 の新機能" + description: "Scala 3 で追加されたさまざまな新機能の概要" + icon: "fa fa-star" + link: /ja/scala3/new-in-scala3.html + - title: "All new Scaladoc for Scala 3" + description: "Highlights of new features for Scaladoc" + icon: "fa fa-star" + link: /scala3/scaladoc.html + - title: "Talks" + description: "Talks about Scala 3 that can be watched online" + icon: "fa fa-play-circle" + link: /scala3/talks.html - title: "Scala の進化" links: @@ -98,4 +93,8 @@ scala2-sections: description: "Scala Improvement Process(Scala 改善プロセス)。言語とコンパイラの進化(英語のみ)" icon: "fa fa-cogs" link: /sips/index.html + - title: "Become a Scala OSS Contributor" + description: "From start to finish: discover how you can help Scala's open-source ecosystem" + icon: "fa fa-code-branch" + link: /contribute/ --- diff --git a/_ja/overviews/collections/sets.md b/_ja/overviews/collections/sets.md index f5fab5cee4..47e7a40b62 100644 --- a/_ja/overviews/collections/sets.md +++ b/_ja/overviews/collections/sets.md @@ -104,7 +104,7 @@ language: ja 可変集合は `+=` と `-=` の別形として `add` と `remove` を提供する。違いは `add` と `remove` は集合に対して演算の効果があったかどうかを示す `Boolean` の戻り値を返すことだ。 -現在の可変集合のデフォルトの実装では要素を格納するのにハッシュテーブルを使っている。不変集合のデフォルトの実装は集合の要素数に応じて方法を変えている。空集合はシングルトンで表される。サイズが4つまでの集合は全要素をフィールドとして持つオブジェクトとして表される。それを超えたサイズの不変集合は[ハッシュトライ]()として表される。 +現在の可変集合のデフォルトの実装では要素を格納するのにハッシュテーブルを使っている。不変集合のデフォルトの実装は集合の要素数に応じて方法を変えている。空集合はシングルトンで表される。サイズが4つまでの集合は全要素をフィールドとして持つオブジェクトとして表される。それを超えたサイズの不変集合は[ハッシュトライ](concrete-immutable-collection-classes.html)として表される。 このような設計方針のため、(例えば 4以下の) 小さいサイズの集合を使う場合は、通常の場合、可変集合に比べて不変集合の方が、よりコンパクトで効率的だ。集合のサイズが小さいと思われる場合は、不変集合を試してみてはいかがだろうか。 diff --git a/_ja/overviews/core/futures.md b/_ja/overviews/core/futures.md index d644886a12..1d0575a723 100644 --- a/_ja/overviews/core/futures.md +++ b/_ja/overviews/core/futures.md @@ -139,43 +139,19 @@ Future の実装の多くは、Future の結果を知りたくなったクライ } `onComplete` メソッドは、Future 計算の失敗と成功の両方の結果を扱えるため、汎用性が高い。 -成功した結果のみ扱う場合は、(部分関数を受け取る) `onSuccess` コールバックを使う: +成功した結果のみ扱う場合は、`foreach` コールバックを使う: val f: Future[List[String]] = Future { session.getRecentPosts } - f onSuccess { - case posts => for (post <- posts) println(post) + f foreach { 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("これが表示されているとしたらビックリ。") - } +`Future` は、失敗した結果のみを処理する方法を提供していて、 +`Failure[Throwable]` を `Success[Throwable]` へと変換する `failed` 投射を用いることができる。 +詳細は以下の投射を参照。 キーワードの初出の位置を検索する例に戻ると、キーワードの位置を画面に表示したいかもしれない: @@ -184,15 +160,12 @@ Future の実装の多くは、Future の結果を知りたくなったクライ source.toSeq.indexOfSlice("myKeyword") } - firstOccurence onSuccess { - case idx => println("キーワードの初出位置: " + idx) - } - - firstOccurence onFailure { - case t => println("ファイルの処理に失敗した: " + t.getMessage) + firstOccurence oncomplete { + case Success(idx) => println("キーワードの初出位置: " + idx) + case Failure(t) => println("処理失敗:" + t.getMessage) } -`onComplete`、`onSuccess`、および `onFailure` メソッドは全て `Unit` 型を返すため、これらの呼び出しを連鎖させることができない。 +`onComplete`、および `foreach` メソッドは全て `Unit` 型を返すため、これらの呼び出しを連鎖させることができない。 これは意図的な設計で、連鎖された呼び出しが登録されたコールバックの実行の順序を暗示しないようにしている (同じ Future に登録されたコールバックは順不同に発火される)。 @@ -212,12 +185,12 @@ Future 内の値が利用可能となることを必要とするため、Future "na" * 16 + "BATMAN!!!" } - text onSuccess { - case txt => totalA += txt.count(_ == 'a') + text.foreach { txt => + totalA += txt.count(_ == 'a') } - text onSuccess { - case txt => totalA += txt.count(_ == 'A') + text.foreach { txt => + totalA += txt.count(_ == 'A') } 2つのコールバックが順次に実行された場合は、変数 `totalA` は期待される値 `18` を持つ。 @@ -232,7 +205,7 @@ Future 内の値が利用可能となることを必要とするため、Future <ol> <li>Future に <code>onComplete</code> コールバックを登録することで、対応するクロージャが Future が完了した後に eventually に実行されることが保証される。</li> -<li><code>onSuccess</code> や <code>onFailure</code> コールバックを登録することは <code>onComplete</code> と同じ意味論を持つ。ただし、クロージャがそれぞれ成功したか失敗した場合のみに呼ばれるという違いがある。</li> +<li><code>foreach</code> コールバックを登録することは <code>onComplete</code> と同じ意味論を持つ。ただし、クロージャがそれぞれ成功したか失敗した場合のみに呼ばれるという違いがある。</li> <li>既に完了した Future にコールバックを登録することは (1 により) コールバックが eventually に実行されることとなる。</li> @@ -257,33 +230,32 @@ Future 内の値が利用可能となることを必要とするため、Future connection.getCurrentValue(USD) } - rateQuote onSuccess { case quote => + for (quote <- rateQuote) { val purchase = Future { if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("有益ではない") } - purchase onSuccess { - case _ => println(amount + " USD を購入した") - } + for (amount <- purchase) + println(amount + " USD を購入した") } まずは現在の為替相場を取得する `rateQuote` という `Future` を作る。 この値がサーバから取得できて Future が成功した場合は、計算は -`onSuccess` コールバックに進み、ここで買うかどうかの決断をすることができる。 +`foreach` コールバックに進み、ここで買うかどうかの決断をすることができる。 ここでもう 1つの Future である `purchase` を作って、有利な場合のみ買う決定をしてリクエストを送信する。 最後に、`purchase` が完了すると、通知メッセージを標準出力に表示する。 これは動作するが、2つの理由により不便だ。 -第一に、`onSuccess` を使わなくてはいけなくて、2つ目の Future である +第一に、`foreach` を使わなくてはいけなくて、2つ目の Future である `purchase` をその中に入れ子にする必要があることだ。 例えば `purchase` が完了した後に別の貨幣を売却したいとする。 -それはまた `onSuccess` の中でこのパターンを繰り返すことになり、インデントしすぎで理解しづらく肥大化したコードとなる。 +それはまた `foreach` の中でこのパターンを繰り返すことになり、インデントしすぎで理解しづらく肥大化したコードとなる。 -第二に、`purchase` は他のコードのスコープ外にあり、`onSuccess` +第二に、`purchase` は他のコードのスコープ外にあり、`foreach` コールバック内においてのみ操作することができる。 そのため、アプリケーションの他の部分は `purchase` を見ることができず、他の貨幣を売るために別の -`onSuccess` コールバックを登録することもできない。 +`foreach` コールバックを登録することもできない。 これらの 2つの理由から Future はより自然な合成を行うコンビネータを提供する。 基本的なコンビネータの 1つが `map` で、これは与えられた Future @@ -301,11 +273,11 @@ Future の投射はコレクションの投射と同様に考えることがで else throw new Exception("有益ではない") } - purchase onSuccess { - case _ => println(amount + " USD を購入した") + purchase.foreach { amount => + println(amount + " USD を購入した") } -`rateQuote` に対して `map` を使うことで `onSuccess` コールバックを一切使わないようになった。 +`rateQuote` に対して `map` を使うことで `foreach` コールバックを一切使わないようになった。 それと、より重要なのが入れ子が無くなったことだ。 ここで他の貨幣を売却したいと思えば、`purchase` に再び `map` するだけでいい。 @@ -321,7 +293,7 @@ Future の投射はコレクションの投射と同様に考えることがで この例外を伝搬させる意味論は他のコンビネータにおいても同様だ。 Future の設計指針の 1つは for 内包表記から利用できるようにすることだった。 -このため、Future は `flatMap`、`filter` そして `foreach` コンビネータを持つ。 +このため、Future は `flatMap` そして `withFilter` コンビネータを持つ。 `flatMap` メソッドは値を新しい Future `g` に投射する関数を受け取り、`g` が完了したときに完了する新たな Future を返す。 @@ -338,8 +310,8 @@ Future の設計指針の 1つは for 内包表記から利用できるように if isProfitable(usd, chf) } yield connection.buy(amount, chf) - purchase onSuccess { - case _ => println(amount + " CHF を購入した") + purchase foreach { _ => + println(amount + " CHF を購入した") } この `purchase` は `usdQuote` と `chfQuote` が完了した場合のみ完了する。 @@ -370,10 +342,6 @@ Future に関しては、`filter` の呼び出しは `withFilter` の呼び出 `collect` と `filter` コンビネータの関係はコレクション API におけるこれらのメソッドの関係に似ている。 -`foreach` コンビネータで注意しなければいけないのは値が利用可能となった場合に走査するのにブロックしないということだ。 -かわりに、`foreach` のための関数は Future が成功した場合のみ非同期に実行される。 -そのため、`foreach` は `onSuccess` コールバックと全く同じ意味を持つ。 - `Future` トレイトは概念的に (計算結果と例外という) 2つの型の値を保持することができるため、例外処理のためのコンビネータが必要となる。 `rateQuote` に基いて何らかの額を買うとする。 @@ -418,7 +386,7 @@ Future は同じ `Throwable` とともに失敗する。 val anyQuote = usdQuote fallbackTo chfQuote - anyQuote onSuccess { println(_) } + anyQuote foreach { println(_) } `andThen` コンビネータは副作用の目的のためだけに用いられる。 これは、成功したか失敗したかに関わらず現在の Future と全く同一の結果を返す新たな Future を作成する。 @@ -454,12 +422,18 @@ Future は同じ `Throwable` とともに失敗する。 } for (exc <- f.failed) println(exc) +上の例での for 内包表記は以下のように書き換えることができる: + + f.failed.foreach( exc => println(exc)) + +`f` が失敗したため、クロージャは新規に成功値を持つ `Future[Throwable]` の +`foreach` コールバックに登録される。 以下の例は画面に何も表示しない: - val f = Future { + val g = Future { 4 / 2 } - for (exc <- f.failed) println(exc) + for (exc <- g.failed) println(exc) <!-- There is another projection called `timedout` which is specific to the @@ -500,7 +474,7 @@ Future の結果に対してブロックする方法を以下に具体例で説 import scala.concurrent._ import scala.concurrent.duration._ - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val rateQuote = Future { connection.getCurrentValue(USD) } @@ -536,8 +510,7 @@ Future が失敗した場合は、呼び出し元には Future が失敗した 非同期の計算が処理されない例外を投げた場合、その計算が行われた Future は失敗する。 失敗した Future は計算値のかわりに `Throwable` のインスタンスを格納する。 -`Future` は、`Throwable` に適用することができる `PartialFunction` を受け取る -`onFailure` コールバックメソッドを提供する。 +また、`Future` はこの `Throwable` インスタンスを別の `Future` の計算値として扱う `failed` 投射メソッドを提供する。 以下の特別な例外に対しては異なる処理が行われる: 1. `scala.runtime.NonLocalReturnControl[_]`。この例外は戻り値に関連する値を保持する。 @@ -576,14 +549,14 @@ Promise の `p` は `p.future` によって返される Future を完了させ val producer = Future { val r = produceSomething() - p success r + p.success(r) continueDoingSomethingUnrelated() } val consumer = Future { startDoingSomething() - f onSuccess { - case r => doSomethingWithResult() + f.foreach { r => + doSomethingWithResult() } } @@ -644,8 +617,8 @@ HTTP レスポンスにのみ興味がある場合で、これは最初に Promi p completeWith f - p.future onSuccess { - case x => println(x) + p.future.foreach { x => + println(x) } Promise を例外とともに失敗させる場合は、`Throwable` の 3つのサブタイプが特殊扱いされる。 @@ -664,12 +637,12 @@ Promise、Future の `onComplete` メソッド、そして `future` def first[T](f: Future[T], g: Future[T]): Future[T] = { val p = Promise[T]() - f onSuccess { - case x => p.tryComplete(x) + f.foreach { x => + p.tryComplete(x) } - g onSuccess { - case x => p.tryComplete(x) + g.foreach { x => + p.tryComplete(x) } p.future diff --git a/_ja/overviews/core/value-classes.md b/_ja/overviews/core/value-classes.md index 72df60ede9..6b10b762e5 100644 --- a/_ja/overviews/core/value-classes.md +++ b/_ja/overviews/core/value-classes.md @@ -51,7 +51,7 @@ language: ja def toHexString: String = java.lang.Integer.toHexString(self) } -実行時には、この `3.toHexString` という式は、新しくインスタンス化されるオブジェクトへのメソッド呼び出しではなく、静的なオブジェクトへのメソッド呼び出しと同様のコード (`RichInt$.MODULE$.extension$toHexString(3)`) へと最適化される。 +実行時には、この `3.toHexString` という式は、新しくインスタンス化されるオブジェクトへのメソッド呼び出しではなく、静的なオブジェクトへのメソッド呼び出しと同様のコード (`RichInt$.MODULE$.toHexString$extension(3)`) へと最適化される。 ## 正当性 diff --git a/_ja/overviews/index.md b/_ja/overviews/index.md index 5358fa7343..77e9d1b8d6 100644 --- a/_ja/overviews/index.md +++ b/_ja/overviews/index.md @@ -3,6 +3,8 @@ layout: overviews partof: overviews title: ガイドと概要 language: ja +redirect_from: + - "/ja/scala3/guides.html" --- <!-- Auto generated from _data/overviews-ja.yml --> diff --git a/_ja/overviews/macros/blackbox-whitebox.md b/_ja/overviews/macros/blackbox-whitebox.md index 36ceca43df..30e582f473 100644 --- a/_ja/overviews/macros/blackbox-whitebox.md +++ b/_ja/overviews/macros/blackbox-whitebox.md @@ -33,7 +33,7 @@ Scala 2.11.x および Scala 2.12.x 系列では、マクロ機能が blackbox しかし、ときとして def マクロは「ただのメソッド呼び出し」という概念を超越することがある。例えば、展開されたマクロは、元のマクロの戻り値の型よりも特化された型を持つ式を返すことが可能だ。Scala 2.10 では、StackOverflow の [Static return type of Scala macros](https://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) で解説したように、そのような展開は特化された型を保持し続けることができる。 -この興味深い機能がもたらす柔軟性によって、[偽装型プロバイダ](https://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、[具現化の拡張](/sips/source-locations.html)、[関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization)、抽出子マクロなどを可能とするけども、書かれたコードの明確さ (人にとってもマシンにとっても) が犠牲になるという側面がある。 +この興味深い機能がもたらす柔軟性によって、[偽装型プロバイダ](https://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、[具現化の拡張](https://github.com/scala/improvement-proposals/pull/18)、[関数従属性の具現化](/ja/overviews/macros/implicits.html#関数従属性の具現化)、抽出子マクロなどを可能とするけども、書かれたコードの明確さ (人にとってもマシンにとっても) が犠牲になるという側面がある。 普通のメソッド同様に振る舞うマクロと戻り値の型を細別化 (refine) するマクロという決定的な区別を明確にするために、blackbox マクロと whitebox マクロという概念を導入することにした。型シグネチャに忠実に従うマクロは、振る舞いを理解するのに実装を知る必要が無い (ブラックボックスとして扱うことができる) ため、**blackbox マクロ** (blackbox macro) と呼ぶ。 Scala の型システムを使ってシグネチャを持つことができないマクロは **whitebox マクロ** (whitebox macro) と呼ぶ。(whitebox def マクロもシグネチャは持つが、これらのシグネチャは近似値でしかない) @@ -49,8 +49,8 @@ blackbox マクロと whitebox マクロの両方とも大切なことは認識 blackbox def マクロは Scala 2.10 の def マクロと異なる扱いとなる。Scala の型検査において以下の制限が加わる: 1. blackbox マクロが構文木 `x` に展開するとき、展開される式は型注釈 `(x: T)` でラップされる。この `T` は blackbox マクロの宣言された戻り値の型に、マクロ適用時に一貫性を持つように型引数やパス依存性を適用したものとなる。これによって、blackbox マクロを[型プロバイダ](https://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/source-locations.html)ことが無効となる。 +1. Scala の型推論アルゴリズムが終わった後でも blackbox マクロの適用に未決定の型パラメータが残る場合、これらの型パラメータは普通のメソッドと同様に強制的に型推論が実行される。これによって blackbox マクロから型推論に影響を与えることが不可能となり、[関数従属性の具現化](/ja/overviews/macros/implicits.html#関数従属性の具現化)に使うことが無効となる。 +1. blackbox マクロの適用が implicit の候補として使われる場合、implicit 検索がそのマクロを選択するまでは展開は実行されない。これによって [implicit マクロの入手可能性を動的に計算する](https://github.com/scala/improvement-proposals/pull/18)ことが無効となる。 1. blackbox マクロの適用がパターンマッチの抽出子として使われる場合、無条件でコンパイラエラーを発生するようにして、マクロで実装されたパターンマッチングのカスタマイズを無効となる。 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 index c62f99e2ef..b2c4809533 100644 --- a/_ja/overviews/macros/bundles.md +++ b/_ja/overviews/macros/bundles.md @@ -21,7 +21,7 @@ Scala 2.10.x においてマクロ実装は関数として表されている。 <ol> <li>関数に制限されることで複雑なマクロのモジュール化がしづらくなる。マクロのロジックがマクロ実装外のヘルパートレイトに集中していて、マクロ実装がヘルパーをインスタンス化するだけのラッパーになってしまっているのは典型的な例だ。</li> -<li>さらに、マクロのパラメータがマクロのコンテキストにパス依存であるため、ヘルパーと実装をつなぐのに<a href="/ja/overviews/macros/overview.html#writing_bigger_macros">特殊なおまじない</a>を必要とする。</li> +<li>さらに、マクロのパラメータがマクロのコンテキストにパス依存であるため、ヘルパーと実装をつなぐのに<a href="/ja/overviews/macros/overview.html#より大きなマクロを書く">特殊なおまじない</a>を必要とする。</li> </ol> マクロバンドルは、マクロ実装を diff --git a/_ja/overviews/macros/implicits.md b/_ja/overviews/macros/implicits.md index 619997501d..9119b0a5a3 100644 --- a/_ja/overviews/macros/implicits.md +++ b/_ja/overviews/macros/implicits.md @@ -86,7 +86,6 @@ Scala implicit の標準機能である複数のパラメータや重複した この場合、必須のインスタンスである `Showable[Int]` は先に定義した具現化マクロによって生成される。つまり、マクロを implicit にすることで型クラスインスタンスの具現化を自動化すると同時にマクロを使わない implicit もシームレスに統合することができる。 -<a name="fundep_materialization"> </a> ## 関数従属性の具現化 diff --git a/_ja/overviews/macros/overview.md b/_ja/overviews/macros/overview.md index 02997905c9..ff18d7f2b6 100644 --- a/_ja/overviews/macros/overview.md +++ b/_ja/overviews/macros/overview.md @@ -214,7 +214,6 @@ Scala コードの生成については[リフレクションの概要](https:// このシナリオは前節で説明したとおりだ。つまり、マクロとそれを使用するコードを別に呼び出した `scalac` によってコンパイルすることで、全てうまくいくはずだ。REPL をつかっているなら、さらに都合がいい。なぜなら REPL はそれぞれの行を独立したコンパイルとして扱うため、マクロを定義してすぐに使うことができる。 -<a name="using_macros_with_maven_or_sbt"> </a> ### 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点だ: @@ -321,7 +320,6 @@ Scala コードの生成については[リフレクションの概要](https:// [SI-6910](https://issues.scala-lang.org/browse/SI-6910) に記述されているとおり、現時点ではある位置から複数の警告やエラーの報告はサポートされていないことに注意してほしい。そのため、ある位置で最初のエラーか警告だけが報告され他は失くなってしまう。(ただし、同じ位置で後から報告されてもエラーは警告よりも優先される) -<a name="writing_bigger_macros"> </a> ### より大きなマクロを書く マクロ実装が実装メソッドの本文におさまりきらなくなって、モジュール化の必要性が出てくると、コンテキストパラメータを渡して回る必要があることに気付くだろう。マクロを定義するのに必要なもののほとんどがこのコンテキストにパス依存しているからだ。 diff --git a/_ja/overviews/macros/paradise.md b/_ja/overviews/macros/paradise.md index 93d610a649..5fd8e5de7d 100644 --- a/_ja/overviews/macros/paradise.md +++ b/_ja/overviews/macros/paradise.md @@ -19,7 +19,7 @@ title: マクロパラダイス マクロパラダイス (Macro paradise) とは Scala の複数のバージョンをサポートするコンパイラプラグインで、一般向けにリリースされている <code>scalac</code> と共に正しく動作するように設計されている。 これによって、将来の Scala に取り込まれるよりもいち早く最新のマクロ機能を使えるようになっている。 [サポートされている機能とバージョンの一覧](/ja/overviews/macros/roadmap.html))に関してはロードマップページを、 -動作の保証に関しては[マクロパラダイスのアナウンスメント](https://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html)を参照してほしい。 +動作の保証に関しては[マクロパラダイスのアナウンスメント](hxxps://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html)を参照してほしい。 ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases phase name id description @@ -35,7 +35,7 @@ title: マクロパラダイス 詳細は[機能の一覧を](/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)ことが前提だが): +(すでに[sbt を使っている](/ja/overviews/macros/overview.html#maven-か-sbt-を用いてマクロを使う)ことが前提だが): resolvers += Resolver.sonatypeRepo("releases") diff --git a/_ja/overviews/macros/roadmap.md b/_ja/overviews/macros/roadmap.md index 5be65f30df..9300672ba6 100644 --- a/_ja/overviews/macros/roadmap.md +++ b/_ja/overviews/macros/roadmap.md @@ -27,7 +27,7 @@ title: ロードマップ | [def マクロ](/ja/overviews/macros/overview.html) | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | | [マクロバンドル](/ja/overviews/macros/bundles.html) | No | No <sup>1</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | | [implicit マクロ](/ja/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | -| [関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization) | No | Yes <sup>2</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | +| [関数従属性の具現化](/ja/overviews/macros/implicits.html#関数従属性の具現化) | No | Yes <sup>2</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | | [型プロバイダ](/ja/overviews/macros/typeproviders.html) | 一部サポート (see docs) | Yes <sup>2</sup> | 一部サポート (see docs) | Yes <sup>2</sup> | 一部サポート (see docs) | Yes <sup>2</sup> | | 準クォート | No | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | Yes | Yes <sup>1</sup> | | [型マクロ](/ja/overviews/macros/typemacros.html) | No | No | No | No | No | No | diff --git a/_ja/overviews/macros/typemacros.md b/_ja/overviews/macros/typemacros.md index 38dae43189..0ed863eb80 100644 --- a/_ja/overviews/macros/typemacros.md +++ b/_ja/overviews/macros/typemacros.md @@ -9,7 +9,7 @@ title: 型マクロ **Eugene Yokota 訳** 型マクロ (type macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 -[the paradise 2.0 announcement](https://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 +[the paradise 2.0 announcement](hxxps://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 ## 直観 diff --git a/_ja/overviews/macros/untypedmacros.md b/_ja/overviews/macros/untypedmacros.md index 08ad463cd9..7b857f783b 100644 --- a/_ja/overviews/macros/untypedmacros.md +++ b/_ja/overviews/macros/untypedmacros.md @@ -9,7 +9,7 @@ title: 型指定の無いマクロ **Eugene Yokota 訳** 型指定の無いマクロ (untyped macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 -[the paradise 2.0 announcement](https://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 +[the paradise 2.0 announcement](hxxps://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 ## 直観 diff --git a/_ja/overviews/macros/usecases.md b/_ja/overviews/macros/usecases.md index 5e4c691242..5a6767e378 100644 --- a/_ja/overviews/macros/usecases.md +++ b/_ja/overviews/macros/usecases.md @@ -20,7 +20,7 @@ Scala の商用ユーザと研究ユーザの両方がマクロを利用して ここ EPFL においても我々はマクロを活用して研究を行っている。Lightbend 社もマクロを数々のプロジェクトに採用している。 マクロはコミュニティー内でも人気があり、既にいくつかの興味深い応用が現れている。 -最近行われた講演の ["What Are Macros Good For?"](https://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf) では Scala 2.10 ユーザのマクロの利用方法を説明し、システム化した。講演の大筋はマクロはコード生成、静的な検査、および DSL に有効であるということで、これを研究や産業からの例を交えながら説明した。 +最近行われた講演の ["What Are Macros Good For?"](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf) では Scala 2.10 ユーザのマクロの利用方法を説明し、システム化した。講演の大筋はマクロはコード生成、静的な検査、および DSL に有効であるということで、これを研究や産業からの例を交えながら説明した。 -Scala'13 ワークショップにおいて ["Scala Macros: Let Our Powers Combine!"](https://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf) という論文を発表した。これは Scala 2.10 における最先端のマクロ論をより学問的な視点から説明した。 +Scala'13 ワークショップにおいて ["Scala Macros: Let Our Powers Combine!"](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/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 index c386beeb6b..146701cecc 100644 --- a/_ja/overviews/parallel-collections/architecture.md +++ b/_ja/overviews/parallel-collections/architecture.md @@ -56,7 +56,7 @@ language: ja Scala の並列コレクションは、Scala の(順次)コレクションライブラリの設計から多大な影響を受けている。 以下に示すよう、トレイト群は通常のコレクションフレームワーク内のトレイト群を鏡写しのように対応している。 -[<img src="{{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png" width="550">]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) +[<img src="{{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png" alt="Hierarchy of Scala Collections and Parallel Collections" width="550">]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) <center><b>Scala のコレクションと並列コレクションライブラリの継承関係</b></center> <br/> diff --git a/_ja/overviews/reflection/annotations-names-scopes.md b/_ja/overviews/reflection/annotations-names-scopes.md index dfe1312d4d..8101c9d772 100644 --- a/_ja/overviews/reflection/annotations-names-scopes.md +++ b/_ja/overviews/reflection/annotations-names-scopes.md @@ -53,7 +53,7 @@ Scala または Java アノテーションに対しては `scalaArgs` は空で ## 名前 **名前** (name) は文字列の簡単なラッパーだ。 -[`Name`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Names$NameApi.html) +[`Name`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Names$NameApi.html) には 2つのサブタイプ `TermName` と `TypeName` があり (オブジェクトやメンバーのような) 項の名前と (クラス、トレイト、型メンバのような) 型の名前を区別する。同じオブジェクト内に同名の項と型が共存することができる。別の言い方をすると、型と項は別の名前空間を持つ。 @@ -96,19 +96,19 @@ Scala のプログラムにおいて、「`_root_`」のような特定の名前 「`package`」のようないくつかの名前は型名と項名の両方が存在する。 標準名は `Universe` クラスの `termNames` と `typeNames` というメンバとして公開されている。 -全ての標準名の仕様は [API doc](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/StandardNames.html) を参照。 +全ての標準名の仕様は [API doc](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/StandardNames.html) を参照。 ## スコープ **スコープ** (scope) は一般にある構文スコープ内の名前をシンボルに関連付ける。 スコープは入れ子にすることもできる。リフレクション API -で公開されているスコープの基底型は [Symbol](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Symbols$Symbol.html) の iterable という最小限のインターフェイスのみを公開する。 +で公開されているスコープの基底型は [Symbol](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Symbols$Symbol.html) の iterable という最小限のインターフェイスのみを公開する。 追加機能は -[scala.reflect.api.Types#TypeApi](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types$TypeApi.html) +[scala.reflect.api.Types#TypeApi](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types$TypeApi.html) 内で定義されている `member` と `decls` が返す**メンバスコープ** (member scope) にて公開される。 -[scala.reflect.api.Scopes#MemberScope](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Scopes$MemberScope.html) +[scala.reflect.api.Scopes#MemberScope](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Scopes$MemberScope.html) は `sorted` メソッドをサポートしており、これはメンバを**宣言順に**ソートする。 以下に `List` クラスでオーバーライドされている全てのシンボルのリストを宣言順に返す具体例をみてみよう: @@ -119,7 +119,7 @@ Scala のプログラムにおいて、「`_root_`」のような特定の名前 ## Expr 構文木の基底型である `scala.reflect.api.Trees#Tree` の他に、型付けされた構文木は -[`scala.reflect.api.Exprs#Expr`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Exprs$Expr.html) 型によっても表すことができる。 +[`scala.reflect.api.Exprs#Expr`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Exprs$Expr.html) 型によっても表すことができる。 `Expr` は構文木と、その構文木の型に対するアクセスを提供するための型タグをラッピングする。 `Expr` は主にマクロのために便宜的に型付けられた構文木を作るために使われる。多くの場合、これは `reify` と `splice` メソッドが関わってくる。 @@ -175,8 +175,8 @@ Scala コンパイラによってコンパイル時に評価することがで 1. プリミティブ値クラスのリテラル ([Byte](https://www.scala-lang.org/api/current/index.html#scala.Byte)、 [Short](https://www.scala-lang.org/api/current/index.html#scala.Short)、 [Int](https://www.scala-lang.org/api/current/index.html#scala.Int)、 [Long](https://www.scala-lang.org/api/current/index.html#scala.Long)、 [Float](https://www.scala-lang.org/api/current/index.html#scala.Float)、 [Double](https://www.scala-lang.org/api/current/index.html#scala.Double)、 [Char](https://www.scala-lang.org/api/current/index.html#scala.Char)、 [Boolean](https://www.scala-lang.org/api/current/index.html#scala.Boolean) および [Unit](https://www.scala-lang.org/api/current/index.html#scala.Unit))。これは直接対応する型で表される。 2. 文字列リテラル。これは文字列のインスタンスとして表される。 -3. 一般に [scala.Predef#classOf](https://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) で構築されるクラスへの参照。[型](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types$Type.html)として表される。 -4. Java の列挙要素。[シンボル](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Symbols$Symbol.html)として表される。 +3. 一般に [scala.Predef#classOf](https://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) で構築されるクラスへの参照。[型](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types$Type.html)として表される。 +4. Java の列挙要素。[シンボル](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Symbols$Symbol.html)として表される。 定数式の用例としては @@ -264,8 +264,8 @@ Java の列挙要素への参照はシンボル (`scala.reflect.api.Symbols#Symb ## プリティプリンタ -[`Trees`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Trees.html) と -[`Types`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types.html) +[`Trees`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Trees.html) と +[`Types`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types.html) を整形して表示するユーティリティを説明しよう。 ### 構文木の表示 @@ -380,7 +380,7 @@ Java の列挙要素への参照はシンボル (`scala.reflect.api.Symbols#Symb ## 位置情報 -**位置情報** ([`Position`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Position.html)) +**位置情報** ([`Position`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Position.html)) はシンボルや構文木のノードの出処を追跡するのに使われる。警告やエラーの表示でよく使われ、プログラムのどこが間違ったのかを正確に表示することができる。位置情報はソースファイルの列と行を表す。 (ソースファイルの初めからのオフセットは「ポイント」と呼ばれるが、これは便利ではないことがある) 位置情報はそれが指す行の内容も保持する。全ての構文木やシンボルが位置情報を持つわけではなく、ない場合は diff --git a/_ja/overviews/reflection/overview.md b/_ja/overviews/reflection/overview.md index 92b0673a22..d884785fe5 100644 --- a/_ja/overviews/reflection/overview.md +++ b/_ja/overviews/reflection/overview.md @@ -54,7 +54,6 @@ Scala の式を抽象構文木へと**レイファイ** (reify) する機能 (3) ### 具体例 -<a name="inspecting_a_runtime_type"> </a> #### ランタイム型のインスペクション (実行時におけるジェネリック型も含む) 他の JVM言語同様に、Scala の型はコンパイル時に**消去** (erase) される。 @@ -82,7 +81,7 @@ Scala コンパイラが持つ型情報を全ては入手できない可能性 上の例では、まず `scala.reflect.runtime.universe` をインポートして (型タグを使うためには必ずインポートされる必要がある)、`l` という名前の `List[Int]` を作る。 -次に、context bound を持った型パラメータ `T` を持つ `getTypeTag` というメソッドは定義する +次に、context bound を持った型パラメータ `T` を持つ `getTypeTag` というメソッドを定義する (REPL が示すとおり、これは暗黙の evidence パラメータを定義することに等価であり、コンパイラは `T` に対する型タグを生成する)。 最後に、このメソッドに `l` を渡して呼び出し、`TypeTag` に格納される型を返す `tpe` を呼び出す。 見ての通り、正しい完全な型 (つまり、`List` の具象型引数を含むということ) である `List[Int]` が返ってきた。 diff --git a/_ja/overviews/reflection/symbols-trees-types.md b/_ja/overviews/reflection/symbols-trees-types.md index b02471883f..13247503b5 100644 --- a/_ja/overviews/reflection/symbols-trees-types.md +++ b/_ja/overviews/reflection/symbols-trees-types.md @@ -167,7 +167,7 @@ title: シンボル、構文木、型 scala> val intTpe = universe.definitions.IntTpe intTpe: scala.reflect.runtime.universe.Type = Int -標準型のリストは [`scala.reflect.api.StandardDefinitions`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/StandardDefinitions$StandardTypes.html) 内の `StandardTypes` +標準型のリストは [`scala.reflect.api.StandardDefinitions`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/StandardDefinitions$StandardTypes.html) 内の `StandardTypes` トレイトにて定義されている。 ### 型の一般的な演算 diff --git a/_ja/overviews/scala3-book/scala-for-python-devs.md b/_ja/overviews/scala3-book/scala-for-python-devs.md new file mode 100644 index 0000000000..41b7fc9f38 --- /dev/null +++ b/_ja/overviews/scala3-book/scala-for-python-devs.md @@ -0,0 +1,1383 @@ +--- +title: Scala for Python Developers +type: chapter +description: This page is for Python developers who are interested in learning about Scala 3. +languages: [zh-cn, ja] +num: 76 +previous-page: scala-for-javascript-devs +next-page: where-next +--- + +{% include_relative scala4x.css %} + +<div markdown="1" class="scala3-comparison-page"> + +{% comment %} + +NOTE: Hopefully someone with more Python experience can give this a thorough review. + +NOTE: On this page (https://contributors.scala-lang.org/t/feedback-sought-optional-braces/4702/10), Li Haoyi comments: “Python’s success also speaks for itself; beginners certainly don’t pick Python because of performance, ease of installation, packaging, IDE support, or simplicity of the language’s runtime semantics!” I’m not a Python expert, so these points are good to know, though I don’t want to go negative in any comparisons. +It’s more like thinking, “Python developers will appreciate Scala’s performance, ease of installation, packaging, IDE support, etc.” +{% endcomment %} + +{% comment %} +TODO: We should probably go through this document and add links to our other detail pages, when time permits. +{% endcomment %} + +このセクションでは、PythonとScalaのプログラミング言語を比較します。 +Pythonに詳しくて、Scalaについて学びたいと考えているプログラマーを対象としています。具体的には、Pythonの言語機能とScalaの比較例を挙げて説明します。 + +## はじめに + +例に入る前に、この最初のセクションでは、後に続くセクションの簡単な紹介と概要を提供します。 +まず、2つの言語の概要を高レベルで比較し、その後、実践的なプログラミングでの比較を行います。 + +### 高レベルでの類似点 + +高レベルで見ると、ScalaはPythonと以下のような *類似点* を共有しています。 + +- 高水準プログラミング言語であり、ポインタや手動でのメモリ管理といった低レベルの概念を気にする必要がありません。 +- 比較的シンプルで簡潔な構文を持ちます。 +- [関数型プログラミングスタイル][fp-intro]をサポートしています。 +- オブジェクト指向プログラミング(OOP)言語です。 +- 内包表記(comprehensions)をサポートしています。Pythonにはリスト内包表記があり、Scalaには`for`内包表記があります。 +- ラムダ式と[高階関数][hofs]をサポートしています。 +- [Apache Spark](https://spark.apache.org)を用いたビッグデータ処理に使用できます。 +- 優れたライブラリが豊富に使用できます。 + +### 高レベルでの相違点 + +高レベルで見ると、PythonとScalaの間には以下のような _相違点_ があります: + +- Python は動的型付け言語であり、Scala は静的型付け言語です。 + - Pythonは動的型付けですが、型ヒントによる「段階的型付け」をサポートしており、`mypy`のような静的型チェッカーで検証できます。 + - Scalaは静的型付けですが、型推論のような機能により動的言語のような感覚で書けます。 +- Pythonはインタプリタ型で実行され、Scalaのコードはコンパイルされて _.class_ ファイルになり、Java仮想マシン(JVM)上で動作します。 +- JVMでの実行に加えて、[Scala.js](https://www.scala-js.org)によりScalaをJavaScriptの代替として使用することもできます。 +- [Scala Native](https://scala-native.org/)では、「システムレベル」のコードを記述し、ネイティブ実行ファイルにコンパイルできます。 +- Scalaではすべてが _式_ である:`if`文、`for`ループ、`match`式、さらには`try`/`catch`式でさえも、戻り値を持ちます。 +- 慣用的に Scala では不変性を基本とする:不変変数や不変コレクションを使用することが推奨されています。 +- Scalaは[並行・並列プログラミング][concurrency]のサポートが優れています。 + +### プログラミングレベルでの類似点 + +このセクションでは、実際に Python と Scala でコードを書く際に見られる類似点を紹介します。 + +- Scalaの型推論により、動的型付け言語のような感覚でコーディングできます。 +- どちらの言語も式の末尾にセミコロンを使用しません。 +- 中括弧や括弧ではなく、インデントを重要視した記述がサポートされています。 +- メソッド定義の構文が似ています。 +- 両方ともリスト、辞書(マップ)、セット、タプルをサポートしています。 +- マッピングやフィルタリングに対応した内包表記を備えています。 +- 優れたIDEサポートがあります。 +- Scala 3の[トップレベル定義][toplevel]を利用することで、メソッドやフィールド、その他の定義をどこにでも記述できます。 + - 一方で、Pythonはメソッドを1つも宣言しなくても動作できますが、Scala 3ではトップレベルですべてを実現することはできません。たとえば、Scalaアプリケーションを開始するには[mainメソッド][main-method](`@main def`)が必要です。 + +### プログラミングレベルでの違い + +プログラミングレベルでも、コードを書く際に日常的に見られるいくつかの違いがあります: + +- Scalaでのプログラミングは非常に一貫性があります: + - フィールドやパラメータを定義する際に、`val`と`var`が一貫して使用されます + - リスト、マップ、セット、タプルはすべて同様に作成・アクセスされます。たとえば、他のScalaクラスを作成するのと同様に、すべてのタイプを作成するために括弧が使用されます---`List(1,2,3)`, `Set(1,2,3)`, `Map(1->"one")` + - [コレクションクラス][collections-classes] は一般的にほとんど同じ高階関数を持っています + - パターンマッチングは言語全体で一貫して使用されます + - メソッドに渡される関数を定義するために使用される構文は、匿名関数を定義するために使用される構文と同じです +- Scalaの変数やパラメータは`val`(不変)または `var`(可変)キーワードで定義されます +- 慣用的に、Scala では不変データ構造を使うことを良しとします。 +- コメント: Pythonはコメントに `#` を使用しますが、ScalaはC、C++、Javaスタイルの `//`、`/*...*/`、および `/**...*/` を使用します +- 命名規則: Pythonの標準は `my_list` のようにアンダースコアを使用しますが、Scalaは `myList` を使用します +- Scalaは静的型付けであるため、メソッドパラメータ、メソッドの戻り値、その他の場所で型を宣言します +- パターンマッチングと `match` 式はScalaで広範に使用されており、コードの書き方を変えるでしょう +- トレイト(Traits): Scalaではトレイトが多用され、Pythonではインターフェースや抽象クラスがあまり使用されません +- Scalaの[コンテキスト抽象][contextual]と _型推論_ は、さまざまな機能のコレクションを提供します: + - [拡張メソッド][extension-methods] により、明確な構文を使用してクラスに新しい機能を簡単に追加できます + - [多元的等価性][multiversal] により、コンパイル時に意味のある比較にのみ等価比較を制限できます +- Scalaには最先端のオープンソース関数型プログラミングライブラリがあります([“Awesome Scala”リスト](https://github.com/lauris/awesome-scala)を参照) +- オブジェクト、名前渡しパラメータ、中置表記、オプションの括弧、拡張メソッド、高階関数などの機能により、独自の「制御構造」やDSLを作成できます +- ScalaコードはJVM上で実行でき、[Scala Native](https://github.com/scala-native/scala-native)や[GraalVM](https://www.graalvm.org)を使用してネイティブイメージにコンパイルすることも可能で、高性能を実現します +- その他多くの機能:コンパニオンクラスとオブジェクト、マクロ、数値リテラル、複数のパラメータリスト、[交差型][intersection-types]、型レベルプログラミングなど + +### 機能の比較と例 + +この導入に基づき、以下のセクションではPythonとScalaのプログラミング言語機能を並べて比較します。 + +{% comment %} TODO: Pythonの例をスペース四つに更新します。開始しましたが、別のPRで行う方が良いと思いました。 {% endcomment %} + +## コメント + +Pythonはコメントに # を使用しますが、Scalaのコメント構文はC、C++、Javaなどの言語と同じです。 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code># a comment</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// a comment + <br>/* ... */ + <br>/** ... */</code> + </td> + </tr> + </tbody> +</table> + +## 変数の割り当て + +これらの例は、PythonとScalaで変数を作成する方法を示しています。 + +### 整数変数,文字列変数 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = 1 + <br>x = "Hi" + <br>y = """foo + <br>       bar + <br>       baz"""</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = 1 + <br>val x = "Hi" + <br>val y = """foo + <br>           bar + <br>           baz"""</code> + </td> + </tr> + </tbody> +</table> + +### リスト + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = [1,2,3]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = List(1,2,3)</code> + </td> + </tr> + </tbody> +</table> + +### 辞書/マップ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = { + <br>  "Toy Story": 8.3, + <br>  "Forrest Gump": 8.8, + <br>  "Cloud Atlas": 7.4 + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = Map( + <br>  "Toy Story" -> 8.3, + <br>  "Forrest Gump" -> 8.8, + <br>  "Cloud Atlas" -> 7.4 + <br>)</code> + </td> + </tr> + </tbody> +</table> + +### 集合 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = {1,2,3}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = Set(1,2,3)</code> + </td> + </tr> + </tbody> +</table> + +### タプル + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = (11, "Eleven")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = (11, "Eleven")</code> + </td> + </tr> + </tbody> +</table> + +Scalaのフィールドが可変になる場合は、変数定義に `val` の代わりに `var` を使います。 + +```scala +var x = 1 +x += 1 +``` + +しかし、Scalaの慣習として、特に変数を変更する必要がない限り、常に`val`を使います。 + +## 関数型プログラミングスタイルのレコード + +Scalaのケース・クラスはPythonのフローズン・データクラスに似ています。 + +### 構造体の定義 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>from dataclasses import dataclass, replace + <br> + <br>@dataclass(frozen=True) + <br>class Person: + <br>  name: str + <br>  age: int</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>case class Person(name: String, age: Int)</code> + </td> + </tr> + </tbody> +</table> + +### インスタンスを作成して使用する + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>p = Person("Alice", 42) + <br>p.name   # Alice + <br>p2 = replace(p, age=43)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val p = Person("Alice", 42) + <br>p.name   // Alice + <br>val p2 = p.copy(age = 43)</code> + </td> + </tr> + </tbody> +</table> + +## オブジェクト指向プログラミングスタイルのクラスとメソッド + +このセクションでは、オブジェクト指向プログラミングスタイルのクラスとメソッドに関する機能の比較を行います。 + +### クラスとプライマリーコンストラクタ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>class Person(object): + <br>  def __init__(self, name): + <br>    self.name = name + <br> + <br>  def speak(self): + <br>    print(f'Hello, my name is {self.name}')</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class Person (var name: String): + <br>  def speak() = println(s"Hello, my name is $name")</code> + </td> + </tr> + </tbody> +</table> + +### インスタンスを作成して使用する + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>p = Person("John") + <br>p.name   # John + <br>p.name = 'Fred' + <br>p.name   # Fred + <br>p.speak()</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val p = Person("John") + <br>p.name   // John + <br>p.name = "Fred" + <br>p.name   // Fred + <br>p.speak()</code> + </td> + </tr> + </tbody> +</table> + +### 1行メソッド + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>def add(a, b): return a + b</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def add(a: Int, b: Int): Int = a + b</code> + </td> + </tr> + </tbody> +</table> + +### 複数行のメソッド + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>def walkThenRun(): + <br>  print('walk') + <br>  print('run')</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def walkThenRun() = + <br>  println("walk") + <br>  println("run")</code> + </td> + </tr> + </tbody> +</table> + +## インターフェース、トレイト、継承 + +Java 8以降に慣れていれば、ScalaのtraitはJavaのインターフェースに良く似ていることに気づくと思います。 +Pythonのインターフェース(プロトコル)や抽象クラスがあまり使われないのに対して、Scalaではトレイトが常に使われています。 +したがって、この例では両者を比較するのではなく、Scalaのトレイトを使って数学のちょっとした問題を解く方法を紹介します: + +```scala +trait Adder: + def add(a: Int, b: Int) = a + b + +trait Multiplier: + def multiply(a: Int, b: Int) = a * b + +// create a class from the traits +class SimpleMath extends Adder, Multiplier +val sm = new SimpleMath +sm.add(1,1) // 2 +sm.multiply(2,2) // 4 +``` + +クラスやオブジェクトでtraitを使う方法は他にも[たくさんあります][modeling-intro]。 +しかし、これは概念を論理的な動作のグループに整理して、完全な解答を作成するために必要に応じてそれらを統合するために、どのように使うことができるかのちょっとしたアイデアを与えてくれます。 + +## 制御構文 + +ここではPythonとScalaの[制御構文][control-structures]を比較します。 +どちらの言語にも `if`/`else`, `while`, `for` ループ、 `try` といった構文があります。 +加えて、Scala には `match` 式があります。 + +### `if` 文, 1行 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>if x == 1: print(x)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x == 1 then println(x)</code> + </td> + </tr> + </tbody> +</table> + +### `if` 文, 複数行 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>if x == 1: + <br>  print("x is 1, as you can see:") + <br>  print(x)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x == 1 then + <br>  println("x is 1, as you can see:") + <br>  println(x)</code> + </td> + </tr> + </tbody> +</table> + +### if, else if, else: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>if x < 0: + <br>  print("negative") + <br>elif x == 0: + <br>  print("zero") + <br>else: + <br>  print("positive")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x < 0 then + <br>  println("negative") + <br>else if x == 0 then + <br>  println("zero") + <br>else + <br>  println("positive")</code> + </td> + </tr> + </tbody> +</table> + +### `if` 文からの戻り値 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>min_val = a if a < b else b</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val minValue = if a < b then a else b</code> + </td> + </tr> + </tbody> +</table> + +### メソッドの本体としての`if` + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>def min(a, b): + <br>  return a if a < b else b</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def min(a: Int, b: Int): Int = + <br>  if a < b then a else b</code> + </td> + </tr> + </tbody> +</table> + +### `while` ループ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>i = 1 + <br>while i < 3: + <br>  print(i) + <br>  i += 1</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>var i = 1 + <br>while i < 3 do + <br>  println(i) + <br>  i += 1</code> + </td> + </tr> + </tbody> +</table> + +### rangeを指定した`for` ループ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in range(0,3): + <br>  print(i)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// preferred + <br>for i <- 0 until 3 do println(i) + <br> + <br>// also available + <br>for (i <- 0 until 3) println(i) + <br> + <br>// multiline syntax + <br>for + <br>  i <- 0 until 3 + <br>do + <br>  println(i)</code> + </td> + </tr> + </tbody> +</table> + +### リスト範囲内の`for` ループ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in ints: print(i) + <br> + <br>for i in ints: + <br>  print(i)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for i <- ints do println(i)</code> + </td> + </tr> + </tbody> +</table> + +### 複数行での`for` ループ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in ints: + <br>  x = i * 2 + <br>  print(f"i = {i}, x = {x}")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- ints + <br>do + <br>  val x = i * 2 + <br>  println(s"i = $i, x = $x")</code> + </td> + </tr> + </tbody> +</table> + +### 複数の “range” ジェネレータ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in range(1,3): + <br>  for j in range(4,6): + <br>    for k in range(1,10,3): + <br>      print(f"i = {i}, j = {j}, k = {k}")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- 1 to 2 + <br>  j <- 4 to 5 + <br>  k <- 1 until 10 by 3 + <br>do + <br>  println(s"i = $i, j = $j, k = $k")</code> + </td> + </tr> + </tbody> +</table> + +### ガード付きジェネレータ (`if` 式) + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in range(1,11): + <br>  if i % 2 == 0: + <br>    if i < 5: + <br>      print(i)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- 1 to 10 + <br>  if i % 2 == 0 + <br>  if i < 5 + <br>do + <br>  println(i)</code> + </td> + </tr> + </tbody> +</table> + +### 行ごとに複数の`if`条件 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in range(1,11): + <br>  if i % 2 == 0 and i < 5: + <br>    print(i)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- 1 to 10 + <br>  if i % 2 == 0 && i < 5 + <br>do + <br>  println(i)</code> + </td> + </tr> + </tbody> +</table> + +### 内包表記 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>xs = [i * 10 for i in range(1, 4)] + <br># xs: [10,20,30]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val xs = for i <- 1 to 3 yield i * 10 + <br>// xs: Vector(10, 20, 30)</code> + </td> + </tr> + </tbody> +</table> + +### `match` 条件式 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code># From 3.10, Python supports structural pattern matching + <br># You can also use dictionaries for basic “switch” functionality + <br>match month: + <br>  case 1: + <br>    monthAsString = "January" + <br>  case 2: + <br>    monthAsString = "February" + <br>  case _: + <br>    monthAsString = "Other"</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val monthAsString = month match + <br>  case 1 => "January" + <br>  case 2 => "February" + <br>  _ => "Other"</code> + </td> + </tr> + </tbody> +</table> + +### switch/match + +<table> + <tbody> + <tr> + <td class="python-block"> + <code># Only from Python 3.10 + <br>match i: + <br>  case 1 | 3 | 5 | 7 | 9: + <br>    numAsString = "odd" + <br>  case 2 | 4 | 6 | 8 | 10: + <br>    numAsString = "even" + <br>  case _: + <br>    numAsString = "too big"</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val numAsString = i match + <br>  case 1 | 3 | 5 | 7 | 9 => "odd" + <br>  case 2 | 4 | 6 | 8 | 10 => "even" + <br>  case _ => "too big"</code> + </td> + </tr> + </tbody> +</table> + +### try, catch, finally + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>try: + <br>  print(a) + <br>except NameError: + <br>  print("NameError") + <br>except: + <br>  print("Other") + <br>finally: + <br>  print("Finally")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>try + <br>  writeTextToFile(text) + <br>catch + <br>  case ioe: IOException => + <br>    println(ioe.getMessage) + <br>  case fnf: FileNotFoundException => + <br>    println(fnf.getMessage) + <br>finally + <br>  println("Finally")</code> + </td> + </tr> + </tbody> +</table> + +マッチ式とパターンマッチは、Scalaプログラミングの大きな要素ですが、ここで紹介しているのは、マッチ式の機能の一部だけです。より多くの例については、[制御構造][control-structures]のページをご覧ください。 + +## コレクションクラス + +このセクションでは、PythonとScalaで利用可能なコレクションクラス[collections classes][collections-classes]を比較します。リスト、辞書/マップ、セット、タプルなどです。 + +### リスト + +Pythonにはリストがあるように、Scalaにはニーズに応じて、可変および不可変な列(Seq)のクラスがいくつか用意されています。 +Pythonのリストは変更可能であるため、Scalaの `ArrayBuffer` によく似ています。 + +### Pythonリスト & Scalaの列(Seq) + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>a = [1,2,3]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// use different sequence classes + <br>// as needed + <br>val a = List(1,2,3) + <br>val a = Vector(1,2,3) + <br>val a = ArrayBuffer(1,2,3)</code> + </td> + </tr> + </tbody> +</table> + +### リストの要素へのアクセス + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>a[0]<br>a[1]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>a(0)<br>a(1)</code> // just like all other method calls + </td> + </tr> + </tbody> +</table> + +### リストの要素の更新 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>a[0] = 10 + <br>a[1] = 20</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// ArrayBuffer is mutable + <br>a(0) = 10 + <br>a(1) = 20</code> + </td> + </tr> + </tbody> +</table> + +### 2つのリストの結合 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>c = a + b</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val c = a ++ b</code> + </td> + </tr> + </tbody> +</table> + +### リストの反復処理 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in ints: print(i) + <br> + <br>for i in ints: + <br>  print(i)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// preferred + <br>for i <- ints do println(i) + <br> + <br>// also available + <br>for (i <- ints) println(i)</code> + </td> + </tr> + </tbody> +</table> + +Scala の主な列(Seq)クラスは `List`、`Vector`、`ArrayBuffer` です。 +`List` と `Vector` は不変な列が必要なときに使うメインクラスで、 `ArrayBuffer` は可変な列が必要なときに使うメインクラスです。 +(Scala における 「バッファ」 とは、大きくなったり小さくなったりする配列のことです。) + +### 辞書/マップ + +Python の辞書はScala の `Map` クラスのようなものです。 +しかし、Scala のデフォルトのマップは _immutable_ であり、新しいマップを簡単に作成するための変換メソッドを持っています。 + +#### 辞書/マップ の作成 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>my_dict = { + <br>  'a': 1, + <br>  'b': 2, + <br>  'c': 3 + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val myMap = Map( + <br>  "a" -> 1, + <br>  "b" -> 2, + <br>  "c" -> 3 + <br>)</code> + </td> + </tr> + </tbody> +</table> + +#### 辞書/マップの要素へのアクセス + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>my_dict['a']   # 1</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>myMap("a")   // 1</code> + </td> + </tr> + </tbody> +</table> + +#### `for` ループでの辞書/マップ + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for key, value in my_dict.items(): + <br>  print(key) + <br>  print(value)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for (key,value) <- myMap do + <br>  println(key) + <br>  println(value)</code> + </td> + </tr> + </tbody> +</table> + +Scalaには、さまざまなニーズに対応する他の専門的な `Map` クラスがあります。 + +### 集合 + +Pythonの集合は、_mutable_ Scalaの`Set`クラスに似ています。 + +#### 集合の作成 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>set = {"a", "b", "c"}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val set = Set(1,2,3)</code> + </td> + </tr> + </tbody> +</table> + +#### 重複する要素 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>set = {1,2,1} + <br># set: {1,2}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val set = Set(1,2,1) + <br>// set: Set(1,2)</code> + </td> + </tr> + </tbody> +</table> + +Scalaには、他にも様々なニーズに特化した`Set`クラスがあります。 + +### タプル + +PythonとScalaのタプルも似ています。 + +#### タプルの作成 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>t = (11, 11.0, "Eleven")</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val t = (11, 11.0, "Eleven")</code> + </td> + </tr> + </tbody> +</table> + +#### タプルの要素へのアクセス + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>t[0]   # 11 + <br>t[1]   # 11.0</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>t(0)   // 11 + <br>t(1)   // 11.0</code> + </td> + </tr> + </tbody> +</table> + +## コレクションクラスでのメソッド + +PythonとScalaには、同じ関数型メソッドがいくつかあります。 + +- `map` +- `filter` +- `reduce` + +Pythonのラムダ式でこれらのメソッドを使うのに慣れていれば、Scalaがコレクション・クラスのメソッドで同じようなアプローチを持っていることがわかると思います。 +この機能を実証するために、ここに2つのサンプルリストを示します。 + +```scala +numbers = [1,2,3] // python +val numbers = List(1,2,3) // scala +``` + +これらのリストは以下の表で使用され、マップ処理とフィルター処理のアルゴリズムを適用する方法を示しています。 + +### マップ処理の内包表記 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = [i * 10 for i in numbers]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = for i <- numbers yield i * 10</code> + </td> + </tr> + </tbody> +</table> + +### フィルター処理の内包表記 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>evens = [i for i in numbers if i % 2 == 0]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val evens = numbers.filter(_ % 2 == 0) + <br>// or + <br>val evens = for i <- numbers if i % 2 == 0 yield i</code> + </td> + </tr> + </tbody> +</table> + +### マップ、フィルター処理の内包表記 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = [i * 10 for i in numbers if i % 2 == 0]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = numbers.filter(_ % 2 == 0).map(_ * 10) + <br>// or + <br>val x = for i <- numbers if i % 2 == 0 yield i * 10</code> + </td> + </tr> + </tbody> +</table> + +### マップ処理 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = map(lambda x: x * 10, numbers)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = numbers.map(_ * 10)</code> + </td> + </tr> + </tbody> +</table> + +### フィルター処理 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>f = lambda x: x > 1 + <br>x = filter(f, numbers)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = numbers.filter(_ > 1)</code> + </td> + </tr> + </tbody> +</table> + + +### Scalaのコレクションメソッド + +Scalaのコレクションクラスには100以上の関数メソッドがあり、コードを簡単にすることができます。 +Python では、これらの関数の一部は `itertools` モジュールで利用できます。 +`map`、`filter`、`reduce` に加えて、Scala でよく使われるメソッドを以下に示します。 +これらのメソッドの例では + +- `c` はコレクションです。 +- `p` は述語です。 +- `f` は関数、無名関数、またはメソッドです。 +- `n` は整数値です。 + +これらは利用可能なフィルタリング方法の一部です。 + +|メソッド|説明| +| -------------- | ------------- | +| `c1.diff(c2)` | `c1` と `c2` の要素の差分を返します。| +| `c.distinct` | `c` の重複しない要素を返します。| +| `c.drop(n)` | 最初の `n` 要素を除くコレクションのすべての要素を返します。| +| `c.filter(p)` | コレクションから、その述語が `true` となるすべての要素を返します。| +| `c.head` | コレクションの最初の要素を返します。 (コレクションが空の場合は `NoSuchElementException` をスローします。)| +| `c.tail` | コレクションから最初の要素を除くすべての要素を返します。 (コレクションが空の場合は `UnsupportedOperationException` をスローします。)| +| `c.take(n)` | コレクション `c` の最初の `n` 個の要素を返します。 +以下に、いくつかのトランスフォーマメソッドを示します。| +|メソッド| 説明 | +| --------------- | ------------- +| `c.flatten` | コレクションのコレクション(リストのリストなど)を単一のコレクション(単一のリスト)に変換します。| +| `c.flatMap(f)` | コレクション `c` のすべての要素に `f` を適用し(`map` のような動作)、その結果のコレクションの要素を平坦化して、新しいコレクションを返します。| +| `c.map(f)` | コレクション `c` のすべての要素に `f` を適用して、新しいコレクションを作成します。| +| `c.reduce(f)` | 「リダクション」関数 `f` を `c` の連続する要素に適用し、単一の値を生成します。| +| `c.sortWith(f)` | 比較関数 `f` によってソートされた `c` のバージョンを返します。| +よく使われるグループ化メソッド: +| メソッド | 説明 | +| ---------------- | ------------- +| `c.groupBy(f)` | コレクションを `f` に従って分割し、コレクションの `Map` を作成します。| +| `c.partition(p)` | 述語 `p` に従って2つのコレクションを返します。| +| `c.span(p)` | 2つのコレクションからなるコレクションを返します。1つ目は `c.takeWhile(p)` によって作成され、2つ目は `c.dropWhile(p)` によって作成されます。| +| `c.splitAt(n)` | コレクション `c` を要素 `n` で分割して、2つのコレクションからなるコレクションを返します。| +情報および数学的なメソッド: +| メソッド | 説明 | +| -------------- | ------------- | +| `c1.containsSlice(c2)` | `c1` がシーケンス `c2` を含む場合に `true` を返します。| +| `c.count(p)` | `p` が `true` である `c` の要素数を数えます。| +| `c.distinct` | `c` の一意な要素を返します。| +| `c.exists(p)` | コレクション内のいずれかの要素に対して `p` が `true` であれば `true` を返します。| +| `c.find(p)` | `p` に一致する最初の要素を返します。 要素は `Option[A]` として返されます。| +| `c.min` | コレクションから最小の要素を返します。 (_java.lang.UnsupportedOperationException_例外が発生する場合があります。)| +| `c.max` | コレクションから最大の要素を返します。 (_java.lang.UnsupportedOperationException_例外が発生する場合があります。)| +| `c slice(from, to)` | 要素 `from` から始まり、要素 `to` で終わる要素の範囲を返します。| +| `c.sum` | コレクション内のすべての要素の合計を返しますw。 (コレクション内の要素に対して `Ordering` を定義する必要があります。)| + +以下に、これらのメソッドがリスト上でどのように機能するかを説明する例をいくつか示します。 + +```scala +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.filter(_ > 100) // List() +a.find(_ > 20) // Some(30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +a.takeWhile(_ < 30) // List(10, 20) +``` + +これらのメソッドは、Scalaにおける共通のパターンを示しています。オブジェクト上で利用可能な機能メソッドです。 +これらの方法はいずれも、初期リスト `a` を変更しません。代わりに、コメントの後に示されているデータをすべて返します。 +利用可能なメソッドは他にもたくさんありますが、これらの説明と例が、組み込みのコレクションメソッドの持つ力を実感する一助となれば幸いです。 + +## 列挙 + +このセクションでは、PythonとScala 3の列挙型を比較します。 + +### 列挙型の作成 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>from enum import Enum, auto + <br>class Color(Enum): + <br>    RED = auto() + <br>    GREEN = auto() + <br>    BLUE = auto()</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>enum Color: + <br>  case Red, Green, Blue</code> + </td> + </tr> + </tbody> +</table> + +### 値とその比較 + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>Color.RED == Color.BLUE  # False</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>Color.Red == Color.Blue  // false</code> + </td> + </tr> + </tbody> +</table> + +### パラメータ化された列挙型 + +<table> + <tbody> + <tr> + <td class="python-block"> + N/A + </td> + </tr> + <tr> + <td class="scala-block"> + <code>enum Color(val rgb: Int): + <br>  case Red   extends Color(0xFF0000) + <br>  case Green extends Color(0x00FF00) + <br>  case Blue  extends Color(0x0000FF)</code> + </td> + </tr> + </tbody> +</table> + +### ユーザー定義による列挙メンバー + +<table> + <tbody> + <tr> + <td class="python-block"> + N/A + </td> + </tr> + <tr> + <td class="scala-block"> + <code>enum Planet( + <br>    mass: Double, + <br>    radius: Double + <br>  ): + <br>  case Mercury extends + <br>    Planet(3.303e+23, 2.4397e6) + <br>  case Venus extends + <br>    Planet(4.869e+24, 6.0518e6) + <br>  case Earth extends + <br>    Planet(5.976e+24, 6.37814e6) + <br>  // more planets ... + <br> + <br>  // fields and methods + <br>  private final val G = 6.67300E-11 + <br>  def surfaceGravity = G * mass / + <br>    (radius * radius) + <br>  def surfaceWeight(otherMass: Double) + <br>    = otherMass * surfaceGravity</code> + </td> + </tr> + </tbody> +</table> + +## Scala 独自の概念 + +Scalaには、Pythonには現在同等の機能がない概念が他にもあります。 +詳細は以下のリンクを参照してください。 +- 拡張メソッド(extension methods)、型クラス(type classes)、暗黙的値(implicit values)など、文脈依存の抽象化(contextual abstractions)に関連するほとんどの概念 +- Scalaでは複数のパラメータリストを使用できるため、部分適用関数などの機能や独自のDSLを作成することが可能 +- 独自の制御構造や DSL を作成できる機能 +- [多様等価][多様等価]: どの等価比較が意味を持つかをコンパイル時に制御できる機能 +- インフィックスメソッド +- マクロ + +## Scala と仮想環境 + +Scalaでは、Pythonの仮想環境に相当するものを明示的に設定する必要はありません。デフォルトでは、Scalaのビルドツールがプロジェクトの依存関係を管理するため、ユーザーは手動でパッケージをインストールする必要がありません。例えば、`sbt`ビルドツールを使用する場合、`build.sbt`ファイルの`libraryDependencies`設定で依存関係を指定し、 + +``` +cd myapp +sbt compile +``` + +以上のコマンドを実行することで、その特定のプロジェクトに必要なすべての依存関係が自動的に解決されます。 +ダウンロードされた依存関係の場所は、主にビルドツールの実装の詳細であり、ユーザーはこれらのダウンロードされた依存関係と直接やりとりする必要はありません。 +例えば、sbtの依存関係キャッシュ全体を削除した場合、プロジェクトの次のコンパイル時には、sbtが自動的に必要な依存関係をすべて解決し、ダウンロードし直します。 +これはPythonとは異なります。Pythonではデフォルトで依存関係がシステム全体またはユーザー全体のディレクトリにインストールされるため、プロジェクトごとに独立した環境を取得するには、対応する仮想環境を作成する必要があります。 +例えば、`venv`モジュールを使用して、特定のプロジェクト用に次のように仮想環境を作成できます。 + +``` +cd myapp +python3 -m venv myapp-env +source myapp-env/bin/activate +pip install -r requirements.txt +``` + +これにより、プロジェクトの `myapp/myapp-env` ディレクトリにすべての依存関係がインストールされ、シェル環境変数 `PATH` が変更されて、依存関係が `myapp-env` から参照されるようになります。 +Scalaでは、このような手動での作業は一切必要ありません。 + +[collections-classes]: {% link _overviews/scala3-book/collections-classes.md %} +[concurrency]: {% link _overviews/scala3-book/concurrency.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control-structures]: {% link _overviews/scala3-book/control-structures.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[intersection-types]: {% link _overviews/scala3-book/types-intersection.md %} +[main-method]: {% link _overviews/scala3-book/methods-main-methods.md %} +[modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %} +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} +[union-types]: {% link _overviews/scala3-book/types-union.md %} +</div> diff --git a/_ja/overviews/scala3-book/scala4x.css b/_ja/overviews/scala3-book/scala4x.css new file mode 100644 index 0000000000..1772c03ac8 --- /dev/null +++ b/_ja/overviews/scala3-book/scala4x.css @@ -0,0 +1,54 @@ +<style> +.content-primary .scala3-comparison-page td code +{ + margin: 0; + padding: 0; + background: none; +} +.content-primary .scala3-comparison-page table +{ + background: #fdfdf7; + border-collapse: collapse; +} + +.content-primary .scala3-comparison-page table td.python-block, +.content-primary .scala3-comparison-page table td.java-block, +.content-primary .scala3-comparison-page table td.javascript-block, +.content-primary .scala3-comparison-page table td.scala-block +{ + position: relative; + padding-bottom: 20px; +} +.content-primary .scala3-comparison-page table td.python-block:after, +.content-primary .scala3-comparison-page table td.java-block:after, +.content-primary .scala3-comparison-page table td.javascript-block:after, +.content-primary .scala3-comparison-page table td.scala-block:after +{ + color: #ccc; + display: block; + font-size: 17px; + line-height: 1; + position: absolute; + top: 8px; + right: 2px; + z-index: 1; +} +.content-primary .scala3-comparison-page table td.python-block:after { + content: "python"; +} +.content-primary .scala3-comparison-page table td.java-block:after { + content: "java"; +} +.content-primary .scala3-comparison-page table td.javascript-block:after { + content: "javascript"; +} +.content-primary .scala3-comparison-page table td.scala-block:after { + content: "scala"; +} +.content-primary .scala3-comparison-page h4 { + font-weight: normal; + font-variant-caps: all-small-caps; +} +</style> + + diff --git a/_ja/scala3/contribute-to-docs.md b/_ja/scala3/contribute-to-docs.md index c13b4b0904..90b123e08f 100644 --- a/_ja/scala3/contribute-to-docs.md +++ b/_ja/scala3/contribute-to-docs.md @@ -1,5 +1,5 @@ --- -layout: inner-page-documentation +layout: singlepage-overview overview-name: "Scala 3 Documentation" title: Contributing to the Docs language: ja @@ -53,10 +53,10 @@ Scala 3 コンパイラとライブラリへの貢献と内部に関する包括 - [Issues](https://github.com/scala/docs.scala-lang/issues) ## Scala 3 Language Reference -The [Dotty reference](/scala3/reference/overview.html) は Scala 3 になる予定である。これにはさまざまな言語仕様に関する公式のプレゼンテーションや技術的情報が含まれている。 +The [Dotty reference]({{ site.scala3ref }}/overview.html) は Scala 3 になる予定である。これにはさまざまな言語仕様に関する公式のプレゼンテーションや技術的情報が含まれている。 -- [Sources](https://github.com/lampepfl/dotty/tree/master/docs/docs/reference) -- [Issues](https://github.com/lampepfl/dotty/issues) +- [Sources](https://github.com/scala/scala3/tree/main/docs/_docs) +- [Issues](https://github.com/scala/scala3/issues) [scala3-book]: {% link _overviews/scala3-book/introduction.md %} diff --git a/_ja/scala3/getting-started.md b/_ja/scala3/getting-started.md deleted file mode 100644 index 120647c4c2..0000000000 --- a/_ja/scala3/getting-started.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: singlepage-overview -title: Getting Started with Scala 3 -scala3: true -language: ja ---- - -{% include _ja/getting-started.md %} diff --git a/_ja/scala3/guides.md b/_ja/scala3/guides.md deleted file mode 100644 index 9f5d0964fe..0000000000 --- a/_ja/scala3/guides.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: inner-page-parent -title: Scala 3 のガイド -language: ja -scala3: true - - -guides: - - title: "Scala 2 から Scala 3 への移行" - icon: suitcase - url: "/scala3/guides/migration/compatibility-intro.html" - description: "Scala 3 との互換性と移行について知っておくべきことすべて" - - title: マクロ - by: Nicolas Stucki - icon: magic - url: "/scala3/guides/macros" - description: "Scala 3 のマクロの書き方に関係する全ての機能をカバーする詳しいチュートリアル" - label-text: feature - - title: TASTyの概要 - by: Alvin Alexander - icon: birthday-cake - url: "/scala3/guides/tasty-overview.html" - description: "Scala のエンドユーザー向けの TASTy のフォーマットの概要" ---- - -<section class="full-width"> - <div class="wrap"> - <div class="content-primary overviews"> - <div class="inner-box toc-context"> - <h2>概要とガイド</h2> - <p> - Scala 3 とその機能についての詳しいガイド - </p> -{% include scala3-guides-card-group.html %} - </div> - </div> - </div> -</section> - - diff --git a/_ja/scala3/index.md b/_ja/scala3/index.md index 8ad7fbabf2..31d285d6a5 100644 --- a/_ja/scala3/index.md +++ b/_ja/scala3/index.md @@ -1,5 +1,5 @@ --- -layout: inner-page-documentation +layout: landing-page title: Documentation for Scala 3 language: ja namespace: root @@ -40,5 +40,5 @@ sections: - title: "Language Reference" description: "Scala 3 の言語仕様" icon: "fa fa-book" - link: /scala3/reference/overview.html ---- \ No newline at end of file + link: https://docs.scala-lang.org/scala3/reference +--- diff --git a/_ja/scala3/new-in-scala3.md b/_ja/scala3/new-in-scala3.md index 8e69859268..6022519757 100644 --- a/_ja/scala3/new-in-scala3.md +++ b/_ja/scala3/new-in-scala3.md @@ -102,7 +102,7 @@ Scala 3 のメタプログラミングについてもっと知りたいかたは [migration]: {% link _overviews/scala3-migration/compatibility-intro.md %} [contribution]: {% link _overviews/scala3-contribution/contribution-intro.md %} -[implicits]: {{ site.scala3ref }}/contextual.html +[implicits]: {{ site.scala3ref }}/contextual [contextual-using]: {{ site.scala3ref }}/contextual/using-clauses.html [contextual-givens]: {{ site.scala3ref }}/contextual/givens.html [contextual-extension]: {{ site.scala3ref }}/contextual/extension-methods.html @@ -120,7 +120,7 @@ Scala 3 のメタプログラミングについてもっと知りたいかたは [meta-quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} [meta-reflection]: {% link _overviews/scala3-macros/tutorial/reflection.md %} -[oo-explicit-null]: {{ site.scala3ref }}/other-new-features/explicit-nulls.html +[oo-explicit-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html [oo-safe-init]: {{ site.scala3ref }}/other-new-features/safe-initialization.html [oo-trait-parameters]: {{ site.scala3ref }}/other-new-features/trait-parameters.html [oo-open]: {{ site.scala3ref }}/other-new-features/open-classes.html diff --git a/_ja/tour/abstract-type-members.md b/_ja/tour/abstract-type-members.md index 2f480833bf..af08eb5c2b 100644 --- a/_ja/tour/abstract-type-members.md +++ b/_ja/tour/abstract-type-members.md @@ -2,16 +2,12 @@ layout: tour title: 抽象型メンバー language: ja - -discourse: true - partof: scala-tour num: 23 next-page: compound-types previous-page: inner-classes topics: abstract type members prerequisite-knowledge: variance, upper-type-bound - --- トレイトや抽象クラスのような抽象型は抽象型メンバーを持つことができます。 diff --git a/_ja/tour/annotations.md b/_ja/tour/annotations.md index d0fad2e82a..90545140fc 100644 --- a/_ja/tour/annotations.md +++ b/_ja/tour/annotations.md @@ -2,15 +2,10 @@ layout: tour title: アノテーション language: ja - -discourse: true - partof: scala-tour - num: 32 next-page: packages-and-imports previous-page: by-name-parameters - --- アノテーションはメタ情報と定義を関連づけます。例えば、メソッドの前のアノテーション`@deprecated`はメソッドが使われたらコンパイラに警告を出力させます。 diff --git a/_ja/tour/automatic-closures.md b/_ja/tour/automatic-closures.md deleted file mode 100644 index 3b38e98178..0000000000 --- a/_ja/tour/automatic-closures.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: tour -title: 型依存クロージャの自動構築 -language: ja - -discourse: true - -partof: scala-tour ---- - -Scalaはメソッドのパラメータとしてパラメータ無しの関数名を渡せます。そのようなメソッドが呼ばれると、パラメータ無しの関数名は実際に評価されず、代わりに、対応するパラメーターの処理をカプセル化した、引数無しの関数が渡されます(いわゆる *名前渡し*評価です)。 - -以下のコードはこの仕組みを説明しています。 - - object TargetTest1 extends Application { - def whileLoop(cond: => Boolean)(body: => Unit): Unit = - if (cond) { - body - whileLoop(cond)(body) - } - var i = 10 - whileLoop (i > 0) { - println(i) - i -= 1 - } - } - -関数 whileLoop は2つのパラメータ`cond`と`body`を受け取ります。関数が適用される時、実際のパラメータは評価されません。しかし形式上のパラメータが`whileLoop`の本体内で使われる度に、暗黙に生成された引数の無い関数が代わりに評価されます。このようにメソッド`whileLoop`はJavaのようなwhileループを再帰的な方法で実装しています。 - -[中置/後置 演算子](operators.html)とこのメカニズムを組み合わせて利用し、より複雑な命令文を(より良い構文で)作れます。 - -こちらがloop-unless式の実装です。 - - object TargetTest2 extends Application { - 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/_ja/tour/basics.md b/_ja/tour/basics.md index b2b06317bb..1f109c930d 100644 --- a/_ja/tour/basics.md +++ b/_ja/tour/basics.md @@ -2,32 +2,24 @@ layout: tour title: 基本 language: ja - -discourse: true - partof: scala-tour - num: 2 next-page: unified-types previous-page: tour-of-scala - --- このページでは、Scalaの基本を取り扱います。 ## Scalaをブラウザで試してみる -ScalaFiddleを利用することでブラウザ上でScalaを実行することができます。 +Scastieを利用することでブラウザ上でScalaを実行することができます。 -1. [https://scalafiddle.io](https://scalafiddle.io)を開きます。 +1. [Scastie](https://scastie.scala-lang.org/)を開きます。 2. 左側のパネルに`println("Hello, world!")`を貼り付けます。 3. "Run"ボタンを押すと、右側のパネルに出力が表示されます。 このサイトを使えば、簡単にセットアップせずScalaのコードの一部を試すことができます。 -このドキュメントの多くのコードの例はScalaFiddleで開発されています。 -そのため、サンプルコード内のRunボタンをクリックするだけで、そのまま簡単にコードを試すことができます。 - ## 式 式は計算可能な文です。 @@ -37,14 +29,12 @@ ScalaFiddleを利用することでブラウザ上でScalaを実行すること ``` `println`を使うことで、式の結果を出力できます。 -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ### 値 @@ -70,7 +60,7 @@ x = 3 // この記述はコンパイルされません。 val x: Int = 1 + 1 ``` -型定義では`Int` は識別子`x`の後にくることに注意してください。そして`:`も必要となります。 +型定義では`Int` は識別子`x`の後にくることに注意してください。そして`:`も必要となります。 ### 変数 @@ -115,21 +105,17 @@ println({ 関数には名前をつけることもできます。 -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} 関数は複数のパラメーターをとることもできます。 -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} またパラメーターを取らないこともありえます。 @@ -144,23 +130,19 @@ println(getTheAnswer()) // 42 メソッドは `def` キーワードで定義されます。 `def` の後ろには名前、パラメーターリスト、戻り値の型、処理の内容が続きます。 -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} 戻り値の型は引数リストとコロンの「後ろ」に宣言することに注意してください。`: Int` メソッドは複数のパラメーターリストを受け取ることができます。 -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} また、パラメーターリストを一切受け取らないこともあります。 @@ -172,7 +154,6 @@ println("Hello, " + name + "!") メソッドは複数行の式も持つことができます。 -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -180,7 +161,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} メソッド本体にある最後の式はメソッドの戻り値になります。(Scalaには`return`キーワードはありますが、めったに使われません。) @@ -227,15 +207,15 @@ val yetAnotherPoint = Point(2, 2) ```scala mdoc if (point == anotherPoint) { - println(point + " と " + anotherPoint + " は同じです。") + println(s"$point と $anotherPoint は同じです。") } else { - println(point + " と " + anotherPoint + " は異なります。") + println(s"$point と $anotherPoint は異なります。") } // Point(1,2) と Point(1,2) は同じです。 if (point == yetAnotherPoint) { - println(point + " と " + yetAnotherPoint + " は同じです。") + println(s"$point と $yetAnotherPoint は同じです。") } else { - println(point + " と " + yetAnotherPoint + " は異なります。") + println(s"$point と $yetAnotherPoint は異なります。") } // Point(1,2) と Point(2,2) は異なります。 ``` @@ -283,7 +263,6 @@ trait Greeter { トレイトはデフォルトの実装を持つこともできます。 -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -308,7 +287,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} ここでは、`DefaultGreeter`は一つのトレイトだけを継承していますが、複数のトレイトを継承することもできます。 diff --git a/_ja/tour/by-name-parameters.md b/_ja/tour/by-name-parameters.md index c3494f5634..b88f6dd617 100644 --- a/_ja/tour/by-name-parameters.md +++ b/_ja/tour/by-name-parameters.md @@ -2,15 +2,10 @@ layout: tour title: 名前渡しパラメータ language: ja - -discourse: true - partof: scala-tour - num: 31 next-page: annotations previous-page: operators - --- *名前渡しのパラメータ*は使用された時に評価されます。それらは*値渡しパラメータ*とは対照的です。名前渡しのパラメータを作るには、単純に`=>`を型の前につけます。 diff --git a/_ja/tour/case-classes.md b/_ja/tour/case-classes.md index 65cce59c9b..2015b843c1 100644 --- a/_ja/tour/case-classes.md +++ b/_ja/tour/case-classes.md @@ -2,16 +2,11 @@ layout: tour title: ケースクラス language: ja - -discourse: true - partof: scala-tour - num: 11 next-page: pattern-matching previous-page: multiple-parameter-lists prerequisite-knowledge: classes, basics, mutability - --- ケースクラスはこれから論じるいくつかの差異はあるものの普通のクラスと似ています。 diff --git a/_ja/tour/classes.md b/_ja/tour/classes.md index e594b4ea37..74bd6c6997 100644 --- a/_ja/tour/classes.md +++ b/_ja/tour/classes.md @@ -2,17 +2,12 @@ layout: tour title: クラス language: ja - -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 - --- Scalaにおけるクラスはオブジェクトを作るための設計図です。 diff --git a/_ja/tour/compound-types.md b/_ja/tour/compound-types.md index 60be4af3c3..2137ecc432 100644 --- a/_ja/tour/compound-types.md +++ b/_ja/tour/compound-types.md @@ -2,15 +2,10 @@ layout: tour title: 複合型 language: ja - -discourse: true - partof: scala-tour - num: 24 next-page: self-types previous-page: abstract-type-members - --- ときどき、あるオブジェクトの型が、複数の他の型のサブタイプであると表現する必要が生じます。 Scalaでは、これは*複合型*を用いて表現できます。複合型とはオブジェクトの型同士を重ねることです。 diff --git a/_ja/tour/default-parameter-values.md b/_ja/tour/default-parameter-values.md index 1e03cebe50..499f543566 100644 --- a/_ja/tour/default-parameter-values.md +++ b/_ja/tour/default-parameter-values.md @@ -2,16 +2,11 @@ layout: tour title: デフォルト引数 language: ja - -discourse: true - partof: scala-tour - num: 33 next-page: named-arguments previous-page: annotations prerequisite-knowledge: named-arguments, function syntax - --- Scalaはパラメータのデフォルト値を与えることができ、呼び出す側はこれらのパラメータを省略できます。 diff --git a/_ja/tour/extractor-objects.md b/_ja/tour/extractor-objects.md index ba4603a3fa..03efef77e3 100644 --- a/_ja/tour/extractor-objects.md +++ b/_ja/tour/extractor-objects.md @@ -2,15 +2,10 @@ layout: tour title: 抽出子オブジェクト language: ja - -discourse: true - partof: scala-tour - num: 16 next-page: for-comprehensions previous-page: regular-expression-patterns - --- 抽出子オブジェクトは`unapply`メソッドを持つオブジェクトです。 @@ -22,7 +17,7 @@ import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") diff --git a/_ja/tour/for-comprehensions.md b/_ja/tour/for-comprehensions.md index c673135b2b..1600904350 100644 --- a/_ja/tour/for-comprehensions.md +++ b/_ja/tour/for-comprehensions.md @@ -2,15 +2,10 @@ layout: tour title: for内包表記 language: ja - -discourse: true - partof: scala-tour - num: 17 next-page: generic-classes previous-page: extractor-objects - --- Scalaは*シーケンス内包表記*を表現するための軽量な記法を提供します。 diff --git a/_ja/tour/generic-classes.md b/_ja/tour/generic-classes.md index 2de82cc5ce..ad944b8b39 100644 --- a/_ja/tour/generic-classes.md +++ b/_ja/tour/generic-classes.md @@ -2,16 +2,11 @@ layout: tour title: ジェネリッククラス language: ja - -discourse: true - partof: scala-tour - num: 18 next-page: variances previous-page: for-comprehensions assumed-knowledge: classes unified-types - --- ジェネリッククラスはパラメータとして型を1つ受け取るクラスです。それらはコレクションクラスで特に役立ちます。 diff --git a/_ja/tour/higher-order-functions.md b/_ja/tour/higher-order-functions.md index 1740d1ba3d..95c069dc4f 100644 --- a/_ja/tour/higher-order-functions.md +++ b/_ja/tour/higher-order-functions.md @@ -2,15 +2,10 @@ layout: tour title: 高階関数 language: ja - -discourse: true - partof: scala-tour - num: 8 next-page: nested-functions previous-page: mixin-class-composition - --- 高階関数は他の関数をパラメーターとして受け取る、もしくは結果として関数を返します。 @@ -45,7 +40,7 @@ Scalaコンパイラはパラメーターの型を(Intが1つだけと)既 唯一の注意点はパラメータ名の代わりに`_`を使う必要があるということです(先の例では`x`でした)。 ## メソッドを関数に強制変換 -高階関数には引数としてとしてメソッドを渡すことも可能で、それはScalaコンパイラがメソッドを関数に強制変換するからです。 +高階関数には引数としてメソッドを渡すことも可能で、それはScalaコンパイラがメソッドを関数に強制変換するからです。 ```scala mdoc case class WeeklyWeatherForecast(temperatures: Seq[Double]) { diff --git a/_ja/tour/implicit-conversions.md b/_ja/tour/implicit-conversions.md index 8ee5a23069..dbf1440872 100644 --- a/_ja/tour/implicit-conversions.md +++ b/_ja/tour/implicit-conversions.md @@ -2,15 +2,10 @@ layout: tour title: 暗黙の変換 language: ja - -discourse: true - partof: scala-tour - num: 27 next-page: polymorphic-methods previous-page: implicit-parameters - --- 型`S`から型`T`への暗黙の変換は`S => T`という型のimplicit値や、その型に一致するimplicitメソッドで定義されます。 @@ -47,8 +42,8 @@ implicit def list2ordered[A](x: List[A]) ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + Integer.valueOf(x) ``` 暗黙の変換は見境なく使われると落とし穴になり得るため、暗黙の変換の定義をコンパイルしている時にコンパイラは警告を出します。 diff --git a/_ja/tour/implicit-parameters.md b/_ja/tour/implicit-parameters.md index 30ff6028df..4160583a0f 100644 --- a/_ja/tour/implicit-parameters.md +++ b/_ja/tour/implicit-parameters.md @@ -2,15 +2,10 @@ layout: tour title: 暗黙のパラメータ language: ja - -discourse: true - partof: scala-tour - num: 26 next-page: implicit-conversions previous-page: self-types - --- メソッドは _暗黙の_ パラメータのリストを持つことができ、パラメータリストの先頭には _implicit_ キーワードで印をつけます。 @@ -21,7 +16,7 @@ Scalaがこれらのパラメータを探す場所は2つのカテゴリに分 * Scalaはまず最初に暗黙のパラメータブロックを持つメソッドが呼び出されている箇所で、直接(プレフィックスなしに)アクセスできる暗黙の定義と暗黙のパラメータを探します。 * 次に、候補となる型に関連づけられた全てのコンパニオンオブジェクトの中でimplicitと宣言されているメンバーを探します。 -Scalaがimplicitをどこから見つけるかについてのより詳しいガイドは[FAQ](//docs.scala-lang.org/tutorials/FAQ/finding-implicits.html)で見ることができます。 +Scalaがimplicitをどこから見つけるかについてのより詳しいガイドは[FAQ](/tutorials/FAQ/finding-implicits.html)で見ることができます。 以下の例では、モノイドの`add`と`unit`の演算を使い、要素のリストの合計を計算するメソッド`sum`を定義しています。 implicitの値をトップレベルには置けないことに注意してください。 diff --git a/_ja/tour/inner-classes.md b/_ja/tour/inner-classes.md index 5d38b1f1f6..60916409b4 100644 --- a/_ja/tour/inner-classes.md +++ b/_ja/tour/inner-classes.md @@ -2,15 +2,10 @@ layout: tour title: 内部クラス language: ja - -discourse: true - partof: scala-tour - num: 22 next-page: abstract-type-members previous-page: lower-type-bounds - --- Scalaではクラスが他のクラスをメンバーとして保持することが可能です。 @@ -24,7 +19,7 @@ Javaのような、内部クラスが外側のクラスのメンバーとなる class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -74,7 +69,7 @@ Scalaではそのような型も同様に表現することができ、`Graph#No class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } diff --git a/_ja/tour/lower-type-bounds.md b/_ja/tour/lower-type-bounds.md index 62a15447d3..e72a3773f8 100644 --- a/_ja/tour/lower-type-bounds.md +++ b/_ja/tour/lower-type-bounds.md @@ -2,16 +2,11 @@ layout: tour title: 下限型境界 language: ja - -discourse: true - partof: scala-tour - num: 21 next-page: inner-classes previous-page: upper-type-bounds prerequisite-knowledge: upper-type-bounds, generics, variance - --- [上限型境界](upper-type-bounds.html) は型を別の型のサブタイプに制限しますが、*下限型境界*は型が別の型のスーパータイプであることを宣言します。表現`B >: A`はパラメータ`B`または抽象型`B`が型`A`のスーパータイプであることを表します。ほとんどのケースで`A`はそのクラスの型パラメータであり、`B`はメソッドの型パラメータになります。 diff --git a/_ja/tour/mixin-class-composition.md b/_ja/tour/mixin-class-composition.md index 8b4e26067e..f95ab54cb9 100644 --- a/_ja/tour/mixin-class-composition.md +++ b/_ja/tour/mixin-class-composition.md @@ -2,16 +2,11 @@ layout: tour title: ミックスインを用いたクラス合成 language: ja - -discourse: true - partof: scala-tour - num: 7 next-page: higher-order-functions previous-page: tuples prerequisite-knowledge: inheritance, traits, abstract-classes, unified-types - --- ミックスインはクラスを構成するのに使われるトレイトです。 diff --git a/_ja/tour/multiple-parameter-lists.md b/_ja/tour/multiple-parameter-lists.md index ff267531b8..eb8fffd6cb 100644 --- a/_ja/tour/multiple-parameter-lists.md +++ b/_ja/tour/multiple-parameter-lists.md @@ -2,15 +2,10 @@ layout: tour title: 複数パラメータリスト(カリー化) language: ja - -discourse: true - partof: scala-tour - num: 10 next-page: case-classes previous-page: nested-functions - --- メソッドは複数のパラメータリストを持てます。 @@ -27,13 +22,11 @@ def foldLeft[B](z: B)(op: (B, A) => B): B 初期値0から始まり、`foldLeft`はここではリスト内の各要素とその一つ前の累積値に関数`(m, n) => m + n`を適用します。 -{% scalafiddle %} ```scala mdoc val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(0)((m, n) => m + n) println(res) // 55 ``` -{% endscalafiddle %} ### ユースケース 推奨される複数パラメータリストのユースケースは次の通りです。 diff --git a/_ja/tour/named-arguments.md b/_ja/tour/named-arguments.md index 8ce70335a9..88bc231fda 100644 --- a/_ja/tour/named-arguments.md +++ b/_ja/tour/named-arguments.md @@ -2,16 +2,11 @@ layout: tour title: 名前付き引数 language: ja - -discourse: true - partof: scala-tour - num: 34 next-page: packages-and-imports previous-page: default-parameter-values prerequisite-knowledge: function-syntax - --- メソッドを呼ぶ時、以下のように引数にパラメータ名でラベル付が可能です。 diff --git a/_ja/tour/nested-functions.md b/_ja/tour/nested-functions.md index 9701e421cc..0e6980451f 100644 --- a/_ja/tour/nested-functions.md +++ b/_ja/tour/nested-functions.md @@ -2,21 +2,15 @@ layout: tour title: ネストしたメソッド language: ja - -discourse: true - partof: scala-tour - num: 9 next-page: multiple-parameter-lists previous-page: higher-order-functions - --- Scalaではメソッドの定義をネストする(_訳注:入れ子にする_)ことができます。 以下のコードは与えられた数値の階乗を計算するための`factorial`メソッドを提供します。 -{% scalafiddle %} ```scala mdoc def factorial(x: Int): Int = { def fact(x: Int, accumulator: Int): Int = { @@ -29,7 +23,6 @@ Scalaではメソッドの定義をネストする(_訳注:入れ子にす println("Factorial of 2: " + factorial(2)) println("Factorial of 3: " + factorial(3)) ``` -{% endscalafiddle %} このプログラムの出力は以下の通りです。 diff --git a/_ja/tour/operators.md b/_ja/tour/operators.md index 7df5800aa1..398790b4e0 100644 --- a/_ja/tour/operators.md +++ b/_ja/tour/operators.md @@ -2,16 +2,11 @@ layout: tour title: 演算子 language: ja - -discourse: true - partof: scala-tour - num: 30 next-page: by-name-parameters previous-page: type-inference prerequisite-knowledge: case-classes - --- Scalaでは演算子はメソッドです。パラメータを1つだけ持つメソッドであれば*中置演算子*として使えます。例えば、`+`はドット記法で呼び出せます。 diff --git a/_ja/tour/package-objects.md b/_ja/tour/package-objects.md index 755935d3ef..f2ed9b1d89 100644 --- a/_ja/tour/package-objects.md +++ b/_ja/tour/package-objects.md @@ -2,11 +2,7 @@ layout: tour title: パッケージオブジェクト language: ja - -discourse: true - partof: scala-tour - num: 36 previous-page: packages-and-imports --- diff --git a/_ja/tour/packages-and-imports.md b/_ja/tour/packages-and-imports.md index 8e44239b05..4e94731644 100644 --- a/_ja/tour/packages-and-imports.md +++ b/_ja/tour/packages-and-imports.md @@ -2,11 +2,7 @@ layout: tour title: パッケージとインポート language: ja - -discourse: true - partof: scala-tour - num: 35 previous-page: named-arguments next-page: package-objects diff --git a/_ja/tour/pattern-matching.md b/_ja/tour/pattern-matching.md index 9e1f5d9692..23c997ca91 100644 --- a/_ja/tour/pattern-matching.md +++ b/_ja/tour/pattern-matching.md @@ -2,17 +2,11 @@ layout: tour title: パターンマッチング language: ja - -discourse: true - partof: scala-tour - num: 12 - next-page: singleton-objects previous-page: case-classes prerequisite-knowledge: case-classes, string-interpolation, subtyping - --- パターンマッチングは値をパターンに照合するための仕組みです。 diff --git a/_ja/tour/polymorphic-methods.md b/_ja/tour/polymorphic-methods.md index 6143ef2612..4bab5f63bd 100644 --- a/_ja/tour/polymorphic-methods.md +++ b/_ja/tour/polymorphic-methods.md @@ -2,17 +2,11 @@ layout: tour title: ポリモーフィックメソッド language: ja - -discourse: true - partof: scala-tour - num: 28 - next-page: type-inference previous-page: implicit-conversions prerequisite-knowledge: unified-types - --- Scalaのメソッドは値と同様に型によってパラメータ化することができます。構文はジェネリッククラスの構文と似ています。 diff --git a/_ja/tour/regular-expression-patterns.md b/_ja/tour/regular-expression-patterns.md index 05882529c0..79cf565c9f 100644 --- a/_ja/tour/regular-expression-patterns.md +++ b/_ja/tour/regular-expression-patterns.md @@ -2,16 +2,10 @@ layout: tour title: 正規表現パターン language: ja - -discourse: true - partof: scala-tour - num: 15 - next-page: extractor-objects previous-page: singleton-objects - --- 正規表現はデータの中からパターン(またはその欠如)を探すために使うことができる文字列です。 どんな文字列も`.r`メソッドを使うことで、正規表現に変換できます。 diff --git a/_ja/tour/self-types.md b/_ja/tour/self-types.md index cb585dc09e..7ffa6745ec 100644 --- a/_ja/tour/self-types.md +++ b/_ja/tour/self-types.md @@ -2,17 +2,12 @@ layout: tour title: 自分型 language: ja - -discourse: true - partof: scala-tour - num: 25 next-page: implicit-parameters previous-page: compound-types topics: self-types prerequisite-knowledge: nested-classes, mixin-class-composition - --- 自分型は、直接継承していなくてもトレイトが他のトレイトにミックスインされていることを宣言する方法です。 これにより依存先のメンバーをimportなしで利用できます。 diff --git a/_ja/tour/singleton-objects.md b/_ja/tour/singleton-objects.md index 69ce1aeede..0414291909 100644 --- a/_ja/tour/singleton-objects.md +++ b/_ja/tour/singleton-objects.md @@ -2,13 +2,8 @@ layout: tour title: シングルトンオブジェクト language: ja - -discourse: true - partof: scala-tour - num: 13 - next-page: regular-expression-patterns previous-page: pattern-matching prerequisite-knowledge: classes, methods, private-methods, packages, option diff --git a/_ja/tour/tour-of-scala.md b/_ja/tour/tour-of-scala.md index ff2d026686..70c1e0f522 100644 --- a/_ja/tour/tour-of-scala.md +++ b/_ja/tour/tour-of-scala.md @@ -2,15 +2,9 @@ layout: tour title: 前書き language: ja - -discourse: true - partof: scala-tour - num: 1 - next-page: basics - --- ## ようこそツアーへ @@ -19,7 +13,7 @@ next-page: basics これはほんの短いツアーで、完全な言語のチュートリアルではありません。 もしそれを望むなら、[こちらの本](/books.html) を手に入れるか、 -[その他の解決手段](/learn.html) での相談を検討してください。 +[その他の解決手段](/online-courses.html) での相談を検討してください。 ## Scalaとは? Scalaは一般的なプログラミング方法を簡潔かつエレガントかつ型安全な方法で表現するために設計されたモダンなマルチパラダイム言語です。 diff --git a/_ja/tour/traits.md b/_ja/tour/traits.md index 81b7dc7046..3803005571 100644 --- a/_ja/tour/traits.md +++ b/_ja/tour/traits.md @@ -2,17 +2,12 @@ layout: tour title: トレイト language: ja - -discourse: true - partof: scala-tour - num: 5 next-page: tuples previous-page: classes topics: traits prerequisite-knowledge: expressions, classes, generics, objects, companion-objects - --- トレイトはクラス間でインターフェースとフィールドを共有するために使います。それらはJava 8のインターフェースと似ています。 @@ -84,5 +79,5 @@ animals.append(dog) animals.append(cat) animals.foreach(pet => println(pet.name)) // Prints Harry Sally ``` -`trait Pet` が持つ抽象フィールド `name`は、Cat と Dog のコンストラクタで実装された。 +`trait Pet` が持つ抽象フィールド `name`は、Cat と Dog のコンストラクタで実装されています。 最終行では、`Pet` トレイトの全てのサブタイプの中で実装される必要がある `pet.name` を呼んでいます。 diff --git a/_ja/tour/tuples.md b/_ja/tour/tuples.md index 7729138914..e3c2f1e0f7 100644 --- a/_ja/tour/tuples.md +++ b/_ja/tour/tuples.md @@ -2,16 +2,11 @@ layout: tour title: タプル language: ja - -discourse: true - partof: scala-tour - num: 6 next-page: mixin-class-composition previous-page: traits topics: tuples - --- Scalaではタプルは決まった数の要素を含む値であり、各要素はそれぞれの型を持ちます。 diff --git a/_ja/tour/type-inference.md b/_ja/tour/type-inference.md index 2db0e440b9..5973da20a2 100644 --- a/_ja/tour/type-inference.md +++ b/_ja/tour/type-inference.md @@ -2,11 +2,7 @@ layout: tour title: 型推論 language: ja - -discourse: true - partof: scala-tour - num: 29 next-page: operators previous-page: polymorphic-methods diff --git a/_ja/tour/unified-types.md b/_ja/tour/unified-types.md index 8f9710b62a..28e44ad510 100644 --- a/_ja/tour/unified-types.md +++ b/_ja/tour/unified-types.md @@ -2,16 +2,11 @@ layout: tour title: 統合された型 language: ja - -discourse: true - partof: scala-tour - num: 3 next-page: classes previous-page: basics prerequisite-knowledge: classes, basics - --- Scalaでは数値や関数を含め、全ての値は型を持ちます。 以下の図は型階層の一部を説明しています。 @@ -69,7 +64,7 @@ true ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (この場合精度が落ちることに注意してください) +val y: Float = x.toFloat // 9.8765434E8 (この場合精度が落ちることに注意してください) val face: Char = '☺' val number: Int = face // 9786 @@ -79,7 +74,7 @@ val number: Int = face // 9786 ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // 一致しない ``` diff --git a/_ja/tour/upper-type-bounds.md b/_ja/tour/upper-type-bounds.md index 7d8ac4df9a..8dfe6ca8ac 100644 --- a/_ja/tour/upper-type-bounds.md +++ b/_ja/tour/upper-type-bounds.md @@ -2,15 +2,11 @@ layout: tour title: 上限型境界 language: ja - -discourse: true - partof: scala-tour categories: tour num: 20 next-page: lower-type-bounds previous-page: variances - --- Scalaでは [型パラメータ](generic-classes.html)と[抽象型メンバー](abstract-type-members.html) は型境界による制約をかけることができます。 diff --git a/_ja/tour/variances.md b/_ja/tour/variances.md index c7eb10304e..314f8c2e48 100644 --- a/_ja/tour/variances.md +++ b/_ja/tour/variances.md @@ -2,15 +2,10 @@ layout: tour title: 変位指定 language: ja - -discourse: true - partof: scala-tour - num: 19 next-page: upper-type-bounds previous-page: generic-classes - --- 変位指定は複合型の間の継承関係とそれらの型パラメータ間の継承関係の相関です。 diff --git a/_ja/tutorials/scala-for-java-programmers.md b/_ja/tutorials/scala-for-java-programmers.md index b49113ff72..a2076100b1 100644 --- a/_ja/tutorials/scala-for-java-programmers.md +++ b/_ja/tutorials/scala-for-java-programmers.md @@ -1,10 +1,7 @@ --- layout: singlepage-overview title: JavaプログラマーのためのScalaチュートリアル - partof: scala-for-java-programmers - -discourse: false language: ja --- diff --git a/_ko/tour/abstract-type-members.md b/_ko/tour/abstract-type-members.md index ba79514b11..b57ea05c11 100644 --- a/_ko/tour/abstract-type-members.md +++ b/_ko/tour/abstract-type-members.md @@ -36,16 +36,14 @@ previous-page: inner-classes 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) - } + 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`의 새로운 인스턴스를 생성한다. @@ -57,15 +55,13 @@ previous-page: inner-classes 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) - } + 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 index 924664303d..11b5da4b50 100644 --- a/_ko/tour/annotations.md +++ b/_ko/tour/annotations.md @@ -7,7 +7,7 @@ num: 31 language: ko next-page: packages-and-imports -previous-page: automatic-closures +previous-page: operators --- 어노테이션은 메타 정보와 정의 내용을 연결해준다. diff --git a/_ko/tour/automatic-closures.md b/_ko/tour/automatic-closures.md deleted file mode 100644 index 873828bb1b..0000000000 --- a/_ko/tour/automatic-closures.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: tour -title: 타입 의존 클로저의 자동 구성 -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/basics.md b/_ko/tour/basics.md index 21c8b94cdf..b0d0fda55c 100644 --- a/_ko/tour/basics.md +++ b/_ko/tour/basics.md @@ -14,16 +14,14 @@ previous-page: tour-of-scala ## 브라우저에서 스칼라 사용하기 -ScalaFiddle를 사용하면 브라우저에서 스칼라를 실행해 볼 수 있다. +Scastie를 사용하면 브라우저에서 스칼라를 실행해 볼 수 있다. -1. [https://scalafiddle.io](https://scalafiddle.io) 로 간다. +1. [Scastie](https://scastie.scala-lang.org/) 로 간다. 2. 왼쪽 창에 `println("Hello, world!")` 를 붙여 넣는다. 3. 실행 버튼을 누르면 오른쪽 창에서 출력을 확인할 수 있다. 이는 설정 없이 스칼라 코드들을 손쉽게 실험할 수 있는 방법이다. -이 페이지의 많은 예제 코드가 ScalaFiddle와 통합되어 있어 간단히 실행 버튼만 눌러 직접 실험해 볼 수 있다. - ## 표현식 표현식은 연산 가능한 명령문이다. @@ -34,14 +32,12 @@ ScalaFiddle를 사용하면 브라우저에서 스칼라를 실행해 볼 수 `println` 표현식을 사용해 결과를 출력할 수 있다. -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ### 값 @@ -110,21 +106,17 @@ println({ 함수에 이름을 지정할 수 있다. -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} 함수는 여러 매개변수를 가질 수 있다. -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} 또는 매개변수를 가지지 않을 수도 있다. @@ -139,23 +131,19 @@ println(getTheAnswer()) // 42 `def` 키워드로 메소드를 정의하고 이름, 매개변수 목록, 반환 타입 그리고 본문이 뒤따른다. -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} 매개변수 목록과 `: Int` 뒤에 반환 타입이 어떻게 선언되는지 주목하자. 메소드는 여러 매개변수 목록을 가질 수 있다. -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} 또는 매개변수 목록을 가지지 않을 수도 있다. @@ -168,7 +156,6 @@ println("Hello, " + name + "!") 메소드는 여러 줄의 표현식을 가질 수 있다. -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -176,7 +163,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} 본문의 마지막 표현식은 메소드의 반환 값이다. (스칼라는 `return` 키워드가 있지만 거의 사용하지 않고 생략한다.) @@ -222,15 +208,15 @@ val yetAnotherPoint = Point(2, 2) ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$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.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) and Point(2,2) are different. ``` @@ -277,7 +263,6 @@ trait Greeter { 또한 트레이트는 기본 구현도 가질 수 있다. -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -302,7 +287,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} `DefaultGreeter` 는 트레이트 하나만 상속하고 있지만 다중 상속도 가능하다. diff --git a/_ko/tour/generic-classes.md b/_ko/tour/generic-classes.md index 724514f338..4a7d70bf56 100644 --- a/_ko/tour/generic-classes.md +++ b/_ko/tour/generic-classes.md @@ -12,13 +12,15 @@ previous-page: extractor-objects 자바 5(다른 이름은 JDK 1.5와 같이, 스칼라는 타입으로 파라미터화된 클래스의 빌트인 지원을 제공한다. 이런 제네릭 클래스는 특히 컬렉션 클래스의 개발에 유용하다. 이에 관한 예제를 살펴보자. - class Stack[T] { - var elems: List[T] = Nil - def push(x: T): Unit = - elems = x :: elems - def top: T = elems.head - def pop() { elems = elems.tail } - } +```scala mdoc +class Stack[T] { + var elems: List[T] = Nil + def push(x: T): Unit = + elems = x :: elems + def top: T = elems.head + def pop(): Unit = { elems = elems.tail } +} +``` 클래스 `Stack`은 임의의 타입 `T`를 항목의 타입으로 하는 명령형(변경 가능한) 스택이다. 타입 파라미터는 올바른 항목(타입 `T` 인)만을 스택에 푸시하도록 강제한다. 마찬가지로 타입 파라미터를 사용해서 메소드 `top`이 항상 지정된 타입만을 반환하도록 할 수 있다. diff --git a/_ko/tour/implicit-conversions.md b/_ko/tour/implicit-conversions.md index 41aa47856e..d5af6dac66 100644 --- a/_ko/tour/implicit-conversions.md +++ b/_ko/tour/implicit-conversions.md @@ -43,8 +43,8 @@ previous-page: implicit-parameters ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + Integer.valueOf(x) ``` 암시적 변환이 무분별하게 사용될 경우 잠재적인 위험을 가질 수 있기 때문에, 컴파일러는 암시적 변환의 선언을 컴파일할 시 이를 경고한다. diff --git a/_ko/tour/inner-classes.md b/_ko/tour/inner-classes.md index 173449ef43..309a4651a1 100644 --- a/_ko/tour/inner-classes.md +++ b/_ko/tour/inner-classes.md @@ -11,49 +11,55 @@ previous-page: lower-type-bounds --- 스칼라의 클래스는 다른 클래스를 멤버로 가질 수 있다. 자바와 같은 언어의 내부 클래스는 자신을 감싸고 있는 클래스의 멤버인 반면에, 스칼라에선 내부 클래스가 외부 객체의 경계 안에 있다. 이런 차이점을 분명히 하기 위해 그래프 데이터타입의 구현을 간단히 그려보자. - - class Graph { - class Node { - var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { - if (!connectedNodes.exists(node.equals)) { - connectedNodes = node :: connectedNodes - } - } - } - var nodes: List[Node] = Nil - def newNode: Node = { - val res = new Node - nodes = res :: nodes - res + +```scala mdoc +class Graph { + class Node { + var connectedNodes: List[Node] = Nil + def connectTo(node: Node): Unit = { + if (!connectedNodes.exists(node.equals)) { + 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) - } - + +```scala mdoc:nest +def graphTest: Unit = { + 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) - } - + +```scala mdoc:nest +def graphTest: Unit = { + 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 @@ -63,13 +69,13 @@ previous-page: lower-type-bounds 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) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -82,7 +88,7 @@ previous-page: lower-type-bounds res } } - + > 이 프로그램에선 하나의 노드를 서로 다른 두 그래프에 추가할 수 없음에 주의하자. 이 제약도 함께 제거하기 위해선 변수 nodes의 타입을 `Graph#Node`로 바꿔야 한다. 윤창석, 이한욱 옮김 diff --git a/_ko/tour/mixin-class-composition.md b/_ko/tour/mixin-class-composition.md index 122d1051a4..94f21d2244 100644 --- a/_ko/tour/mixin-class-composition.md +++ b/_ko/tour/mixin-class-composition.md @@ -21,7 +21,7 @@ _단일 상속_ 만을 지원하는 여러 언어와는 달리, 스칼라는 더 이어서, 이터레이터가 반환하는 모든 항목에 주어진 함수를 적용해주는 `foreach` 메소드로 `AbsIterator`를 확장한 믹스인 클래스를 살펴보자. 믹스인으로 사용할 수 있는 클래스를 정의하기 위해선 `trait`이란 키워드를 사용한다. trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next()) } + def foreach(f: T => Unit): Unit = { while (hasNext) f(next()) } } 다음은 주어진 문자열의 캐릭터를 차례로 반환해주는 콘크리트 이터레이터 클래스다. @@ -36,7 +36,7 @@ _단일 상속_ 만을 지원하는 여러 언어와는 달리, 스칼라는 더 `StringIterator`와 `RichIterator`를 하나의 클래스로 합치고 싶다면 어떻게 할까. 두 클래스 모두는 코드가 포함된 멤버 구현을 담고 있기 때문에 단일 상속과 인터페이스 만으론 불가능한 일이다. 스칼라는 _믹스인 클래스 컴포지션_ 으로 이런 상황을 해결해준다. 프로그래머는 이를 사용해 클래스 정의에서 변경된 부분을 재사용할 수 있다. 이 기법은 주어진 문자열에 포함된 모든 캐릭터를 한 줄로 출력해주는 다음의 테스트 프로그램에서와 같이, `StringIterator`와 `RichIterator`를 통합할 수 있도록 해준다. object StringIteratorTest { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { class Iter extends StringIterator("Scala") with RichIterator val iter = new Iter iter foreach println diff --git a/_ko/tour/operators.md b/_ko/tour/operators.md index fd904bfdd7..4306b08744 100644 --- a/_ko/tour/operators.md +++ b/_ko/tour/operators.md @@ -6,7 +6,7 @@ partof: scala-tour num: 29 language: ko -next-page: automatic-closures +next-page: annotations previous-page: type-inference --- diff --git a/_ko/tour/self-types.md b/_ko/tour/self-types.md index 56ca99a2e6..931a5e8312 100644 --- a/_ko/tour/self-types.md +++ b/_ko/tour/self-types.md @@ -88,7 +88,7 @@ previous-page: compound-types 다음은 클래스 `ConcreteDirectedGraph`를 사용하는 예다. - object GraphTest extends App { + def graphTest: Unit = { val g: Graph = new ConcreteDirectedGraph val n1 = g.addNode val n2 = g.addNode diff --git a/_ko/tour/tour-of-scala.md b/_ko/tour/tour-of-scala.md index 0c83ea5e0c..0bf0b28232 100644 --- a/_ko/tour/tour-of-scala.md +++ b/_ko/tour/tour-of-scala.md @@ -12,7 +12,7 @@ next-page: basics ## 투어를 환영합니다 이 투어에서는 스칼라에서 가장 자주 사용되는 기능을 요약하여 소개하며, 스칼라 초보자를 대상으로 합니다. -언어 전체를 다루는 튜토리얼이 아닌 간단히 둘러보기입니다. 자세히 다루고 싶다면, [책](/books.html)을 구하거나 [다른 자료](/learn.html)를 찾아보세요. +언어 전체를 다루는 튜토리얼이 아닌 간단히 둘러보기입니다. 자세히 다루고 싶다면, [책](/books.html)을 구하거나 [다른 자료](/online-courses.html)를 찾아보세요. ## 스칼라란? 스칼라는 일반적인 프로그래밍 패턴을 간결하고 우아하며 타입-세이프한 방식으로 표현할 수 있게 설계된 최신 멀티-패러다임 프로그래밍 언어입니다. 객체지향과 함수형 언어의 특징을 자연스럽게 통합합니다. diff --git a/_ko/tour/unified-types.md b/_ko/tour/unified-types.md index 989ef50d6b..0e12a3aaff 100644 --- a/_ko/tour/unified-types.md +++ b/_ko/tour/unified-types.md @@ -59,7 +59,7 @@ true ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (이 경우 일부 자리수가 소실되었음을 주의) +val y: Float = x.toFloat // 9.8765434E8 (이 경우 일부 자리수가 소실되었음을 주의) val face: Char = '☺' val number: Int = face // 9786 @@ -69,7 +69,7 @@ val number: Int = face // 9786 ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // 적합하지 않음(캐스팅 불가) ``` @@ -78,4 +78,4 @@ val z: Long = y // 적합하지 않음(캐스팅 불가) # Nothing과 Null `Nothing`은 모든 타입의 서브타입이며, 바텀타입이라고도 합니다. `Nothing`은 값이 없음을 의미하는 타입니다. 일반적으로 예외 발생, 프로그램 종료 또는 무한 루프와 같은 비 종료 신호를 보내는 용도로 사용합니다 (즉, 값으로 평가되지 않는 표현식의 타입 또는 정상적으로 반환되지 않는 메소드). -`Null`은 모든 참조 타입의 서브타입입니다(즉, AnyRef의 모든 서브타입). 예약어 `null`로 식별되는 단일 값을 갖습니다. `Null`은 주로 다른 JVM 언어와의 상호 운용성을 위해 제공되며 스칼라 코드에서는 거의 사용되지 않아야합니다. 우리는 투어에서 나중에 `null`에 대한 대안을 다룰 것입니다. +`Null`은 모든 참조 타입의 서브타입입니다(즉, AnyRef의 모든 서브타입). 예약어 `null`로 식별되는 단일 값을 갖습니다. `Null`은 주로 다른 JVM 언어와의 상호 운용성을 위해 제공되며 스칼라 코드에서는 거의 사용되지 않아야합니다. 우리는 투어에서 나중에 `null`에 대한 대안을 다룰 것입니다. diff --git a/_ko/tutorials/scala-for-java-programmers.md b/_ko/tutorials/scala-for-java-programmers.md index b3f3d1b6d3..1dc0358edb 100644 --- a/_ko/tutorials/scala-for-java-programmers.md +++ b/_ko/tutorials/scala-for-java-programmers.md @@ -464,7 +464,7 @@ Scala로 나타내는 것은 어렵지 않다: **와일드카드**이다. 밑줄 문자 `_`로 쓰며, 모든 값과 매치 되고 따로 이름을 붙이지 않는다. -매턴 매칭의 뛰어난 기능들을 모두 살펴보지는 못했지만, 문서를 너무 +패턴 매칭의 뛰어난 기능들을 모두 살펴보지는 못했지만, 문서를 너무 지루하게 만들지 않기 위하여 이쯤에서 멈추기로 한다. 이제 위에서 정의한 두 개의 예제 함수가 실제로 동작하는 모습을 보자. 산술 표현식 `(x+x)+(7+y)`에 대해 몇가지의 연산을 실행하는 간단한 `main` 함수를 diff --git a/_layouts/inner-page.html b/_layouts/basic-index.html similarity index 87% rename from _layouts/inner-page.html rename to _layouts/basic-index.html index e0cc813293..901f1a4453 100644 --- a/_layouts/inner-page.html +++ b/_layouts/basic-index.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent +layout: root-index-layout --- <section class="full-width"> diff --git a/_layouts/blog-detail.html b/_layouts/blog-detail.html deleted file mode 100644 index 8e6419da3b..0000000000 --- a/_layouts/blog-detail.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: inner-page-parent ---- - -<!-- Main content --> -{% 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 deleted file mode 100644 index d06c97051a..0000000000 --- a/_layouts/blog-list.html +++ /dev/null @@ -1,9 +0,0 @@ ---- -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 9ee9695c01..8809f9cc3e 100644 --- a/_layouts/cheatsheet.html +++ b/_layouts/cheatsheet.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout --- <section class="full-width"> @@ -7,6 +7,9 @@ <div class="content-primary cheatsheet"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} {{content}} </div> diff --git a/_layouts/contribute.html b/_layouts/contribute.html deleted file mode 100644 index 8cd5f886e5..0000000000 --- a/_layouts/contribute.html +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: inner-page-no-masthead -includeTOC: true ---- - -<div class="wrapper"> - <div class="bottom"> - <div class="container"> - <div class="row"> - <div class="span13"> - {{ content }} - </div> - </div> - </div> - </div> - <div class="push"></div> -</div> diff --git a/_layouts/documentation.html b/_layouts/documentation.html deleted file mode 100644 index f578b644d1..0000000000 --- a/_layouts/documentation.html +++ /dev/null @@ -1,83 +0,0 @@ ---- ---- -{% include headertop.html %} {% include headerbottom.html %} -{% if page.new-version %}<a class="new-version-notice" href="{{ page.new-version }}">This page has a new version.</a>{% endif %} - -<div class="navigation-fade-screen"></div> - -{% include site-header.html %} - -<main id="inner-main" class="landing-page"> - - <section class="title-page"> - <div class="wrap"> - <div class="content-title-documentation"> - - <h1>{{page.title}}</h1> - <div class="search-container"> - <div class="icon-search"> - <i class="fa fa-search"></i> - </div> - <input type="text" class="doc-search" id="doc-search-bar" placeholder="Search in doc..."> - <ul class="result-container" id="result-container" style="display: none;"></ul> - </div> - </div> - </div> - </section> - - <section class="table-of-content"> - <div class="wrap scala2"> - <div class="language-header"> - <h1>Scala 2</h1> - </div> - <div class="inner-box"> - <div class="language-dropdown inverted"> - <div id="dd" class="wrapper-dropdown" tabindex="1"> - <span>Language</span> - <ul class="dropdown"></ul> - </div> - </div> - - <ul id="available-languages" style="display: none;"> - {% if page.languages %} - <li><a href="{{ site.baseurl }}{{ page.url }}">English</a></li> - {% for l in page.languages %} - {% capture intermediate %}{{ page.url }}{% endcapture %} - {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% assign lang = site.data.languages[l] %} - <li><a href="{{ site.baseurl }}/{{ l }}/{{ rootTutorialURL }}" class="lang">{{ lang.name }}</a></li> - {% endfor %} - {% elsif page.language %} - {% assign engPath = page.url | remove_first: "/" | remove_first: page.language | remove_first: "index.html" %} - {% assign engPg = site.pages | where: 'partof', page.partof | first %} - <li><a href="{{ site.baseurl }}{{ engPath }}">English</a></li> - {% for l in engPg.languages %} - {% assign lang = site.data.languages[l] %} - <li><a href="{{ site.baseurl }}/{{ l }}{{ engPath }}" class="lang">{{ lang.name }}</a></li> - {% endfor %} - {% endif %} - </ul> - - {% include documentation-sections.html sections=page.scala2-sections %} - </div> - </div> - - <div class="wrap scala3"> - <div class="language-header"> - <h1>Scala 3</h1> - </div> - <div class="inner-box"> - <blockquote> - We are still in the process of writing the documentation for Scala 3. - You can <a href="/scala3/contribute-to-docs.html">help us to improve the documentation</a>. - </blockquote> - {% include documentation-sections.html sections=page.scala3-sections %} - </div> - </div> - </section> - - {{content}} - -</main> - -{% include footer.html %} diff --git a/_layouts/downloadpage.html b/_layouts/downloadpage.html deleted file mode 100644 index 0849a3d013..0000000000 --- a/_layouts/downloadpage.html +++ /dev/null @@ -1,100 +0,0 @@ ---- -layout: inner-page-parent ---- - -<section class="books"> - <div class="wrap"> - <div class="inner-box download"> - <div class="content-ribbon"> - <div class="ribbon-version"> - <span>{{page.release_version}}</span> - </div> - <ul> - <li><a href="/download/changelog/">Release Notes</a></li> - <li class="dot">•</li> - <li><a href="/download/changelog/">Changelog</a></li> - </ul> - </div> - <div class="main-download"> - <h2>Compared to other programming languages, installing Scala is a bit unusual. It's possible to "install" Scala in numerous ways.</h2> - <div class="install-steps"> - <div class="step"> - <img src="{{ site.baseurl }}/resources/img/download/arrow-asset.png" alt=""> - <div class="number-step">1</div> - <div class="text-step" id="download-step-one"> - <h3>First, make sure you have the Java 8 JDK installed.</h3> - <p><code>java -version</code><span>(Make sure you have version 1.8.)</span></p> - </div> - </div> - <div class="step"> - <div class="number-step">2</div> - <div class="text-step"> - <h3>Then, install Scala using:</h3> - </div> - </div> - <div class="download-options"> - <div class="download-intellij"> - <span class="or">or</span> - <div class="description"> - <img src="{{ site.baseurl }}/resources/img/download/arrow-left.png" alt=""> - <p>Best if you prefer a full-featured IDE (recommended for beginners)</p> - </div> - <a href="" class="btn-download" id="download-intellij-link"> - <i class="fa fa-download"></i> - <span>Download intellij</span> - </a> - <ul> - {% include tutorial-list.html column=1 %} - </ul> - </div> - <div class="download-sbt"> - <div class="description"> - <img src="{{ site.baseurl }}/resources/img/download/arrow-right.png" alt=""> - <p>Best if you are familiar with the command line</p> - </div> - <a href="" class="btn-download" id="download-sbt-link"> - <i class="fa fa-download"></i> - <span>Download Sbt</span> - </a> - <ul> - {% include tutorial-list.html column=0 %} - </ul> - </div> - </div> - <p class="bottom-lead">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.</p> - - <h3>Release Notes</h3> - <p>For important changes, please consult the <a href="/blog/announcements/">release notes</a>.</p> - - <h3>Software Requirements</h3> - - {{ page.requirements }} - - {{ content }} - - {% if page.show_resources == "true" %} - - {% include download-resource-list.html %} - - {% endif %} - - <h3>License</h3> - <p>The Scala distribution is released under the <a href="{{ site.baseurl }}/license/">3-clause BSD license</a>.</p> - </div> - - </div> - </div> - </div> - - {% for step in site.data.downloads.stepOne %} - <div style="display:none" id="stepOne-{{step.os}}">{{step.text}}</div> - {% endfor %} - - {% for intellijUrl in site.data.downloads.intellijUrls %} - <div style="display:none" id="intellij-{{intellijUrl.os}}">{{intellijUrl.url}}</div> - {% endfor %} - - {% for sbtUrl in site.data.downloads.sbtUrls %} - <div style="display:none" id="sbt-{{sbtUrl.os}}">{{sbtUrl.url}}</div> - {% endfor %} -</section> \ No newline at end of file diff --git a/_layouts/events.html b/_layouts/events.html deleted file mode 100644 index 4959010ad3..0000000000 --- a/_layouts/events.html +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: inner-page-parent ---- - -{% include events-training-list-top.html collection=paginator.events %} - <div class="org-scala-event"> - {{content}} - </div> - - </div> - - {% include paginator.html urlPath="events" %} -</div> \ No newline at end of file diff --git a/_layouts/frontpage.html b/_layouts/frontpage.html deleted file mode 100644 index 29fa5825fe..0000000000 --- a/_layouts/frontpage.html +++ /dev/null @@ -1,386 +0,0 @@ ---- ---- - -{% include headertop.html %} -{% include headerbottom.html %} - -<div class="navigation-fade-screen"></div> -<!-- Header --> - <header id="site-header" class="header-home"> - <div class="header-background"> - <div class="new-on-the-blog"> - <p>New on the blog: - {% assign newPost = site.posts.first %} - <a href="{{newPost.url}}">{{newPost.title}}</a> - </p> - <span class="hide"><i class="fa fa-close"></i></span> - </div> - <div class="wrap"> - <!-- Navigation --> - <nav class="navigation" role="menu"> - <a href="/" class="navigation-bdand"> - <img src="{{ site.baseurl }}/resources/img/frontpage/scala-logo-white@2x.png" alt=""> - </a> - <div class="navigation-panel-button"> - <i class="fa fa-bars"></i> - </div> - <ul class="navigation-menu"> - {% for navItem in site.data.nav-header %} - <li class="navigation-menu-item"> - <a href="{{navItem.url}}">{{navItem.title}}</a> - </li> - {% endfor %} - </ul> - </nav> - <!-- Inner Text --> - <div class="inner-text"> - <h1>{{page.headerTitle}}</h1> - <p>{{page.headerSubtitle}}</p> - <a href="{{page.headerButtonUrl}}" class="button">{{page.headerButtonTitle | upcase}}</a> - </div> - <div id="position-marker" class="marker"> - <div class="info-marker"> - <span class="arrow"> - <img src="{{ site.baseurl }}/resources/img/frontpage/arrow.png" alt="pushpin"> - </span> - {{site.data.common.texts.frontpageMarker}} - </div> - </div> - </div> - </div> - <!-- Scala Main Resources --> - <div class="scala-main-resources"> - <div class="wrap"> - <div class="resources"> - <div class="download"> - <a href="{{page.gettingStarted.mainUrl}}" class="button">{{page.gettingStarted.mainTitle | upcase}}</a> - <ul> - <li><a href="{{page.gettingStarted.subtitleLink}}">{{page.gettingStarted.subtitle}}</a></li> - {% for link in page.gettingStarted.links %} - <li><a href="{{link.url}}">{{link.title}}</a></li> - {% endfor %} - </ul> - </div> - <div class="api-docs"> - <a href="{{page.apiDocs.mainUrl}}" class="button">{{page.apiDocs.mainTitle | upcase}}</a> - <ul> - <li><a href="{{page.apiDocs.subtitleLink}}">{{page.apiDocs.subtitle}}</a></li> - {% for link in page.apiDocs.links %} - <li><a href="{{link.url}}">{{link.title}}</a></li> - {% endfor %} - </ul> - </div> - <div class="scala-brand-circle"> - <div class="circle-solid"> - <img src="{{ site.baseurl }}/resources/img/frontpage/scala-spiral.png" alt=""> - <div class="scala-version"> - <span>Scala</span> - <span>{{page.currentScalaVersion}}</span> - </div> - </div> - </div> - </div> - </div> - </div> - </header> - - -<main id="site-main"> -<!-- Scala backends --> -<section class="runs"> - <div class="wrap"> - <h2>{{site.data.common.texts.scalaBackendsTitle}}</h2> - <ul> - {% for backend in page.scalaBackends %} - <li class="masterTooltip" title="{{backend.description}}"> - <span> - <img src="{{backend.icon}}" alt="{{backend.description}}"> - </span> - </li> - {% unless forloop.last %}<li></li>{% endunless %} - {% endfor %} - </ul> - <p><span>- {{site.data.common.texts.scalaBackendsMore}} -</span></p> - </div> -</section> - -<!-- IDEs for Scala --> -<section class="ides"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>IDEs for Scala</span></h2> - </div> - <ul> - {% for ide in page.scalaIDEs %} - <li> - <a {% if ide.ensime == true %}class="masterTooltip" title="{{site.data.common.texts.ensimeSupportedInIDE}}"{% endif %} href="{{ide.url}}" target="_blank"> - {% if ide.ensime == true %} - <span class="bullet"> - <img src="{{ site.baseurl }}/resources/img/frontpage/icon-ensime.png" alt=""> - </span> - {% endif %} - <img src="{{ide.icon}}" alt="{{ide.name}}"> - <span>{{ide.name}}</span> - </a> - </li> - {% unless forloop.last %} - <li></li> - {% endunless %} - {% endfor %} - </ul> - </div> -</section> - -<!-- Scala in a Nutshell --> -<section class="nutshell"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>Scala in a Nutshell</span></h2> - <div class="sub-heading"> - <p> click the boxes below to see Scala in action! </p> - </div> - </div> - </div> - <div class="scala-items-list"> - <div class="items-menu"> - {% for scalaItem in site.scala_items %} - {% assign loopIndexMod = forloop.index | minus: 1 | modulo: 3 %} - - {% if loopIndexMod == 0 %} - {% assign codeSnippets = '' | split: ',' %} - <div class="wrap"> - {% endif %} - {% assign codeSnippets = codeSnippets | push: scalaItem.content %} - <div class="scala-item"> - <h3>{{scalaItem.shortTitle}}</h3> - <p>{{scalaItem.shortDescription}}</p> - </div> - {% if loopIndexMod == 2 or forloop.last %} - </div> - <div class="items-content"> - {% for snippet in codeSnippets %} - <div class="items-code">{{snippet}}</div> - {% endfor %} - </div> - {% endif %} - {% endfor %} - </div> - <div class="call-to-action action-medium"> - <a href="{{page.headerButtonUrl}}" class="button">{{page.headerButtonTitle}}</a> - <p class="align-bottom">or visit the <a href="/documentation/">Scala Documentation</a></p> - </div> - </div> -</section> - -<!-- Run Scala in your browser --> -<!-- {% include scastie.html %} --> - -<!-- Courses --> -<section class="courses"> - <div class="wrap"> - {% include online-courses.html %} - {% include upcoming-training.html %} - </div> -</section> - -<!-- Upcoming events --> -<section class="upcoming-events"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>Upcoming Events</span></h2> - </div> - <div class="events-items-list"> - {% 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 %} - <div href="#" class="event-item"> - <a href="{{event.link-out}}" class="card"> - <img src="{{event.logo}}" alt="{{event.title}}"> - <div class="card-text"> - <h4>{{event.title}}</h4> - <ul> - <li class="event-location">{{event.location}}</li> - </ul> - <ul> - <li class="date-event">{{event.start | date_to_string}} {% if event.start != event.end %}- {{event.end | date_to_string}}{% endif %}</li> - </ul> - </div> - </a> - </div> - {% endfor %} - </div> - <div class="call-to-action action-medium"> - <p class="align-top">See <a href="/events/">more events</a> or <a href="https://github.com/scala/scala-lang/tree/main/events">add one to our feed</a></p> - </div> - </div> -</section> -<!-- Scala Ecosystem --> -<section class="scala-ecosystem"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>{{page.ecosystemTitle}}</span></h2> - <p class="lead">{{page.ecosystemDescription}}</p> - </div> - <div class="browser"> - <div class="header-browser"> - <a href="{{site.data.common.scaladexUrl}}" target="_blank"><img src="{{ site.baseurl }}/resources/img/frontpage/scaladex-logo.png" alt=""></a> - <a href="{{site.data.common.scaladexUrl}}" target="_blank"><img src="{{ site.baseurl }}/resources/img/frontpage/button-github.png" alt=""></a> - </div> - <div class="main-browser"> - <h2>The Scala Library Index</h2> - <div class="input-control"> - <span><i class="fa fa-search"></i></span> - <input type="text" placeholder="Search" id="scaladex-search"> - <div class="autocomplete-suggestions"> - <div class="autocomplete-suggestion autocomplete-selected"></div> - <div class="autocomplete-suggestion"></div> - </div> - </div> - </div> - </div> - </div> -</section> -<!-- Blog --> -<section class="new-blog"> - <div class="wrap"> - <div class="new"> - <div class="heading-line"> - <h2><span>What’s New</span></h2> - </div> - {% assign firstPost = site.posts | first %} - <div class="content-card"> - <p class="tag-new">{{firstPost.post-type|upcase}}</p> - <h3><a href="{{firstPost.url}}">{{firstPost.title}}</a></h3> - <span class="date">{{firstPost.date | date: "%A, %B %-d, %Y"}}</span> - <p>{{firstPost.content}}</p> - </div> - </div> - <div class="recently"> - <div class="heading-line"> - <h2><span>Recently</span></h2> - </div> - {% for post in site.posts limit:3 offset:1 %} - <a href="{{post.url}}" class="content-card"> - <h3>{{post.title}}</h3> - <ul> - <li>{{post.date | date: "%A, %B %-d, %Y"}}</li> - <li class="dot">•</li> - <li>{{post.by}}</li> - <li class="tag">{{post.post-type|upcase}}</li> - </ul> - <p>{{post.content| strip_html | truncate:140}}</p> - </a> - {% endfor %} - </div> - </div> - <div class="call-to-action action-medium"> - <a href="/blog/" class="button">View our blog</a> - </div> -</section> -<!-- Talk to us --> -<section class="talk-to-us"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>Talk to us!</span></h2> - </div> - <div class="discourse"> - <h3>Mailing lists / forums</h3> - {% for forum in site.data.chats-forums.discourseForums %} - <a href="{{forum.url}}" class="{{forum.cssClass}}"> - <img src="{{ site.baseurl }}/resources/img/frontpage/discourse-logo.png" alt="{{forum.title}}"> - <h4>{{forum.title}}</h4> - <p>{{forum.subtitle}}</p> - </a> - {% endfor %} - </div> - <div class="discord"> - <h3>Real-time chat</h3> - {% assign modLimit = site.data.chats-forums.discordServers.size | modulo: 2 %} - {% capture channelLimit %} - {% if modLimit != 0 %} - {{site.data.chats-forums.discordServers.size | minus: 1}} - {% else %} - {{site.data.chats-forums.discordServers.size}} - {% endif %} - {% endcapture %} - {% for server in site.data.chats-forums.discordServers limit: channelLimit %} - {% if forloop.first %} - <ul class="first"> - {% endif %} - <li> - <a href="{{server.url}}"> - <img src="{{ site.baseurl }}/resources/img/frontpage/discord-logo.png" alt=""> - <span>{{server.name}}</span> - </a> - </li> - - {% assign halfLength = forloop.length | divided_by: 2 | floor %} - {% if forloop.index == halfLength %} - </ul> - <ul class="second"> - {% endif %} - - {% if forloop.last %} - </ul> - {% endif %} - {% endfor %} - </div> - {% if page.communities %} - <div class="communities"> - <h3>Communities</h3> - <ul> - {% for community in page.communities %} - <li> - <a href="{{community.url}}"><img src="{{community.icon}}" alt="{{community.name}}"></a> - </li> - {% endfor %} - </ul> - </div> - {% endif %} - <div class="social"> - <ul> - <li> - <a href="{{site.data.common.twitterUrl}}"><i class="fa fa-twitter"></i></a> - </li> - <li> - <a href="{{site.data.common.githubUrl}}"><i class="fa fa-github"></i></a> - </li> - </ul> - </div> - </div> -</section> - -<!-- Twitter feed --> -{% include twitter-feed.html %} - -<!-- Spire --> -<section class="spire"></section> - -<!-- Supporters --> -<section class="maintenance"> - <div class="wrap"> - <div class="heading-line"> - <h2><span>{{site.data.common.texts.scalaMaintainersTitle}}</span></h2> - </div> - <ul class="maintained"> - {% for maintainer in site.data.scala-supporters.maintainers %} - <li><a href="{{maintainer.url}}"><img src="{{maintainer.logo}}" alt="{{maintainer.name}}"></a></li> - {% endfor %} - </ul> - <h3>{{site.data.common.texts.scalaSupportersTitle}}</h3> - <div class="supported"> - {% for supporter in site.data.scala-supporters.supporters %} - <a href="{{supporter.url}}"><img src="{{supporter.logo}}" alt="{{supporter.name}}"></a> - {% endfor %} - </div> - </div> -</section> -</main> - -{% include footer.html %} diff --git a/_layouts/glossary.html b/_layouts/glossary.html index 59a14bf8f9..bd15a6274e 100644 --- a/_layouts/glossary.html +++ b/_layouts/glossary.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeTOC: true --- @@ -8,6 +8,9 @@ <div class="content-primary documentation glossary"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} {{content}} </div> {% if page.languages %} diff --git a/_layouts/inner-page-community.html b/_layouts/inner-page-community.html deleted file mode 100644 index f5cdb3afdb..0000000000 --- a/_layouts/inner-page-community.html +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: inner-page-parent ---- - -<!-- Masthead --> -{% include masthead-community.html %} - -<!-- Main content --> -{% include inner-page-main-content.html %} \ No newline at end of file diff --git a/_layouts/inner-page-no-masthead.html b/_layouts/inner-page-no-masthead.html deleted file mode 100644 index 28c8fe1663..0000000000 --- a/_layouts/inner-page-no-masthead.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: inner-page-parent ---- - -<!-- Main content --> -{% include inner-page-main-content.html %} \ No newline at end of file diff --git a/_layouts/inner-page-parent.html b/_layouts/inner-page-parent.html deleted file mode 100644 index 2dc28bb539..0000000000 --- a/_layouts/inner-page-parent.html +++ /dev/null @@ -1,31 +0,0 @@ - {% include headertop.html %} {% include headerbottom.html %} - {% if page.new-version %}<a class="new-version-notice" href="{{ page.new-version }}">This page has a new version.</a>{% endif %} - -<div class="navigation-fade-screen"></div> - -{% include navbar-inner.html %} - -<main id="inner-main"> - - <!-- Title --> - <section class="title-page"> - <div class="wrap"> - <div class="content-title-documentation"> - - <h1>{{page.title}}</h1> - <div class="search-container"> - <div class="icon-search"> - <i class="fa fa-search"></i> - </div> - <input type="text" class="doc-search" id="doc-search-bar" placeholder="Search in doc..."> - <ul class="result-container" id="result-container" style="display: none;"></ul> - </div> - </div> - </div> - </section> - - {% comment %}Specific content from child layouts{% endcomment %} {{content}} - -</main> - -{% include footer.html %} diff --git a/_layouts/inner-page-documentation.html b/_layouts/landing-page.html similarity index 72% rename from _layouts/inner-page-documentation.html rename to _layouts/landing-page.html index 5c420b46c3..5dfce6e343 100644 --- a/_layouts/inner-page-documentation.html +++ b/_layouts/landing-page.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent +layout: root-index-layout --- <!-- Masthead --> diff --git a/_layouts/multipage-overview.html b/_layouts/multipage-overview.html index 17033116ae..6ff8b3aa85 100644 --- a/_layouts/multipage-overview.html +++ b/_layouts/multipage-overview.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeTOC: true includeCollectionTOC: true --- @@ -9,6 +9,28 @@ <div class="content-primary documentation"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} + {% if page.versionSpecific %} + {% if page.scala3 %} + {% assign versionSpecificLang = 'scala3' %} + {% else if page.scala2 %} + {% assign versionSpecificLang = 'scala2' %} + {% endif %} + {% if page.language %} + {% assign pageLanguage = page.language %} + {% else %} + {% assign pageLanguage = 'en' %} + {% endif %} + {% include version-specific-notice.html language=versionSpecificLang page-language=pageLanguage %} + {% endif %} + <noscript> + <blockquote> + <span style="font-weight: bold;">Info:</span> JavaScript is currently disabled, code tabs will still work, + but preferences will not be remembered. + </blockquote> + </noscript> {{content}} </div> diff --git a/_layouts/online-courses.html b/_layouts/online-courses.html new file mode 100644 index 0000000000..45addb8dfa --- /dev/null +++ b/_layouts/online-courses.html @@ -0,0 +1,29 @@ +--- +layout: root-index-layout +--- + +<!-- Main content: Other online resources --> +<section class="online-courses"> + <div class="content-primary"> + <div class="wrap"> + {% include online-courses-box.html + path="_markdown/courses-coursera.md" + image="coursera.png" + link="https://www.coursera.org/learn/scala-functional-programming" + %} + {% include online-courses-box.html + path="_markdown/courses-rock-the-jvm.md" + image="rock-the-jvm.png" + link="https://rockthejvm.com" + %} + {% include online-courses-box.html + path="_markdown/courses-extension-school.md" + image="extension-school.png" + link="https://www.epfl.ch/education/continuing-education/effective-programming-in-scala/" + %} + <div class="inner-box"> + {{content}} + </div> + </div> + </div> +</section> diff --git a/_layouts/overview.html b/_layouts/overview.html deleted file mode 100644 index 2d255c52bf..0000000000 --- a/_layouts/overview.html +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: inner-page-parent -includeTOC: true -includeCollectionTOC: true ---- - -{% 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 %} - -<!-- Main content --> -{% include inner-page-main-content.html %} diff --git a/_layouts/overviews.html b/_layouts/overviews.html index 2436cf1855..2568640141 100644 --- a/_layouts/overviews.html +++ b/_layouts/overviews.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent +layout: root-index-layout --- {% if page.language %} @@ -44,6 +44,11 @@ <h2>{{ category.category }}</h2> {% if category.description %}<p>{{ category.description }}</p>{% endif %} <div class="card-group"> {% for overview in category.overviews %} + {% if overview.root %} + {% assign overviewroot = overview.root %} + {% else %} + {% assign overviewroot = 'overviews/' %} + {% endif %} <div class="white-card"> <div class="card-wrap"> <div class="card-header"> @@ -52,8 +57,8 @@ <h2>{{ category.category }}</h2> <div class="icon"><i class="fa fa-{{ overview.icon }}" aria-hidden="true"></i></div> </div> {% endif %} - {% capture originalOverviewUrl %}/overviews/{{ overview.url }}{% endcapture %} - {% capture translatedOverviewId %}/{{page.language}}/overviews/{{ overview.url | remove_first: '.html' }}{% endcapture %} + {% capture originalOverviewUrl %}/{{overviewroot}}{{ overview.url }}{% endcapture %} + {% capture translatedOverviewId %}/{{page.language}}/{{overviewroot}}{{ overview.url | remove_first: '.html' }}{% endcapture %} {% assign translatedOverview = site.documents | where: 'id', translatedOverviewId | first %} <a href="{{ translatedOverview.url | default: originalOverviewUrl }}"><h3>{{ overview.title }}</h3></a> </div> @@ -65,8 +70,8 @@ <h2>{{ category.category }}</h2> <strong>Contents</strong> <ul class="subdocs"> {% for doc in overview.subdocs %} - {% capture originalDocUrl %}/overviews/{{ doc.url }}{% endcapture %} - {% capture translatedDocId %}/{{page.language}}/overviews/{{ doc.url | remove_first: '.html' }}{% endcapture %} + {% capture originalDocUrl %}/{{overviewroot}}{{ doc.url }}{% endcapture %} + {% capture translatedDocId %}/{{page.language}}/{{overviewroot}}{{ doc.url | remove_first: '.html' }}{% endcapture %} {% assign translatedDoc = site.documents | where: 'id', translatedDocId | first %} <li><a href="{{ translatedDoc.url | default: originalDocUrl }}">{{ doc.title }}</a></li> {% endfor %} diff --git a/_layouts/inner-page-parent-dropdown.html b/_layouts/root-content-layout.html similarity index 70% rename from _layouts/inner-page-parent-dropdown.html rename to _layouts/root-content-layout.html index 1bd7ae1f23..b45513d346 100644 --- a/_layouts/inner-page-parent-dropdown.html +++ b/_layouts/root-content-layout.html @@ -1,11 +1,10 @@ {% include headertop.html %} {% include headerbottom.html %} -{% if page.new-version %}<a class="new-version-notice" href="{{ page.new-version }}">This page has a new version.</a>{% endif %} - - <div class="navigation-fade-screen"></div> +{% include alert-banner.html message_id='disabled' message=site.data.messages.scam-banner %} + {% include navbar-inner.html %} <main id="inner-main"> @@ -13,17 +12,6 @@ <section class="title-page"> <div class="wrap"> <div class="content-title-documentation"> - {% if page.scala3 %} - <div class="wip-notice"> - <h4>Work in Progress</h4> - <p> - We are still in the process of writing the documentation for Scala 3. - You can <a href="/scala3/contribute-to-docs.html">help us</a> to improve the documentation. - </p> - <p>Are you searching for the <a href="/">Scala 2 documentation</a>?</p> - </div> - {% endif %} - <div class="titles"> {% if page.overview-name %} <div class="supertitle">{{ page.overview-name }}</div> diff --git a/_layouts/root-index-layout.html b/_layouts/root-index-layout.html new file mode 100644 index 0000000000..c236bb7b2f --- /dev/null +++ b/_layouts/root-index-layout.html @@ -0,0 +1,33 @@ +{% include headertop.html %} {% include headerbottom.html %} + +<div class="navigation-fade-screen"></div> + +{% include alert-banner.html message_id='disabled' message=site.data.messages.scam-banner %} + +{% include navbar-inner.html %} + +<main id="inner-main"> + + <!-- Title --> + <section class="title-page"> + <div class="wrap"> + <div class="content-title-documentation"> + + <h1>{{page.title}}</h1> + <div class="search-container"> + <div class="icon-search"> + <i class="fa fa-search"></i> + </div> + <input type="text" class="doc-search" id="doc-search-bar" placeholder="Search in doc..."> + <ul class="result-container" id="result-container" style="display: none;"></ul> + </div> + </div> + </div> + </section> + + {% comment %}Specific content from child layouts{% endcomment %} + {{content}} + +</main> + +{% include footer.html %} diff --git a/_layouts/singlepage-overview.html b/_layouts/singlepage-overview.html index 08e67dde8e..867e4af164 100644 --- a/_layouts/singlepage-overview.html +++ b/_layouts/singlepage-overview.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeTOC: true --- @@ -8,6 +8,23 @@ <div class="content-primary documentation"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} + {% if page.versionSpecific %} + {% if page.scala3 %} + {% assign versionSpecificLang = 'scala3' %} + {% else if page.scala2 %} + {% assign versionSpecificLang = 'scala2' %} + {% endif %} + {% include version-specific-notice.html language=versionSpecificLang %} + {% endif %} + <noscript> + <blockquote> + <span style="font-weight: bold;">Info:</span> JavaScript is currently disabled, code tabs will still work, + but preferences will not be remembered. + </blockquote> + </noscript> {{content}} </div> diff --git a/_layouts/sip-meeting-results.html b/_layouts/sip-meeting-results.html new file mode 100644 index 0000000000..92ef1999f9 --- /dev/null +++ b/_layouts/sip-meeting-results.html @@ -0,0 +1,31 @@ +--- +layout: sips +--- + +<p>The Committee discussed and voted on the proposals listed below.</p> + +<table> + <thead> + <tr><th>Proposal</th><th>Result</th></tr> + </thead> + <tbody> + {% for proposal in page.proposals %} + <tr> + <td><a href="{{ proposal.url }}">{{proposal.name}}</a></td> + <td> + {% if proposal.result == 'rejected' %} + Rejected <span class="fa fa-xmark"></span> + {% elsif proposal.result == 'accepted' %} + Accepted <span class="fa fa-check"></span> + {% else %} + Under Review <span class="fa fa-clipboard-list"></span> + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> +</table> + +<div> + {{content}} +</div> diff --git a/_layouts/sip.html b/_layouts/sip.html index 3d778db55e..90174048d9 100644 --- a/_layouts/sip.html +++ b/_layouts/sip.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeTOC: true --- @@ -14,19 +14,32 @@ <!-- TOC --> <div class="content-nav"> <div class="inner-box sidebar-toc-wrapper sip-toc" style=""> - {% if page.vote-status %} - <h5 class="contents">SIP Committee Decision</h5> - <div class="tag" style="background-color: {{ site.data.sip-data[page.vote-status].color }}">{{ site.data.sip-data[page.vote-status].text }}</div> - <p class="vote-text" style="color: {{ site.data.sip-data[page.vote-status].color }}"> - {% if page.vote-text %}{{ page.vote-text }}{% endif %} - </p> - {% endif %} + <h5 class="contents">Status</h5> + {% if page.stage == "implementation" %} + <p> + This proposal has been accepted by the committee. + {% if page.status == "waiting-for-implementation" %} + An implementation is welcome in the compiler. + {% else %} + It might be available as an experimental feature in the latest version of the compiler. + {% endif %} + </p> + {% else if page.stage == "completed" %} + <p> + This proposal has been implemented, + {% if page.status == "accepted" %} + it will be available in the next minor release of the compiler. + {% else if page.status == "shipped" %} + it is available in the latest version of the compiler. + {% endif %} + </p> + {% endif %} <h5 class="contents">SIP Contents</h5> <div class="inner-toc" id="sidebar-toc"> <div id="toc"></div> </div> <hr> - <div class="help-us"><a href="https://github.com/scala/docs.scala-lang/blob/main/{% if page.collection %}{{ page.relative_path }}{% else %}{{ page.path }}{% endif %}"><i class="fa fa-pencil" aria-hidden="true"></i> Problem with this page?<br>     Please help us fix it!</a></div> + <div class="help-us"><a href="https://github.com/scala/improvement-proposals/blob/main/content/{{ page.name }}"><i class="fa fa-pencil" aria-hidden="true"></i> Problem with this page?<br>     Please help us fix it!</a></div> </div> </div> diff --git a/_layouts/sips.html b/_layouts/sips.html index de3535a24e..b7da7039b5 100644 --- a/_layouts/sips.html +++ b/_layouts/sips.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout --- <section class="content"> @@ -17,27 +17,17 @@ <h5 class="contents" style="line-height: 1.1; margin-bottom: 18px;">Scala Improv <div style="text-align: center; margin-bottom: 24px;"> <a href="{{ site.baseurl }}/sips/all.html" class="button">View all SIPs</a> </div> - <h5 class="contents" style="margin-bottom: 0;">SIP/SPP Meetings</h5> + <h5 class="contents" style="margin-bottom: 0;">SIP Meetings</h5> <div id="sip-meetings"> - <div id="upcoming-meeting"> - <h6>Next meeting:</h6> - <div id="meeting-title">  <span class="tag" style="background-color: #dc322f; top: 0px; margin-bottom: 6px;">Live</span></div> - <div id="meeting-time"></div> - <div id="live-on-youtube"> - Tune in and join us, live! Ask the SIP committee questions, weigh in, or just follow along. - <br><a id="watch-meeting">Watch →</a> - </div> - </div> <div id="meeting-minutes"> <ul> - <li><a href="{{ site.baseurl }}/sips/minutes-list.html">Meeting Minutes</a></li> - <li><a href="https://www.youtube.com/channel/UCn_8OeZlf5S6sqCqntAvaIw/videos?view=2&sort=dd&shelf_id=1&live_view=502">Meeting Video Registrations (YouTube)</a></li> + <li><a href="{{ site.baseurl }}{% link _sips/meeting-results.md %}">Meeting Results</a></li> </ul> </div> </div> - <h5 class="contents" style="margin-bottom: 0;">Writing a SIP</h5> + <h5 class="contents" style="margin-bottom: 0;">Submitting a SIP</h5> <ul> - <li><a href="{{ site.baseurl }}/sips/sip-submission.html">Submitting a SIP</a></li> + <li><a href="{% link _sips/process-specification.md %}">Process Specification</a></li> <li><a href="{{ site.baseurl }}/sips/sip-tutorial.html">Tutorial: Writing a SIP</a></li> </ul> <div class="help-us"><a href="https://github.com/scala/docs.scala-lang/blob/main/{% if page.collection %}{{ page.relative_path }}{% else %}{{ page.path }}{% endif %}"><i class="fa fa-pencil" aria-hidden="true"></i> Problem with this page?<br>     Please help us fix it!</a></div> diff --git a/_layouts/style-guide.html b/_layouts/style-guide.html index b5e7741522..41e39d8709 100644 --- a/_layouts/style-guide.html +++ b/_layouts/style-guide.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeCollectionTOC: true includeTOC: true --- @@ -9,6 +9,9 @@ <div class="content-primary documentation style-guide"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} {{content}} </div> diff --git a/_layouts/tour.html b/_layouts/tour.html index ce65fd5098..33e67984de 100644 --- a/_layouts/tour.html +++ b/_layouts/tour.html @@ -1,5 +1,5 @@ --- -layout: inner-page-parent-dropdown +layout: root-content-layout includeTOC: true includeCollectionTOC: true --- @@ -9,6 +9,15 @@ <div class="content-primary documentation"> <div class="inner-box"> <div class="toc-context"> + {% if page.new-version %} + {% include outdated-notice.html new-version=page.new-version %} + {% endif %} + <noscript> + <blockquote> + <span style="font-weight: bold;">Info:</span> JavaScript is currently disabled, code tabs will still work, + but preferences will not be remembered. + </blockquote> + </noscript> {{content}} </div> diff --git a/_layouts/training.html b/_layouts/training.html deleted file mode 100644 index 3274549b11..0000000000 --- a/_layouts/training.html +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: inner-page-parent ---- - -{% include events-training-list-top.html collection=paginator.trainings %} - </div> - - {% include paginator.html urlPath="training" %} -</div> \ No newline at end of file diff --git a/_overviews/FAQ/index.md b/_overviews/FAQ/index.md index fe4b457451..a3aa167c98 100644 --- a/_overviews/FAQ/index.md +++ b/_overviews/FAQ/index.md @@ -41,15 +41,18 @@ reasons for wanting to learn Scala. ### Should I learn Scala 2, or Scala 3? -Scala 3 was released in May 2021. Because Scala 3 is still so new, -most Scala jobs are Scala 2 jobs; most Scala books and online learning -materials cover Scala 2; tooling and library support is strongest in -Scala 2; and so on. +Don't sweat the decision too much. You can't go far wrong either +way. It isn't that hard to switch later, in either direction. -Thus, Scala 2 remains a common and reasonable choice. +Regardless, you should choose Scala 3 unless you have a specific reason +to need 2. Scala 3 is the future, and it's the best version for +falling in love with the language and everything it has to offer. +Scala 3 has plenty of books, plenty of libraries, and high quality +tooling. -Some books that cover Scala 3 are already available; more are on the -way. In time, there will be more and more Scala 3 jobs as well. +That said, many Scala jobs are still Scala 2 jobs. In most cases, the +cause of that is simply inertia, especially at large shops. (But it can +sometimes be due to availability of specific libraries.) ### Where are Scala jobs advertised? @@ -60,7 +63,7 @@ In short, the only officially sanctioned place is the \#jobs channel ### Who's behind Scala? -This is answered [on the community page](https://www.scala-lang.org/community/#whos-behind-scala). +This is answered [on the Governance page](https://www.scala-lang.org/governance/). ### Can I use the Scala logo? @@ -68,6 +71,10 @@ See [scala/scala-lang#1040](https://github.com/scala/scala-lang/issues/1040). ## Technical questions +### What IDEs are available for Scala? + +See [this doc page](https://docs.scala-lang.org/getting-started/scala-ides.html). + ### What compiler flags are recommended? The list of available options is @@ -78,11 +85,11 @@ individual to individual. `-Xlint` is valuable to enable. Some brave people enable `-Werror` (formerly `-Xfatal-warnings`) to make warnings fatal. -[sbt-tpolecat](https://github.com/DavidGregory084/sbt-tpolecat) is an +[sbt-tpolecat](https://github.com/typelevel/sbt-tpolecat) is an opinionated sbt plugin that sets many options automatically, depending on Scala version; you can see -[here](https://github.com/DavidGregory084/sbt-tpolecat/blob/master/src/main/scala/io/github/davidgregory084/TpolecatPlugin.scala) -what it sets. Some of the choices it makes are oriented towards +[here](https://github.com/typelevel/sbt-tpolecat/blob/main/plugin/src/main/scala/io/github/davidgregory084/TpolecatPlugin.scala) +what it sets. Some choices it makes are oriented towards pure-functional programmers. ### How do I find what some symbol means or does? @@ -108,20 +115,34 @@ Your sbt 1.x build definition is always a Scala 2.12 program. Regardless, in your `build.sbt`, you can set `scalaVersion` to whichever available distribution you want and your program code will be compiled with that version. -### I want Scala 3.1.1 (etc); why does std lib say it's using Scala 2.13? +### I want Scala 3. Why does `versionNumberString` say I'm on 2.13? -Scala 3 currently uses the Scala 2.13 library by leveraging its seamless -interoperability. Note that it does not necessarily ingest the latest -version of the Scala 2.13 library. +To aid migration, Scala 3 currently uses the Scala 2.13 library as-is, +with only minor supplements. That's why `versionString` and +`versionNumberString` report that Scala 2 is in use: ``` -Welcome to Scala 3.1.1 (17.0.2, Java OpenJDK 64-Bit Server VM). +Welcome to Scala 3.3.4 (17.0.3, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help. -scala> util.Properties.versionString -val res0: String = version 2.13.6 +scala> util.Properties.versionNumberString +val res0: String = 2.13.15 ``` +Note that even the latest Scala 3 version might not use the very +latest Scala 2 standard library, since the 3 and 2 release schedules +aren't coordinated. + +So how do you ask for the Scala 3 version number? Scala 3 offers +`dotty.tools.dotc.config.Properties.versionNumberString`, but only if +you have scala3-compiler on the classpath. So that works in the Scala 3 +REPL, but won't work in typical Scala 3 application code. + +For an alternative way to detect the Scala 3 version, see +[this gist](https://gist.github.com/romanowski/de14691cab7340134e197419bc48919a). + +There is a proposal to provide something easier at [scala/scala3#22144](https://github.com/scala/scala3/issues/22144). + ### Why is my (abstract or overridden) `val` null? <!-- this is left over from a previous version of the FAQ. @@ -136,11 +157,11 @@ See [this]({{ site.baseurl }}/tutorials/FAQ/initialization-order.html). See the [Scala 2.13 Collections Guide](https://docs.scala-lang.org/overviews/collections-2.13/introduction.html). -### What are context bounds (`[T : Foo]`)? +### What are context bounds? -It's syntactic sugar for an `implicit` parameter of type `Foo[T]`. +It's syntactic sugar for a context parameter (an `implicit` parameter in Scala 2, or a `using` parameter in Scala 3). -More details in this [Stack Overflow answer](https://stackoverflow.com/a/4467012). +More details in this [section of the Scala 3 Book](https://docs.scala-lang.org/scala3/book/ca-context-bounds.html) and this [Stack Overflow answer](https://stackoverflow.com/a/4467012). ### How does `for / yield` work? @@ -179,6 +200,22 @@ be written using the `_` syntax. See also [SLS 6.23.2](https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#placeholder-syntax-for-anonymous-functions). +### Why couldn't Scala infer the correct type in my code? + +It is difficult to generalize about type inference, because various features of the language +affect how your code is construed. There may be several ways to rewrite your code to make +the types fall out naturally. + +The most straightforward workaround is to supply explicit types in your code. + +That may involve specifying an explicit type to a definition, or a type argument to a method. + +Type inference is greatly improved in Scala 3. If Scala 2 doesn't compile your code, it's worth trying with Scala 3. + +Sometimes, using multiple parameter lists helps inference, as explained in [this section of the language tour](https://docs.scala-lang.org/tour/multiple-parameter-lists.html#drive-type-inference). + +For common questions about type inference involving `toSet`, see the discussions on [this ticket](https://github.com/scala/bug/issues/7743) and a related [Q&A](https://stackoverflow.com/questions/5544536/in-scala-2-type-inference-fails-on-set-made-with-toset). + ### Can I chain or nest implicit conversions? Not really, but you can [make it work](https://stackoverflow.com/a/5332804). @@ -197,7 +234,7 @@ So for example, a `List[Int]` in Scala code will appear to Java as a appear as type parameters, but couldn't they appear as their boxed equivalents, such as `List[java.lang.Integer]`? -One would hope so, but doing it that way was tried and it proved impossible. +One would hope so, but doing it that way was tried, and it proved impossible. [This SO question](https://stackoverflow.com/questions/11167430/why-are-primitive-types-such-as-int-erased-to-object-in-scala) sadly lacks a concise explanation, but it does link to past discussions. @@ -211,18 +248,22 @@ differ from a function value such as: val square: Int => Int = x => x * x -For Scala 2, there is a [complete answer on Stack Overflow](https://stackoverflow.com/a/2530007/4111404) +For **Scala 2**, there is a [complete answer on Stack Overflow](https://stackoverflow.com/a/2530007/4111404) and a [summary with practical differences](https://tpolecat.github.io/2014/06/09/methods-functions.html). -Note that in **Scala 3** the differences are fewer; -for example, they will be able to -[accept implicit parameters](/scala3/reference/contextual/context-functions.html) -as well as [type parameters](/scala3/reference/new-types/polymorphic-function-types.html). +In **Scala 3**, the differences are fewer. +[Context functions]({{ site.scala3ref }}/contextual/context-functions.html) +accept given parameters and +[polymorphic functions]({{ site.scala3ref }}/new-types/polymorphic-function-types.html) +have type parameters. -Nevertheless, it is still recommended to use methods most of the time, -unless you absolutely need a function. And, thanks to -[eta-expansion](https://stackoverflow.com/questions/39445018/what-is-the-eta-expansion-in-scala) -you rarely would need to define a function rather than a method. +It's standard to use methods most of the time, +except when a function value is actually needed. +[Eta-expansion](https://stackoverflow.com/questions/39445018/what-is-the-eta-expansion-in-scala), +converts methods to functions when needed. +For example, a method such as `map` expects a function, +but even if you `def square` as shown above, you can +still `xs.map(square)`. ### What's the difference between types and classes? @@ -240,16 +281,27 @@ for multiple reasons, most notoriously For an in-depth treatment of types vs. classes, see the blog post ["There are more types than classes"](https://typelevel.org/blog/2017/02/13/more-types-than-classes.html). +### Should I declare my parameterless method with or without parentheses? + +In other words, should one write `def foo()` or just `def foo`? + +Answer: by convention, the former is used to indicate that a method +has side effects. + +For more details, see the Scala Style Guide, [here](https://docs.scala-lang.org/style/naming-conventions.html#parentheses). + ### How can a method in a superclass return a value of the “current” type? -First, note that using `this.type` won't work. People often try that, -but `this.type` means "the singleton type of this instance", a -different and too-specific meaning. Only `this` itself has the -type `this.type`; other instances do not. +Using `this.type` will only work if you are returning `this` itself. +`this.type` means "the singleton type of this instance". Only `this` +itself has the type `this.type`; other instances of the same class do +not. + +What does work for returning other values of the same type? -What does work? Possible solutions include F-bounded polymorphism -_(familiar to Java programmers)_, type members, -and the [typeclass pattern](http://tpolecat.github.io/2013/10/12/typeclass.html). +Possible solutions include F-bounded polymorphism _(familiar to Java +programmers)_, type members, and the [typeclass +pattern](http://tpolecat.github.io/2013/10/12/typeclass.html). This [blog post](http://tpolecat.github.io/2015/04/29/f-bounds.html) argues against F-bounds and in favor of typeclasses; @@ -288,7 +340,7 @@ tool you are using. For sbt, add it to `.jvmopts`. If the stack overflow doesn't go away no matter how much stack you give the compiler, then it's a compiler bug. Please report it on the [Scala 2 bug tracker](https://github.com/scala/bug/issues) or [Scala 3 -bug tracker](https://github.com/lampepfl/dotty/issues), but check +bug tracker](https://github.com/scala/scala3/issues), but check first if it's a duplicate of an existing ticket. ### I set a setting in sbt but nothing happened. Why? @@ -299,14 +351,14 @@ setting in a multi-project build. For example, if you add this to your `build.sbt`: - scalaVersion := "2.13.7" + scalaVersion := "2.13.16" that's a "bare" setting, and you might expect it to apply build-wide. But it doesn't. _It only applies to the root project._ In many cases one should instead write: - ThisBuild / scalaVersion := "2.13.7" + ThisBuild / scalaVersion := "2.13.16" Other possibilities include: diff --git a/_overviews/FAQ/initialization-order.md b/_overviews/FAQ/initialization-order.md index ece62c6b9f..ebe07308c6 100644 --- a/_overviews/FAQ/initialization-order.md +++ b/_overviews/FAQ/initialization-order.md @@ -7,80 +7,89 @@ permalink: /tutorials/FAQ/:title.html ## Example -To understand the problem, let's pick the following concrete example. +The following example illustrates how classes in a subclass relation +witness the initialization of two fields which are inherited from +their top-most parent. The values are printed during the constructor +of each class, that is, when an instance is initialized. abstract class A { val x1: String val x2: String = "mom" - println("A: " + x1 + ", " + x2) + println(s"A: $x1, $x2") } class B extends A { val x1: String = "hello" - println("B: " + x1 + ", " + x2) + println(s"B: $x1, $x2") } class C extends B { override val x2: String = "dad" - println("C: " + x1 + ", " + x2) + println(s"C: $x1, $x2") } -Let's observe the initialization order through the Scala REPL: +In the Scala REPL we observe: scala> new C A: null, null B: hello, null C: hello, dad -Only when we get to the constructor of `C` are both `x1` and `x2` initialized. Therefore, constructors of `A` and `B` risk running into `NullPointerException`s. +Only when we get to the constructor of `C` are both `x1` and `x2` properly initialized. +Therefore, constructors of `A` and `B` risk running into `NullPointerException`s, +since fields are null-valued until set by a constructor. ## Explanation -A 'strict' or 'eager' val is one which is not marked lazy. -In the absence of "early definitions" (see below), initialization of strict vals is done in the following order. +A "strict" or "eager" val is a `val` which is not a `lazy val`. +Initialization of strict vals is done in the following order: 1. Superclasses are fully initialized before subclasses. -2. Otherwise, in declaration order. - -Naturally when a val is overridden, it is not initialized more than once. So though x2 in the above example is seemingly defined at every point, this is not the case: an overridden val will appear to be null during the construction of superclasses, as will an abstract val. - -There is a compiler flag which can be useful for identifying this situation: - -**-Xcheckinit**: Add runtime check to field accessors. - -It is inadvisable to use this flag outside of testing. It adds significantly to the code size by putting a wrapper around all potentially uninitialized field accesses: the wrapper will throw an exception rather than allow a null (or 0/false in the case of primitive types) to silently appear. Note also that this adds a *runtime* check: it can only tell you anything about code paths which you exercise with it in place. - -Using it on the opening example: - - % scalac -Xcheckinit a.scala - % scala -e 'new C' - scala.UninitializedFieldError: Uninitialized field: a.scala: 13 - at C.x2(a.scala:13) - at A.<init>(a.scala:5) - at B.<init>(a.scala:7) - at C.<init>(a.scala:12) - -### Solutions ### +2. Within the body or "template" of a class, vals are initialized in declaration order, + the order in which they are written in source. + +When a `val` is overridden, it's more precise to say that its accessor method (the "getter") is overridden. +So the access to `x2` in class `A` invokes the overridden getter in class `C`. +That getter reads the underlying field `C.x2`. +This field is not yet initialized during the construction of `A`. + +## Mitigation + +The [`-Wsafe-init` compiler flag](https://docs.scala-lang.org/scala3/reference/other-new-features/safe-initialization.html) +in Scala 3 enables a compile-time warning for accesses to uninitialized fields: + + -- Warning: Test.scala:8:6 ----------------------------------------------------- + 8 | val x1: String = "hello" + | ^ + | Access non-initialized value x1. Calling trace: + | ├── class B extends A { [ Test.scala:7 ] + | │ ^ + | ├── abstract class A { [ Test.scala:1 ] + | │ ^ + | └── println(s"A: $x1, $x2") [ Test.scala:5 ] + | ^^ + +In Scala 2, the `-Xcheckinit` flag adds runtime checks in the generated bytecode to identify accesses of uninitialized fields. +That code throws an exception when an uninitialized field is referenced +that would otherwise be used as a `null` value (or `0` or `false` in the case of primitive types). +Note that these runtime checks only report code that is actually executed at runtime. +Although these checks can be helpful to find accesses to uninitialized fields during development, +it is never advisable to enable them in production code due to the performance cost. + +## Solutions Approaches for avoiding null values include: -#### Use lazy vals #### - - abstract class A { - val x1: String - lazy val x2: String = "mom" +### Use class / trait parameters + abstract class A(val x1: String, val x2: String = "mom") { println("A: " + x1 + ", " + x2) } - class B extends A { - lazy val x1: String = "hello" - + class B(x1: String = "hello", x2: String = "mom") extends A(x1, x2) { println("B: " + x1 + ", " + x2) } - class C extends B { - override lazy val x2: String = "dad" - + class C(x2: String = "dad") extends B(x2 = x2) { println("C: " + x1 + ", " + x2) } // scala> new C @@ -88,31 +97,29 @@ Approaches for avoiding null values include: // B: hello, dad // C: hello, dad -Usually the best answer. Unfortunately you cannot declare an abstract lazy val. If that is what you're after, your options include: +Values passed as parameters to the superclass constructor are available in its body. -1. Declare an abstract strict val, and hope subclasses will implement it as a lazy val or with an early definition. If they do not, it will appear to be uninitialized at some points during construction. -2. Declare an abstract def, and hope subclasses will implement it as a lazy val. If they do not, it will be re-evaluated on every access. -3. Declare a concrete lazy val which throws an exception, and hope subclasses override it. If they do not, it will... throw an exception. +Scala 3 also [supports trait parameters](https://docs.scala-lang.org/scala3/reference/other-new-features/trait-parameters.html). -An exception during initialization of a lazy val will cause the right hand side to be re-evaluated on the next access: see SLS 5.2. +Note that overriding a `val` class parameter is deprecated / disallowed in Scala 3. +Doing so in Scala 2 can lead to surprising behavior. -Note that using multiple lazy vals creates a new risk: cycles among lazy vals can result in a stack overflow on first access. +### Use lazy vals -#### Use early definitions #### abstract class A { - val x1: String - val x2: String = "mom" + lazy val x1: String + lazy val x2: String = "mom" println("A: " + x1 + ", " + x2) } - class B extends { - val x1: String = "hello" - } with A { + class B extends A { + lazy val x1: String = "hello" + println("B: " + x1 + ", " + x2) } - class C extends { - override val x2: String = "dad" - } with B { + class C extends B { + override lazy val x2: String = "dad" + println("C: " + x1 + ", " + x2) } // scala> new C @@ -120,45 +127,54 @@ Note that using multiple lazy vals creates a new risk: cycles among lazy vals ca // B: hello, dad // C: hello, dad -Early definitions are a bit unwieldy, there are limitations as to what can appear and what can be referenced in an early definitions block, and they don't compose as well as lazy vals: but if a lazy val is undesirable, they present another option. They are specified in SLS 5.1.6. +Note that abstract `lazy val`s are supported in Scala 3, but not in Scala 2. +In Scala 2, you can define an abstract `val` or `def` instead. -Note that early definitions are deprecated in Scala 2.13; they will be replaced by trait parameters in Scala 3. So, early definitions are not recommended for use if future compatibility is a concern. +An exception during initialization of a lazy val will cause the right-hand side to be re-evaluated on the next access; see SLS 5.2. -#### Use constant value definitions #### - abstract class A { - val x1: String - val x2: String = "mom" +Note that using multiple lazy vals incurs a new risk: cycles among lazy vals can result in a stack overflow on first access. +When lazy vals are annotated as thread-safe in Scala 3, they risk deadlock. - println("A: " + x1 + ", " + x2) - } - class B extends A { - val x1: String = "hello" - final val x3 = "goodbye" +### Use a nested object - println("B: " + x1 + ", " + x2) - } - class C extends B { - override val x2: String = "dad" +For purposes of initialization, an object that is not top-level is the same as a lazy val. - println("C: " + x1 + ", " + x2) +There may be reasons to prefer a lazy val, for example to specify the type of an implicit value, +or an object where it is a companion to a class. Otherwise, the most convenient syntax may be preferred. + +As an example, uninitialized state in a subclass may be accessed during construction of a superclass: + + class Adder { + var sum = 0 + def add(x: Int): Unit = sum += x + add(1) // in LogAdder, the `added` set is not initialized yet + } + class LogAdder extends Adder { + private var added: Set[Int] = Set.empty + override def add(x: Int): Unit = { added += x; super.add(x) } } - abstract class D { - val c: C - val x3 = c.x3 // no exceptions! - println("D: " + c + " but " + x3) + +In this case, the state can be initialized on demand by wrapping it in a local object: + + class Adder { + var sum = 0 + def add(x: Int): Unit = sum += x + add(1) } - class E extends D { - val c = new C - println(s"E: ${c.x1}, ${c.x2}, and $x3...") + class LogAdder extends Adder { + private object state { + var added: Set[Int] = Set.empty + } + import state._ + override def add(x: Int): Unit = { added += x; super.add(x) } } - //scala> new E - //D: null but goodbye - //A: null, null - //B: hello, null - //C: hello, dad - //E: hello, dad, and goodbye... -Sometimes all you need from an interface is a compile-time constant. +### Early definitions: deprecated + +Scala 2 supports early definitions, but they are deprecated in Scala 2.13 and unsupported in Scala 3. +See the [migration guide](https://docs.scala-lang.org/scala3/guides/migration/incompat-dropped-features.html#early-initializer) for more information. + +Constant value definitions (specified in SLS 4.1 and available in Scala 2) +and inlined definitions (in Scala 3) can work around initialization order issues +because they can supply constant values without evaluating an instance that is not yet initialized. -Constant values are stricter than strict and earlier than early definitions and have even more limitations, -as they must be constants. They are specified in SLS 4.1. diff --git a/_overviews/collections-2.13/arrays.md b/_overviews/collections-2.13/arrays.md index 64d96a95db..32f9fb0584 100644 --- a/_overviews/collections-2.13/arrays.md +++ b/_overviews/collections-2.13/arrays.md @@ -14,23 +14,40 @@ permalink: /overviews/collections-2.13/:title.html [Array](https://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) +{% tabs arrays_1 %} +{% tab 'Scala 2 and 3' for=arrays_1 %} +```scala +scala> val a1 = Array(1, 2, 3) +val a1: Array[Int] = Array(1, 2, 3) + +scala> val a2 = a1.map(_ * 3) +val a2: Array[Int] = Array(3, 6, 9) + +scala> val a3 = a2.filter(_ % 2 != 0) +val a3: Array[Int] = Array(3, 9) + +scala> a3.reverse +val res0: Array[Int] = Array(9, 3) +``` +{% endtab %} +{% endtabs %} Given that Scala arrays are represented just like Java arrays, how can these additional features be supported in Scala? The Scala array implementation makes systematic use of implicit conversions. In Scala, 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.ArraySeq`, which is a subclass of `Seq`. Here you see it in action: - scala> val seq: collection.Seq[Int] = a1 - seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) - scala> val a4: Array[Int] = seq.toArray - a4: Array[Int] = Array(1, 2, 3) - scala> a1 eq a4 - res1: Boolean = false +{% tabs arrays_2 %} +{% tab 'Scala 2 and 3' for=arrays_2 %} +```scala +scala> val seq: collection.Seq[Int] = a1 +val seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) + +scala> val a4: Array[Int] = seq.toArray +val a4: Array[Int] = Array(1, 2, 3) + +scala> a1 eq a4 +val res1: Boolean = false +``` +{% endtab %} +{% endtabs %} The interaction above demonstrates that arrays are compatible with sequences, because there's an implicit conversion from arrays to `ArraySeq`s. To go the other way, from an `ArraySeq` to an `Array`, you can use the `toArray` method defined in `Iterable`. The last REPL line above shows that wrapping and then unwrapping with `toArray` produces a copy of the original array. @@ -38,82 +55,188 @@ There is yet another implicit conversion that gets applied to arrays. This conve The difference between the two implicit conversions on arrays is shown in the next REPL dialogue: - scala> val seq: collection.Seq[Int] = a1 - seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) - scala> seq.reverse - res2: scala.collection.Seq[Int] = ArraySeq(3, 2, 1) - scala> val ops: collection.ArrayOps[Int] = a1 - ops: scala.collection.ArrayOps[Int] = scala.collection.ArrayOps@2d7df55 - scala> ops.reverse - res3: Array[Int] = Array(3, 2, 1) - -You see that calling reverse on `seq`, which is an `ArraySeq`, will give again a `ArraySeq`. That's logical, because arrayseqs 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 `ArraySeq`. Normally, you'd never define a value of class `ArrayOps`. You'd just call a `Seq` method on an array: +{% tabs arrays_3 %} +{% tab 'Scala 2 and 3' for=arrays_3 %} +```scala +scala> val seq: collection.Seq[Int] = a1 +val seq: scala.collection.Seq[Int] = ArraySeq(1, 2, 3) - scala> a1.reverse - res4: Array[Int] = Array(3, 2, 1) +scala> seq.reverse +val res2: scala.collection.Seq[Int] = ArraySeq(3, 2, 1) -The `ArrayOps` object gets inserted automatically by the implicit conversion. So the line above is equivalent to +scala> val ops: collection.ArrayOps[Int] = a1 +val ops: scala.collection.ArrayOps[Int] = scala.collection.ArrayOps@2d7df55 - scala> intArrayOps(a1).reverse - res5: Array[Int] = Array(3, 2, 1) +scala> ops.reverse +val res3: Array[Int] = Array(3, 2, 1) +``` +{% endtab %} +{% endtabs %} -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 `ArraySeq` 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 `ArraySeq` conversion. The first is defined in the `Predef` object whereas the second is defined in a class `scala.LowPriorityImplicits`, which is inherited by `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. +You see that calling reverse on `seq`, which is an `ArraySeq`, will give again a `ArraySeq`. That's logical, because arrayseqs 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`. -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. +The `ArrayOps` example above was quite artificial, intended only to show the difference to `ArraySeq`. Normally, you'd never define a value of class `ArrayOps`. You'd just call a `Seq` method on 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 - } +{% tabs arrays_4 %} +{% tab 'Scala 2 and 3' for=arrays_4 %} +```scala +scala> a1.reverse +val res4: Array[Int] = Array(3, 2, 1) +``` +{% endtab %} +{% endtabs %} -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: +The `ArrayOps` object gets inserted automatically by the implicit conversion. So the line above is equivalent to - error: cannot find class manifest for element type T - val arr = new Array[T]((arr.length + 1) / 2) - ^ +{% tabs arrays_5 %} +{% tab 'Scala 2 and 3' for=arrays_5 %} +```scala +scala> intArrayOps(a1).reverse +val res5: Array[Int] = Array(3, 2, 1) +``` +{% endtab %} +{% endtabs %} + +where `intArrayOps` is the implicit conversion that was inserted previously. This raises the question of how the compiler picked `intArrayOps` over the other implicit conversion to `ArraySeq` 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 `ArraySeq` conversion. The first is defined in the `Predef` object whereas the second is defined in a class `scala.LowPriorityImplicits`, which is inherited by `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 to 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 of help from you. To illustrate the issue, consider the following attempt to write a generic method that creates an array. + +{% tabs arrays_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=arrays_6 %} +```scala mdoc:fail +// 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 +} +``` +{% endtab %} +{% tab 'Scala 3' for=arrays_6 %} +```scala +// 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 do + arr(i / 2) = xs(i) + arr +``` +{% endtab %} +{% endtabs %} + +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 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: + +{% tabs arrays_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=arrays_7 %} +```scala +error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ +``` +{% endtab %} +{% tab 'Scala 3' for=arrays_7 %} +```scala +-- Error: ---------------------------------------------------------------------- +3 | val arr = new Array[T]((xs.length + 1) / 2) + | ^ + | No ClassTag available for T +``` +{% endtab %} +{% endtabs %} 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] = ... +{% tabs arrays_8 class=tabs-scala-version %} +{% tab 'Scala 2' for=arrays_8 %} +```scala +def evenElems[T](xs: Vector[T])(implicit m: ClassTag[T]): Array[T] = ... +``` +{% endtab %} +{% tab 'Scala 3' for=arrays_8 %} +```scala +def evenElems[T](xs: Vector[T])(using m: ClassTag[T]): Array[T] = ... +``` +{% endtab %} +{% endtabs %} 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 - } +{% tabs arrays_9 class=tabs-scala-version %} +{% tab 'Scala 2' for=arrays_9 %} +```scala +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 +} +``` +{% endtab %} +{% tab 'Scala 3' for=arrays_9 %} +```scala +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 do + arr(i / 2) = xs(i) + arr +``` +{% endtab %} +{% endtabs %} 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) +{% tabs arrays_10 %} +{% tab 'Scala 2 and 3' for=arrays_10 %} +```scala +scala> evenElems(Vector(1, 2, 3, 4, 5)) +val res6: Array[Int] = Array(1, 3, 5) + +scala> evenElems(Vector("this", "is", "a", "test", "run")) +val res7: Array[java.lang.String] = Array(this, a, run) +``` +{% endtab %} +{% endtabs %} 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) - <console>:6: error: No ClassTag available for U. - def wrap[U](xs: Vector[U]) = evenElems(xs) - ^ +{% tabs arrays_11 class=tabs-scala-version %} +{% tab 'Scala 2' for=arrays_11 %} +```scala +scala> def wrap[U](xs: Vector[U]) = evenElems(xs) +<console>:6: error: No ClassTag available for U. + def wrap[U](xs: Vector[U]) = evenElems(xs) + ^ +``` +{% endtab %} +{% tab 'Scala 3' for=arrays_11 %} +```scala +-- Error: ---------------------------------------------------------------------- +6 |def wrap[U](xs: Vector[U]) = evenElems(xs) + | ^ + | No ClassTag available for U +``` +{% endtab %} +{% endtabs %} 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] +{% tabs arrays_12 %} +{% tab 'Scala 2 and 3' for=arrays_12 %} +```scala +scala> def wrap[U: ClassTag](xs: Vector[U]) = evenElems(xs) +def wrap[U](xs: Vector[U])(implicit evidence$1: scala.reflect.ClassTag[U]): Array[U] +``` +{% endtab %} +{% endtabs %} 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]`. diff --git a/_overviews/collections-2.13/concrete-immutable-collection-classes.md b/_overviews/collections-2.13/concrete-immutable-collection-classes.md index 166f3e280d..f4d746de58 100644 --- a/_overviews/collections-2.13/concrete-immutable-collection-classes.md +++ b/_overviews/collections-2.13/concrete-immutable-collection-classes.md @@ -24,25 +24,42 @@ A [LazyList](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/colle Whereas lists are constructed with the `::` operator, lazy lists are constructed with the similar-looking `#::`. Here is a simple example of a lazy list containing the integers 1, 2, and 3: - scala> val lazyList = 1 #:: 2 #:: 3 #:: LazyList.empty - lazyList: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>) +{% tabs LazyList_1 %} +{% tab 'Scala 2 and 3' for=LazyList_1 %} +~~~scala +scala> val lazyList = 1 #:: 2 #:: 3 #:: LazyList.empty +lazyList: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>) +~~~ +{% endtab %} +{% endtabs %} The head of this lazy list is 1, and the tail of it has 2 and 3. None of the elements are printed here, though, because the list hasn’t been computed yet! Lazy lists are specified to compute lazily, and the `toString` method of a lazy list is careful not to force any extra evaluation. Below is a more complex example. It computes a lazy list 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): LazyList[Int] = a #:: fibFrom(b, a + b) - fibFrom: (a: Int,b: Int)LazyList[Int] +{% tabs LazyList_2 %} +{% tab 'Scala 2 and 3' for=LazyList_2 %} +~~~scala +scala> def fibFrom(a: Int, b: Int): LazyList[Int] = a #:: fibFrom(b, a + b) +fibFrom: (a: Int,b: Int)LazyList[Int] +~~~ +{% endtab %} +{% endtabs %} 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.LazyList[Int] = LazyList(<not computed>) - scala> fibs.toList - res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) +{% tabs LazyList_3 %} +{% tab 'Scala 2 and 3' for=LazyList_3 %} +~~~scala +scala> val fibs = fibFrom(1, 1).take(7) +fibs: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>) +scala> fibs.toList +res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) +~~~ +{% endtab %} +{% endtabs %} ## Immutable ArraySeqs @@ -56,7 +73,9 @@ and thus they can be much more convenient to write. ArraySeqs are built and updated just like any other sequence. -~~~ +{% tabs ArraySeq_1 %} +{% tab 'Scala 2 and 3' for=ArraySeq_1 %} +~~~scala scala> val arr = scala.collection.immutable.ArraySeq(1, 2, 3) arr: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3) scala> val arr2 = arr :+ 4 @@ -64,43 +83,55 @@ arr2: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3, 4) scala> arr2(0) res22: Int = 1 ~~~ +{% endtab %} +{% endtabs %} ArraySeqs are immutable, so you cannot change an element in place. However, the `updated`, `appended` and `prepended` operations create new ArraySeqs that differ from a given ArraySeq only in a single element: -~~~ +{% tabs ArraySeq_2 %} +{% tab 'Scala 2 and 3' for=ArraySeq_2 %} +~~~scala scala> arr.updated(2, 4) res26: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 4) scala> arr res27: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3) ~~~ +{% endtab %} +{% endtabs %} As the last line above shows, a call to `updated` has no effect on the original ArraySeq `arr`. -ArraySeqs store their elements in a private [Array](arrays.html). This is a compact representation that supports fast +ArraySeqs store their elements in a private [Array]({% link _overviews/collections-2.13/arrays.md %}). This is a compact representation that supports fast indexed access, but updating or adding one element is linear since it requires creating another array and copying all the original array’s elements. ## Vectors We have seen in the previous sections that `List` and `ArraySeq` are efficient data structures in some specific -use cases but they are also inefficient in other use cases: for instance, prepending an element is constant for `List`, +use cases, but they are also inefficient in other use cases: for instance, prepending an element is constant for `List`, but linear for `ArraySeq`, and, conversely, indexed access is constant for `ArraySeq` but linear for `List`. [Vector](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) is a collection type that provides good performance for all its operations. Vectors allow accessing any element of the sequence 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 ArraySeq, 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 +{% tabs Vector_1 %} +{% tab 'Scala 2 and 3' for=Vector_1 %} +~~~scala +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 +~~~ +{% endtab %} +{% endtabs %} -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.) The details of how this is accomplished [changed](https://github.com/scala/scala/pull/8534) in Scala 2.13.2, but the basic idea remains the same, as follows. +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). The details of how this is accomplished [changed](https://github.com/scala/scala/pull/8534) in Scala 2.13.2, but the basic idea remains the same, as follows. 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 2<sup>15</sup> elements, three hops for vectors with 2<sup>20</sup>, four hops for vectors with 2<sup>25</sup> elements and five hops for vectors with up to 2<sup>30</sup> 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". @@ -108,8 +139,14 @@ Like selection, functional vector updates are also "effectively constant time". 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) +{% tabs Vector_2 %} +{% tab 'Scala 2 and 3' for=Vector_2 %} +~~~scala +scala> collection.immutable.IndexedSeq(1, 2, 3) +res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) +~~~ +{% endtab %} +{% endtabs %} ## Immutable Queues @@ -117,25 +154,49 @@ A [Queue](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collecti 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() +{% tabs Queue_1 %} +{% tab 'Scala 2 and 3' for=Queue_1 %} +~~~scala +scala> val empty = scala.collection.immutable.Queue[Int]() +empty: scala.collection.immutable.Queue[Int] = Queue() +~~~ +{% endtab %} +{% endtabs %} 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) +{% tabs Queue_2 %} +{% tab 'Scala 2 and 3' for=Queue_2 %} +~~~scala +scala> val has1 = empty.enqueue(1) +has1: scala.collection.immutable.Queue[Int] = Queue(1) +~~~ +{% endtab %} +{% endtabs %} To append multiple elements to a queue, call `enqueueAll` with a collection as its argument: - scala> val has123 = has1.enqueueAll(List(2, 3)) - has123: scala.collection.immutable.Queue[Int] - = Queue(1, 2, 3) +{% tabs Queue_3 %} +{% tab 'Scala 2 and 3' for=Queue_3 %} +~~~scala +scala> val has123 = has1.enqueueAll(List(2, 3)) +has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) +~~~ +{% endtab %} +{% endtabs %} 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) +{% tabs Queue_4 %} +{% tab 'Scala 2 and 3' for=Queue_4 %} +~~~scala +scala> val (element, has23) = has123.dequeue +element: Int = 1 +has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) +~~~ +{% endtab %} +{% endtabs %} Note that `dequeue` returns a pair consisting of the element removed and the rest of the queue. @@ -143,15 +204,27 @@ Note that `dequeue` returns a pair consisting of the element removed and the res A [Range](https://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) +{% tabs Range_1 %} +{% tab 'Scala 2 and 3' for=Range_1 %} +~~~scala +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) +~~~ +{% endtab %} +{% endtabs %} 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) +{% tabs Range_2 %} +{% tab 'Scala 2 and 3' for=Range_2 %} +~~~scala +scala> 1 until 3 +res2: scala.collection.immutable.Range = Range(1, 2) +~~~ +{% endtab %} +{% endtabs %} 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. @@ -159,7 +232,7 @@ Ranges are represented in constant space, because they can be defined by just th Hash tries are a standard way to implement immutable sets and maps efficiently. [Compressed Hash-Array Mapped Prefix-trees](https://github.com/msteindorfer/oopsla15-artifact/) are a design for hash tries on the JVM which improves locality and makes sure the trees remain in a canonical and compact representation. They are supported by class [immutable.HashMap](https://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. +Hash tries strike a nice balance between reasonably fast lookups and reasonably efficient functional insertions (`+`) and deletions (`-`). That's why they underlie 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 @@ -167,11 +240,16 @@ Red-black trees are a form of balanced binary tree where some nodes are designat Scala provides implementations of immutable sets and maps that use a red-black tree internally. Access them under the names [TreeSet](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) and [TreeMap](https://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) +{% tabs Red-Black_1 %} +{% tab 'Scala 2 and 3' for=Red-Black_1 %} +~~~scala +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) +~~~ +{% endtab %} +{% endtabs %} Red-black trees are the standard implementation of `SortedSet` in Scala, because they provide an efficient iterator that returns all elements in sorted order. @@ -183,14 +261,20 @@ Internally, bit sets use an array of 64-bit `Long`s. The first `Long` in the arr 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 +{% tabs BitSet_1 %} +{% tab 'Scala 2 and 3' for=BitSet_1 %} +~~~scala +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 +~~~ +{% endtab %} +{% endtabs %} ## VectorMaps @@ -198,7 +282,9 @@ A [VectorMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/coll a map using both a `Vector` of keys and a `HashMap`. It provides an iterator that returns all the entries in their insertion order. -~~~ +{% tabs VectorMap_1 %} +{% tab 'Scala 2 and 3' for=VectorMap_1 %} +~~~scala scala> val vm = scala.collection.immutable.VectorMap.empty[Int, String] vm: scala.collection.immutable.VectorMap[Int,String] = VectorMap() @@ -211,6 +297,8 @@ vm2: scala.collection.immutable.VectorMap[Int,String] = scala> vm2 == Map(2 -> "two", 1 -> "one") res29: Boolean = true ~~~ +{% endtab %} +{% endtabs %} The first lines show that the content of the `VectorMap` keeps the insertion order, and the last line shows that `VectorMap`s are comparable with other `Map`s and that this comparison does not take the @@ -220,8 +308,14 @@ order of elements into account. A [ListMap](https://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" +{% tabs ListMap_1 %} +{% tab 'Scala 2 and 3' for=ListMap_1 %} +~~~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" +~~~ +{% endtab %} +{% endtabs %} diff --git a/_overviews/collections-2.13/concrete-mutable-collection-classes.md b/_overviews/collections-2.13/concrete-mutable-collection-classes.md index 883d1978ca..0de0bb1996 100644 --- a/_overviews/collections-2.13/concrete-mutable-collection-classes.md +++ b/_overviews/collections-2.13/concrete-mutable-collection-classes.md @@ -16,42 +16,72 @@ You've now seen the most commonly used immutable collection classes that Scala p ## Array Buffers -An [ArrayBuffer](https://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) +An [ArrayBuffer](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) 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. + +{% tabs ArrayBuffer_1 %} +{% tab 'Scala 2 and 3' for=ArrayBuffer_1 %} +~~~scala +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) +~~~ +{% endtab %} +{% endtabs %} ## List Buffers A [ListBuffer](https://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) +{% tabs ListBuffer_1 %} +{% tab 'Scala 2 and 3' for=ListBuffer_1 %} +~~~scala +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.to(List) +res37: List[Int] = List(1, 10) +~~~ +{% endtab %} +{% endtabs %} ## StringBuilders Just like an array buffer is useful for building arrays, and a list buffer is useful for building lists, a [StringBuilder](https://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 +{% tabs StringBuilders_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=StringBuilders_1 %} +~~~scala +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 +~~~ +{% endtab %} +{% tab 'Scala 3' for=StringBuilders_1 %} +~~~scala +scala> val buf = StringBuilder() +buf: StringBuilder = +scala> buf += 'a' +res38: buf.type = a +scala> buf ++= "bcdef" +res39: buf.type = abcdef +scala> buf.toString +res41: String = abcdef +~~~ +{% endtab %} +{% endtabs %} ## ArrayDeque @@ -66,48 +96,98 @@ an `ArrayBuffer`. 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) +{% tabs Queues_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=Queues_1 %} +~~~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) +~~~ +{% endtab %} +{% tab 'Scala 3' for=Queues_1 %} +~~~scala +scala> val queue = 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) +~~~ +{% endtab %} +{% endtabs %} ## Stacks A stack implements a data structure which allows to store and retrieve objects in a last-in-first-out (LIFO) fashion. It is supported by class [mutable.Stack](https://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) +{% tabs Stacks_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=Stacks_1 %} +~~~scala +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(2, 1) +scala> stack.top +res8: Int = 2 +scala> stack +res9: scala.collection.mutable.Stack[Int] = Stack(2, 1) +scala> stack.pop +res10: Int = 2 +scala> stack +res11: scala.collection.mutable.Stack[Int] = Stack(1) +~~~ +{% endtab %} +{% tab 'Scala 3' for=Stacks_1 %} +~~~scala +scala> val stack = 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(2, 1) +scala> stack.top +res8: Int = 2 +scala> stack +res9: scala.collection.mutable.Stack[Int] = Stack(2, 1) +scala> stack.pop +res10: Int = 2 +scala> stack +res11: scala.collection.mutable.Stack[Int] = Stack(1) +~~~ +{% endtab %} +{% endtabs %} ## Mutable ArraySeqs 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](https://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 `ClassTag` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.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 `ClassTag` to provide it at run-time. These issues are explained in the section on [arrays]({% link _overviews/collections-2.13/arrays.md %}). ## Hash Tables @@ -115,16 +195,22 @@ A hash table stores its elements in an underlying array, placing each item at a 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 +{% tabs Hash-Tables_1 %} +{% tab 'Scala 2 and 3' for=Hash-Tables_1 %} +~~~scala +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 +~~~ +{% endtab %} +{% endtabs %} 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. @@ -145,17 +231,23 @@ A concurrent map can be accessed by several threads at once. In addition to the | `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.| -`concurrent.Map` is a trait in the Scala collections library. Currently, it has two implementations. The first one 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). The second implementation is [TrieMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), which is a lock-free implementation of a hash array mapped trie. +`concurrent.Map` is a trait in the Scala collections library. Currently, it has two implementations. The first one is Java's `java.util.concurrent.ConcurrentMap`, which can be converted automatically into a Scala map using the [standard Java/Scala collection conversions]({% link _overviews/collections-2.13/conversions-between-java-and-scala-collections.md %}). The second implementation is [TrieMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), which is a lock-free implementation of a hash array mapped trie. ## Mutable Bitsets A mutable bit of type [mutable.BitSet](https://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) +{% tabs BitSet_1 %} +{% tab 'Scala 2 and 3' for=BitSet_1 %} +~~~scala +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) +~~~ +{% endtab %} +{% endtabs %} diff --git a/_overviews/collections-2.13/conversion-between-option-and-the-collections.md b/_overviews/collections-2.13/conversion-between-option-and-the-collections.md index 7fff2ea548..d1b2e771cf 100644 --- a/_overviews/collections-2.13/conversion-between-option-and-the-collections.md +++ b/_overviews/collections-2.13/conversion-between-option-and-the-collections.md @@ -13,6 +13,8 @@ permalink: /overviews/collections-2.13/:title.html Hence `Option` can be used everywhere an `IterableOnce` is expected, for example, when calling `flatMap` on a collection (or inside a for-comprehension) +{% tabs options_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=options_1 %} ```scala mdoc for { a <- Set(1) @@ -20,33 +22,59 @@ for { } yield (a + b) // : Set[Int] = Set(42) ``` +{% endtab %} +{% tab 'Scala 3' for=options_1 %} +```scala +for + a <- Set(1) + b <- Option(41) +yield (a + b) +// : Set[Int] = Set(42) +``` +{% endtab %} +{% endtabs %} since the operation `flatMap` on the type `Set[Int]` takes a function returning an `IterableOnce`: +{% tabs options_2 %} +{% tab 'Scala 2 and 3' for=options_2 %} ``` def flatMap[B](f: Int => IterableOnce[B]): Set[B] ``` +{% endtab %} +{% endtabs %} Although `Option` does not extend `Iterable`, there exists an [implicit conversion](https://github.com/scala/scala/blob/6c68c2825e893bb71d6dc78465ac8c6f415cbd93/src/library/scala/Option.scala#L19) between `Option` and `Iterable` - +{% tabs options_3 %} +{% tab 'Scala 2 and 3' for=options_3 %} ``` implicit def option2Iterable[A](xo: Option[A]): Iterable[A] ``` +{% endtab %} +{% endtabs %} so although `Option[A]` is not a full collection it can be _viewed_ as one. For example, +{% tabs options_4 %} +{% tab 'Scala 2 and 3' for=options_4 %} ```scala mdoc Some(42).drop(1) // : Iterable[Int] = List() ``` +{% endtab %} +{% endtabs %} expands to +{% tabs options_5 %} +{% tab 'Scala 2 and 3' for=options_5 %} ```scala mdoc Option.option2Iterable(Some(42)).drop(1) // : Iterable[Int] = List() ``` +{% endtab %} +{% endtabs %} because `drop` is not defined on `Option`. A downside of the above implicit conversion is that instead of getting back an `Option[A]` we are left with an `Iterable[A]`. For this reason, `Option`’s documentation carries the following note: diff --git a/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md b/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md index b4090053ba..d66183d84a 100644 --- a/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md +++ b/_overviews/collections-2.13/conversions-between-java-and-scala-collections.md @@ -16,49 +16,101 @@ Like Scala, Java also has a rich collections library. There are many similaritie 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 [CollectionConverters](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/jdk/CollectionConverters$.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, import them from the [CollectionConverters](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/jdk/CollectionConverters$.html) object: + +{% tabs java_scala_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=java_scala_1 %} + +```scala +scala> import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters._ +``` + +{% endtab %} +{% tab 'Scala 3' for=java_scala_1 %} + +```scala +scala> import scala.jdk.CollectionConverters.* +import scala.jdk.CollectionConverters.* +``` + +{% endtab %} +{% endtabs %} - 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 +This enables conversions between Scala collections and their corresponding Java collections by way of extension methods called `asScala` and `asJava`: -To enable these conversions, simply import them from the [CollectionConverters](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/jdk/CollectionConverters$.html) object: +{% tabs java_scala_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=java_scala_2 %} - scala> import scala.jdk.CollectionConverters._ - import scala.jdk.CollectionConverters._ +```scala +scala> import collection.mutable._ +import collection.mutable._ -This enables conversions between Scala collections and their corresponding Java collections by way of extension methods called `asScala` and `asJava`: +scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava +val jul: java.util.List[Int] = [1, 2, 3] + +scala> val buf: Seq[Int] = jul.asScala +val buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + +scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava +val m: java.util.Map[String,Int] = {abc=1, hello=2} +``` - scala> import collection.mutable._ - import collection.mutable._ +{% endtab %} +{% tab 'Scala 3' for=java_scala_2 %} - scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava - jul: java.util.List[Int] = [1, 2, 3] +```scala +scala> import collection.mutable.* +import collection.mutable.* - scala> val buf: Seq[Int] = jul.asScala - buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) +scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava +val jul: java.util.List[Int] = [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} +scala> val buf: Seq[Int] = jul.asScala +val buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + +scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava +val m: java.util.Map[String,Int] = {abc=1, hello=2} +``` + +{% endtab %} +{% endtabs %} 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 +``` +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] +{% tabs java_scala_3 %} +{% tab 'Scala 2 and 3' for=java_scala_3 %} + +```scala +scala> val jul = List(1, 2, 3).asJava +val jul: java.util.List[Int] = [1, 2, 3] + +scala> jul.add(7) +java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) +``` - scala> jul.add(7) - java.lang.UnsupportedOperationException - at java.util.AbstractList.add(AbstractList.java:148) +{% endtab %} +{% endtabs %} diff --git a/_overviews/collections-2.13/creating-collections-from-scratch.md b/_overviews/collections-2.13/creating-collections-from-scratch.md index 729b3008f9..9f10410750 100644 --- a/_overviews/collections-2.13/creating-collections-from-scratch.md +++ b/_overviews/collections-2.13/creating-collections-from-scratch.md @@ -14,34 +14,60 @@ permalink: /overviews/collections-2.13/: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: - Iterable() // An empty collection - 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 +{% tabs creating_1 %} +{% tab 'Scala 2 and 3' for=creating_1 %} + +```scala +val a = Iterable() // An empty collection +val b = List() // The empty list +val c = List(1.0, 2.0) // A list with elements 1.0, 2.0 +val d = Vector(1.0, 2.0) // A vector with elements 1.0, 2.0 +val e = Iterator(1, 2, 3) // An iterator returning three integers. +val f = Set(dog, cat, bird) // A set of three animals +val g = HashSet(dog, cat, bird) // A hash set of the same animals +val h = Map('a' -> 7, 'b' -> 0) // A map from characters to integers +``` + +{% endtab %} +{% endtabs %} "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) +{% tabs creating_2 %} +{% tab 'Scala 2 and 3' for=creating_2 %} + +```scala +val c = List.apply(1.0, 2.0) +``` + +{% endtab %} +{% endtabs %} 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`, `LazyList` or `Vector`, or whether it is an abstract base class such as `Seq`, `Set` or `Iterable`. 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> Iterable(1, 2, 3) - res18: Iterable[Int] = List(1, 2, 3) - scala> mutable.Iterable(1, 2, 3) - res19: scala.collection.mutable.Iterable[Int] = ArrayBuffer(1, 2, 3) +{% tabs creating_3 %} +{% tab 'Scala 2 and 3' for=creating_3 %} + +```scala +scala> List(1, 2, 3) +val res17: List[Int] = List(1, 2, 3) + +scala> Iterable(1, 2, 3) +val res18: Iterable[Int] = List(1, 2, 3) + +scala> mutable.Iterable(1, 2, 3) +val res19: scala.collection.mutable.Iterable[Int] = ArrayBuffer(1, 2, 3) +``` + +{% endtab %} +{% endtabs %} 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. The operations provided by collection companion objects are summarized in the following table. In short, there's * `concat`, which concatenates an arbitrary number of collections together, -* `fill` and `tabulate`, which generate single or multi-dimensional collections of given dimensions initialized by some expression or tabulating function, +* `fill` and `tabulate`, which generate single or multidimensional collections of given dimensions initialized by some expression or tabulating function, * `range`, which generates integer collections with some constant step length, and * `iterate` and `unfold`, which generates the collection resulting from repeated application of a function to a start element or state. diff --git a/_overviews/collections-2.13/equality.md b/_overviews/collections-2.13/equality.md index 3ce85f4815..7fa334c8d9 100644 --- a/_overviews/collections-2.13/equality.md +++ b/_overviews/collections-2.13/equality.md @@ -14,21 +14,34 @@ permalink: /overviews/collections-2.13/: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: +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 on what elements are added or removed. This is a potential trap when using a mutable collection as a key in a hashmap. Example: + +{% tabs equality_1 %} +{% tab 'Scala 2 and 3' for=equality_1 %} + +```scala +scala> import collection.mutable.{HashMap, ArrayBuffer} +import collection.mutable.{HashMap, ArrayBuffer} + +scala> val buf = ArrayBuffer(1, 2, 3) +val buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + +scala> val map = HashMap(buf -> 3) +val map: scala.collection.mutable.HashMap[scala.collection. + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + +scala> map(buf) +val res13: Int = 3 + +scala> buf(0) += 1 + +scala> map(buf) + java.util.NoSuchElementException: key not found: ArrayBuffer(2, 2, 3) +``` + +{% endtab %} +{% endtabs %} In this example, the selection in the last line will most likely fail because the hash-code of the array `buf` has changed in the second-to-last line. Therefore, the hash-code-based lookup will look at a different place than the one where `buf` was stored. diff --git a/_overviews/collections-2.13/introduction.md b/_overviews/collections-2.13/introduction.md index 477f8ffb10..2e4d5f8abb 100644 --- a/_overviews/collections-2.13/introduction.md +++ b/_overviews/collections-2.13/introduction.md @@ -12,7 +12,7 @@ permalink: /overviews/collections-2.13/:title.html --- The collections framework is the heart of the Scala 2.13 standard -library. It provides a common, uniform, and all-encompassing +library, also used in Scala 3.x. It provides a common, uniform, and all-encompassing framework for collection types. This framework enables you to work with data in memory at a high level, with the basic building blocks of a program being whole collections, instead of individual elements. @@ -48,7 +48,7 @@ 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 +efficient. You might be able to do a little 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. @@ -70,12 +70,18 @@ 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) +{% tabs introduction_1 %} +{% tab 'Scala 2 and 3' for=introduction_1 %} +``` +val (minors, adults) = people partition (_.age < 18) +``` +{% endtab %} +{% endtabs %} 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 type `IterableOps`, 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. diff --git a/_overviews/collections-2.13/iterators.md b/_overviews/collections-2.13/iterators.md index 0f88475625..a72716740d 100644 --- a/_overviews/collections-2.13/iterators.md +++ b/_overviews/collections-2.13/iterators.md @@ -16,46 +16,99 @@ An iterator is not a collection, but rather a way to access the elements of a co 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()) +{% tabs iterators_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=iterators_1 %} +```scala +while (it.hasNext) + println(it.next()) +``` +{% endtab %} +{% tab 'Scala 3' for=iterators_1 %} +```scala +while it.hasNext do + println(it.next()) +``` +{% endtab %} +{% endtabs %} + +Iterators in Scala also provide analogues of most of the methods that you find in the `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: + +{% tabs iterators_2 %} +{% tab 'Scala 2 and 3' for=iterators_2 %} + +```scala +it.foreach(println) +``` + +{% endtab %} +{% endtabs %} -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: +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: - it foreach println +{% tabs iterators_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=iterators_3 %} +```scala +for (elem <- it) println(elem) +``` +{% endtab %} +{% tab 'Scala 3' for=iterators_3 %} +```scala +for elem <- it do println(elem) +``` +{% endtab %} +{% endtabs %} + +There's an important difference between the foreach method on iterators and the same method on iterable 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 or removes elements, but this is discouraged, because it may lead to surprising results). -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: +The other operations that `Iterator` has in common with `Iterable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: - for (elem <- it) println(elem) +{% tabs iterators_4 %} +{% tab 'Scala 2 and 3' for=iterators_4 %} -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 or removes elements, but this is discouraged, because it may lead to surprising results). +```scala +scala> val it = Iterator("a", "number", "of", "words") +val it: Iterator[java.lang.String] = <iterator> -The other operations that `Iterator` has in common with `Iterable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: +scala> it.map(_.length) +val res1: Iterator[Int] = <iterator> + +scala> it.hasNext +val res2: Boolean = true + +scala> res1.foreach(println) +1 +6 +2 +5 - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = <iterator> - scala> it.map(_.length) - res1: Iterator[Int] = <iterator> - scala> it.hasNext - res2: Boolean = true - scala> res1 foreach println - 1 - 6 - 2 - 5 - scala> it.hasNext - res4: Boolean = false +scala> it.hasNext +val res4: Boolean = false +``` + +{% endtab %} +{% endtabs %} As you can see, after the call to `it.map`, the `it` iterator hasn’t advanced to its end, but traversing the iterator resulting from the call to `res1.foreach` also traverses `it` and advances it 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] = <iterator> - scala> it dropWhile (_.length < 2) - res4: Iterator[java.lang.String] = <iterator> - scala> res4.next() - res5: java.lang.String = number +{% tabs iterators_5 %} +{% tab 'Scala 2 and 3' for=iterators_5 %} + +```scala +scala> val it = Iterator("a", "number", "of", "words") +val it: Iterator[java.lang.String] = <iterator> + +scala> it.dropWhile(_.length < 2) +val res4: Iterator[java.lang.String] = <iterator> + +scala> res4.next() +val res5: java.lang.String = number +``` + +{% endtab %} +{% endtabs %} 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. @@ -63,15 +116,23 @@ In fact, `it` and the result `res4` returned by `dropWhile` will return exactly 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] = <iterator> - ns: Iterator[String] = <iterator> +{% tabs iterators_6 %} +{% tab 'Scala 2 and 3' for=iterators_6 %} + +```scala +scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate +val words: Iterator[String] = <iterator> +val ns: Iterator[String] = <iterator> - scala> val shorts = words.filter(_.length < 3).toList - shorts: List[String] = List(a, of) +scala> val shorts = words.filter(_.length < 3).toList +val shorts: List[String] = List(a, of) - scala> val count = ns.map(_.length).sum - count: Int = 14 +scala> val count = ns.map(_.length).sum +val count: Int = 14 +``` + +{% endtab %} +{% endtabs %} 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 @@ -87,31 +148,31 @@ All operations on iterators are summarized below. | 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. | +| `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". | -| `it sliding size` | An iterator that yields the elements returned by `it` in sequences representing a sliding fixed-sized window. | +| `it.grouped(size)` | An iterator that yields the elements returned by `it` in fixed-sized sequence "chunks". | +| `it.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 concat jt`<br>or `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. | +| `it.concat(jt)`<br>or `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. | +| `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.toLazyList` | Collects the elements returned by `it` in a lazy list. | +| `it.toLazyList` | Collects the elements returned by `it` in a lazy list. | | `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:** | | +| **Copying:** | | | `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`). | @@ -120,44 +181,44 @@ All operations on iterators are summarized below. | `it.length` | Same as `it.size`. | | `it.knownSize` |The number of elements, if this one is known without modifying the iterator’s state, otherwise `-1`. | | **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. | +| `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`. | +| `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`. | | `it.distinct` | An iterator returning the elements from `it` without duplicates. | | **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`. | +| `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`. | +| `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. | +| `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.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`. | +| `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. | +| `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. | @@ -170,44 +231,88 @@ A lazy operation does not immediately compute all of its results. Instead, it co So the expression `(1 to 10).iterator.map(println)` would not print anything to the screen. The `map` method in this case doesn't apply its argument function to the values in the range, it returns a new `Iterator` that will do this as each one is requested. Adding `.toList` to the end of that expression will actually print the elements. -A consequence of this is that a method like `map` or `filter` won't necessarily apply its argument function to all of the input elements. The expression `(1 to 10).iterator.map(println).take(5).toList` would only print the values `1` to `5`, for instance, since those are only ones that will be requested from the `Iterator` returned by `map`. +A consequence of this is that a method like `map` or `filter` won't necessarily apply its argument function to all the input elements. The expression `(1 to 10).iterator.map(println).take(5).toList` would only print the values `1` to `5`, for instance, since those are only ones that will be requested from the `Iterator` returned by `map`. This is one of the reasons why it's important to only use pure functions as arguments to `map`, `filter`, `fold` and similar methods. Remember, a pure function has no side-effects, so one would not normally use `println` in a `map`. `println` is used to demonstrate laziness as it's not normally visible with pure functions. Laziness is still valuable, despite often not being visible, as it can prevent unneeded computations from happening, and can allow for working with infinite sequences, like so: - def zipWithIndex[A](i: Iterator[A]): Iterator[(Int, A)] = - Iterator.from(0).zip(i) +{% tabs iterators_7 %} +{% tab 'Scala 2 and 3' for=iterators_7 %} + +```scala +def zipWithIndex[A](i: Iterator[A]): Iterator[(Int, A)] = + Iterator.from(0).zip(i) +``` + +{% endtab %} +{% endtabs %} ### 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) {} +{% tabs iterators_8 class=tabs-scala-version %} +{% tab 'Scala 2' for=iterators_8 %} +```scala mdoc +def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} +``` +{% endtab %} +{% tab 'Scala 3' for=iterators_8 %} +```scala +def skipEmptyWordsNOT(it: Iterator[String]) = + while it.next().isEmpty do () +``` +{% endtab %} +{% endtabs %} 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](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) is a subclass of [Iterator](https://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() } +{% tabs iterators_9 class=tabs-scala-version %} +{% tab 'Scala 2' for=iterators_9 %} +```scala +def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } +``` +{% endtab %} +{% tab 'Scala 3' for=iterators_9 %} +```scala +def skipEmptyWords(it: BufferedIterator[String]) = + while it.head.isEmpty do it.next() +``` +{% endtab %} +{% endtabs %} 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] = <iterator> - scala> val bit = it.buffered - bit: scala.collection.BufferedIterator[Int] = <iterator> - scala> bit.head - res10: Int = 1 - scala> bit.next() - res11: Int = 1 - scala> bit.next() - res12: Int = 2 - scala> bit.headOption - res13: Option[Int] = Some(3) +{% tabs iterators_10 %} +{% tab 'Scala 2 and 3' for=iterators_10 %} + +```scala +scala> val it = Iterator(1, 2, 3, 4) +val it: Iterator[Int] = <iterator> + +scala> val bit = it.buffered +val bit: scala.collection.BufferedIterator[Int] = <iterator> + +scala> bit.head +val res10: Int = 1 + +scala> bit.next() +val res11: Int = 1 + +scala> bit.next() +val res12: Int = 2 + +scala> bit.headOption +val res13: Option[Int] = Some(3) +``` + +{% endtab %} +{% endtabs %} 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`. @@ -217,21 +322,50 @@ The buffered iterator only buffers the next element when `head` is invoked. Othe 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) +{% tabs iterators_11 class=tabs-scala-version %} +{% tab 'Scala 2' for=iterators_11 %} + +```scala +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 + |} +def collapse(it: Iterator[Int]): Iterator[Int] + +scala> def collapse(it: Iterator[Int]) = { + | val (zeros, rest) = it.span(_ == 0) + | zeros.take(1) ++ rest + |} +def collapse(it: Iterator[Int]): Iterator[Int] + +scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList +val res14: List[Int] = List(0, 1, 2, 3, 4) +``` + +{% endtab %} +{% tab 'Scala 3' for=iterators_11 %} + +```scala +scala> def collapse(it: Iterator[Int]) = if !it.hasNext then Iterator.empty else + | var head = it.next + | val rest = if head == 0 then it.dropWhile(_ == 0) else it + | Iterator.single(head) ++ rest + | +def collapse(it: Iterator[Int]): Iterator[Int] + +scala> def collapse(it: Iterator[Int]) = + | val (zeros, rest) = it.span(_ == 0) + | zeros.take(1) ++ rest + | +def collapse(it: Iterator[Int]): Iterator[Int] + +scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList +val res14: List[Int] = List(0, 1, 2, 3, 4) +``` + +{% endtab %} +{% endtabs %} 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 diff --git a/_overviews/collections-2.13/maps.md b/_overviews/collections-2.13/maps.md index 3a27586614..34d9696f19 100644 --- a/_overviews/collections-2.13/maps.md +++ b/_overviews/collections-2.13/maps.md @@ -16,7 +16,7 @@ A [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html) is an 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. +* **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. @@ -24,90 +24,140 @@ The fundamental operations on maps are similar to those on sets. They are summar ### 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`. | -| **Subcollections:** | | +| 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`. | +| **Subcollections:** | | | `ms.keys` |An iterable containing each key in `ms`. | | `ms.keySet` |A set containing each key in `ms`. | | `ms.keysIterator` |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.view filterKeys p` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.| -| `ms.view mapValues f` |A map view resulting from applying function `f` to each value associated with a key in `ms`.| +| **Transformation:** | | +| `ms.view.filterKeys(p)` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.| +| `ms.view.mapValues(f)` |A map view resulting from applying function `f` to each value associated with a key in `ms`.| Immutable maps support in addition operations to add and remove mappings by returning new `Map`s, as summarized in the following table. ### Operations in Class immutable.Map ### -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Additions and Updates:**| | +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Additions and Updates:**| | | `ms.updated(k, v)`<br>or `ms + (k -> v)` |The map containing all mappings of `ms` as well as the mapping `k -> v` from key `k` to value `v`.| -| **Removals:** | | -| `ms remove k`<br>or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.| -| `ms removeAll ks`<br>or `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.| +| **Removals:** | | +| `ms.removed(k)`<br>or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.| +| `ms.removedAll(ks)`<br>or `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.| 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.addOne(k -> v)`<br>or `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.| -| `ms addAll xvs`<br>or `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 subtractOne k`<br>or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.| -| `ms subtractAll ks`<br>or `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 filterInPlace p` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.| -| `ms.clear()` |Removes all mappings from `ms`. | -| **Transformation:** | | -| `ms mapValuesInPlace 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. 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. +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Additions and Updates:** | | +| `ms(k) = v` |(Or, written out, `ms.update(k, v)`). Adds mapping from key `k` to value `v` to map ms as a side effect, overwriting any previous mapping of `k`.| +| `ms.addOne(k -> v)`<br>or `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.| +| `ms.addAll(kvs)`<br>or `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.subtractOne(k)`<br>or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.| +| `ms.subtractAll(ks)`<br>or `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.filterInPlace(p)` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.| +| `ms.clear()` |Removes all mappings from `ms`. | +| **Transformation:** | | +| `ms.mapValuesInPlace(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. 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 +{% tabs expensive-computation-reverse class=tabs-scala-version %} + +{% tab 'Scala 2' for=expensive-computation-reverse %} +```scala +scala> def f(x: String): String = { + println("taking my time."); Thread.sleep(100) + x.reverse + } +f: (x: String)String +``` +{% endtab %} + +{% tab 'Scala 3' for=expensive-computation-reverse %} +```scala +scala> def f(x: String): String = + println("taking my time."); Thread.sleep(100) + x.reverse + +def f(x: String): String +``` +{% endtab %} + +{% endtabs %} 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() +{% tabs cache-creation %} +{% tab 'Scala 2 and 3' for=cache-creation %} +```scala +scala> val cache = collection.mutable.Map[String, String]() +cache: scala.collection.mutable.Map[String,String] = Map() +``` +{% endtab %} +{% endtabs %} 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 - } +{% tabs cache-usage %} +{% tab 'Scala 2 and 3' for=cache-usage %} +```scala +scala> def cachedF(s: String): 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 +``` +{% endtab %} +{% endtabs %} + +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: + +{% tabs cacheF class=tabs-scala-version %} + +{% tab 'Scala 2' for=cacheF %} +```scala +def cachedF(arg: String): String = cache.get(arg) match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result +} +``` +{% endtab %} + +{% tab 'Scala 3' for=cacheF %} +```scala +def cachedF(arg: String): String = cache.get(arg) match + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result +``` +{% endtab %} + +{% endtabs %} diff --git a/_overviews/collections-2.13/overview.md b/_overviews/collections-2.13/overview.md index 4d739156ac..5ef0c9b0f3 100644 --- a/_overviews/collections-2.13/overview.md +++ b/_overviews/collections-2.13/overview.md @@ -13,10 +13,10 @@ permalink: /overviews/collections-2.13/:title.html --- Scala collections systematically distinguish between mutable and -immutable collections. A _mutable_ collection can be updated or +immutable collections. A _mutable_ collection can be updated, reduced 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 +contrast, never change. You still have operations that simulate additions, removals, or updates, but those operations will in each case return a new collection and leave the old collection unchanged. @@ -36,14 +36,14 @@ 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 +a 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\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) is a superclass of both [collection.immutable.IndexedSeq\[T\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html) and -[collection.mutable.IndexedSeq\[T\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html) +[collection.mutable.IndexedSeq\[T\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html). Generally, the root collections in package `scala.collection` support transformation operations affecting the whole collection, the immutable @@ -73,7 +73,13 @@ 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 +{% tabs overview_1 %} +{% tab 'Scala 2 and 3' for=overview_1 %} +```scala mdoc +import scala.collection.mutable +``` +{% endtab %} +{% endtabs %} Then a word like `Set` without a prefix still refers to an immutable collection, whereas `mutable.Set` refers to the mutable counterpart. @@ -86,10 +92,16 @@ 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 +{% tabs overview_2 %} +{% tab 'Scala 2 and 3' for=overview_2 %} +```scala mdoc +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 +``` +{% endtab %} +{% endtabs %} Other types aliased are [Iterable](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html), [Seq](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Seq.html), [IndexedSeq](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html), [Iterator](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), [LazyList](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/LazyList.html), [Vector](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), [StringBuilder](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html), and [Range](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html). @@ -116,27 +128,45 @@ Legend: 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: - 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) +{% tabs overview_3 %} +{% tab 'Scala 2 and 3' for=overview_3 %} +```scala +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) +``` +{% endtab %} +{% endtabs %} The same principle also applies for specific collection implementations, such as: - List(1, 2, 3) - HashMap("x" -> 24, "y" -> 25, "z" -> 26) +{% tabs overview_4 %} +{% tab 'Scala 2 and 3' for=overview_4 %} +```scala +List(1, 2, 3) +HashMap("x" -> 24, "y" -> 25, "z" -> 26) +``` +{% endtab %} +{% endtabs %} All these collections get displayed with `toString` in the same way they are written above. All collections support the API provided by `Iterable`, but specialize types wherever this makes sense. For instance the `map` method in class `Iterable` returns another `Iterable` 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) +{% tabs overview_5 %} +{% tab 'Scala 2 and 3' for=overview_5 %} +``` +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) +``` +{% endtab %} +{% endtabs %} This behavior which is implemented everywhere in the collections libraries is called the _uniform return type principle_. diff --git a/_overviews/collections-2.13/performance-characteristics.md b/_overviews/collections-2.13/performance-characteristics.md index 9d408add88..ed1885017a 100644 --- a/_overviews/collections-2.13/performance-characteristics.md +++ b/_overviews/collections-2.13/performance-characteristics.md @@ -23,7 +23,7 @@ Performance characteristics of sequence types: | `LazyList` | C | C | L | L | C | L | - | | `ArraySeq` | C | L | C | L | L | L | - | | `Vector` | eC | eC | eC | eC | eC | eC | - | -| `Queue` | aC | aC | L | L | L | C | - | +| `Queue` | aC | aC | L | L | C | C | - | | `Range` | C | C | C | - | - | - | - | | `String` | C | L | C | L | L | L | - | | **mutable** | | | | | | | | diff --git a/_overviews/collections-2.13/seqs.md b/_overviews/collections-2.13/seqs.md index 1cd8ebfc7b..cabd0b8a0a 100644 --- a/_overviews/collections-2.13/seqs.md +++ b/_overviews/collections-2.13/seqs.md @@ -16,7 +16,7 @@ The [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html) trai 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. +* **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 or with an `Iterable` even if the sequences has infinite length. * **Index search operations** `indexOf`, `lastIndexOf`, `indexOfSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, which return the index of an element equal to a given value or matching some predicate. * **Addition operations** `prepended`, `prependedAll`, `appended`, `appendedAll`, `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. @@ -32,17 +32,17 @@ If a sequence is mutable, it offers in addition a side-effecting `update` method | 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(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 `LazyList.from(1) lengthCompare 42` returns a positive value.| +| `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 `LazyList.from(1).lengthCompare(42)` returns a positive value.| | `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.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`.| | **Additions:** | | | `xs.prepended(x)`<br>or `x +: xs` |A new sequence that consists of `x` prepended to `xs`.| @@ -56,26 +56,26 @@ If a sequence is mutable, it offers in addition a side-effecting `update` method | `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.| +| `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.| | **Comparisons:** | | -| `xs sameElements ys` |A test whether `xs` and `ys` contain the same elements in the same order| -| `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 search x` |Tests whether a sorted sequence `xs` has an element equal to `x`, possibly in a more efficient way than `xs contains 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`.| +| `xs.sameElements(ys)` |A test whether `xs` and `ys` contain the same elements in the same order| +| `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.search(x)` |Tests whether a sorted sequence `xs` has an element equal to `x`, possibly in a more efficient way than `xs.contains(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.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.distinct` |A subsequence of `xs` that contains no duplicated element.| -| `xs distinctBy f` |A subsequence of `xs` that contains no duplicated element after applying the transforming function `f`. For instance, `List("foo", "bar", "quux").distinctBy(_.length) == List("foo", "quux")`| +| `xs.distinctBy(f)` |A subsequence of `xs` that contains no duplicated element after applying the transforming function `f`. For instance, `List("foo", "bar", "quux").distinctBy(_.length) == List("foo", "quux")`| -Trait [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html) has two subtraits [LinearSeq](https://www.scala-lang.org/api/current/scala/collection/LinearSeq.html), and [IndexedSeq](https://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). These do not add any new operations to the immutable branch, 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.LazyList`. 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](concrete-immutable-collection-classes.html). +Trait [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html) has two subtraits [LinearSeq](https://www.scala-lang.org/api/current/scala/collection/LinearSeq.html), and [IndexedSeq](https://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). These do not add any new operations to the immutable branch, 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.LazyList`. 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]({% link _overviews/collections-2.13/concrete-immutable-collection-classes.md %}). On the mutable branch, `IndexedSeq` adds operations for transforming its elements in place (by contrast with transformation operations such as `map` and `sort`, available on the root `Seq`, which return a new collection @@ -102,20 +102,20 @@ Two often used implementations of buffers are `ListBuffer` and `ArrayBuffer`. A | WHAT IT IS | WHAT IT DOES| | ------ | ------ | | **Additions:** | | -| `buf append x`<br>or `buf += x` |Appends element `x` to buffer, and returns `buf` itself as result.| -| `buf appendAll xs`<br>or`buf ++= xs` |Appends all elements in `xs` to buffer.| -| `buf prepend x`<br>or `x +=: buf` |Prepends element `x` to buffer.| -| `buf prependAll xs`<br>or `xs ++=: buf` |Prepends all elements in `xs` to buffer.| +| `buf.append(x)`<br>or `buf += x` |Appends element `x` to buffer, and returns `buf` itself as result.| +| `buf.appendAll(xs)`<br>or `buf ++= xs` |Appends all elements in `xs` to buffer.| +| `buf.prepend(x)`<br>or `x +=: buf` |Prepends element `x` to buffer.| +| `buf.prependAll(xs)`<br>or `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.| | `buf.padToInPlace(n, x)` |Appends element `x` to buffer until it has `n` elements in total.| | **Removals:** | | -| `buf subtractOne x`<br>or `buf -= x` |Removes element `x` from buffer.| -| `buf subtractAll xs`<br>or `buf --= xs` |Removes elements in `xs` from buffer.| -| `buf remove i` |Removes element at index `i` from buffer.| +| `buf.subtractOne(x)`<br>or `buf -= x` |Removes element `x` from buffer.| +| `buf.subtractAll(xs)`<br>or `buf --= xs` |Removes elements in `xs` 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.trimStart(n)` |Removes first `n` elements from buffer.| +| `buf.trimEnd(n)` |Removes last `n` elements from buffer.| | `buf.clear()` |Removes all elements from buffer.| | **Replacement:** | | | `buf.patchInPlace(i, xs, n)` |Replaces (at most) `n` elements of buffer by elements in `xs`, starting from index `i` in buffer.| diff --git a/_overviews/collections-2.13/sets.md b/_overviews/collections-2.13/sets.md index 96984d34f3..a57814ffd1 100644 --- a/_overviews/collections-2.13/sets.md +++ b/_overviews/collections-2.13/sets.md @@ -18,14 +18,18 @@ permalink: /overviews/collections-2.13/:title.html 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 - +{% tabs sets_1 %} +{% tab 'Scala 2 and 3' for=sets_1 %} +```scala +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 +``` +{% endtab %} +{% endtabs %} * **Additions** `incl` and `concat` (or `+` and `++`, respectively), which add one or more elements to a set, yielding a new set. * **Removals** `excl` and `removedAll` (or `-` and `--`, respectively), which remove one or more elements from a set, yielding a new set. @@ -85,22 +89,33 @@ The operation `s += elem` adds `elem` to the set `s` as a side effect, and retur 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) +{% tabs sets_2 %} +{% tab 'Scala 2 and 3' for=sets_2 %} +```scala +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) +``` +{% endtab %} +{% endtabs %} 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) +{% tabs sets_3 %} +{% tab 'Scala 2 and 3' for=sets_3 %} +```scala +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) +``` +{% endtab %} +{% endtabs %} 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. @@ -108,7 +123,7 @@ Comparing the two interactions shows an important principle. You often can repla 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 [Compressed Hash-Array Mapped Prefix-tree](concrete-immutable-collection-classes.html). +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 [Compressed Hash-Array Mapped Prefix-tree]({% link _overviews/collections-2.13/concrete-immutable-collection-classes.md %}). 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. @@ -120,34 +135,63 @@ A [SortedSet](https://www.scala-lang.org/api/current/scala/collection/SortedSet. To create an empty [TreeSet](https://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] = ... +{% tabs sorted-sets_1 %} +{% tab 'Scala 2 and 3' for=sorted-sets_1 %} +```scala +scala> val myOrdering = Ordering.fromLessThan[String](_ > _) +myOrdering: scala.math.Ordering[String] = ... +``` +{% endtab %} +{% endtabs %} 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() +{% tabs sorted-sets_2 %} +{% tab 'Scala 2 and 3' for=sorted-sets_2 %} +```scala +scala> TreeSet.empty(myOrdering) +res1: scala.collection.immutable.TreeSet[String] = TreeSet() +``` +{% endtab %} +{% endtabs %} + +Or you can leave out the ordering argument but give an element type for the empty set. In that case, the default ordering on the element type will be used. + +{% tabs sorted-sets_3 %} +{% tab 'Scala 2 and 3' for=sorted-sets_3 %} +```scala +scala> TreeSet.empty[String] +res2: scala.collection.immutable.TreeSet[String] = TreeSet() +``` +{% endtab %} +{% endtabs %} 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) +{% tabs sorted-sets_4 %} +{% tab 'Scala 2 and 3' for=sorted-sets_4 %} +```scala +scala> res2 + "one" + "two" + "three" + "four" +res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) +``` +{% endtab %} +{% endtabs %} 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 rangeFrom "three" - res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) - +{% tabs sorted-sets_5 %} +{% tab 'Scala 2 and 3' for=sorted-sets_5 %} +```scala +scala> res3.range("one", "two") +res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) +scala> res3 rangeFrom "three" +res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) +``` +{% endtab %} +{% endtabs %} ### 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](https://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 sets of non-negative integer elements that are implemented in one or more words of packed bits. The internal representation of a [BitSet](https://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-2.13/strings.md b/_overviews/collections-2.13/strings.md index 485410df49..aebe244304 100644 --- a/_overviews/collections-2.13/strings.md +++ b/_overviews/collections-2.13/strings.md @@ -14,17 +14,30 @@ permalink: /overviews/collections-2.13/: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] = hello +{% tabs strings_1 %} +{% tab 'Scala 2 and 3' for=strings_1 %} + +```scala +scala> val str = "hello" +val str: java.lang.String = hello + +scala> str.reverse +val res6: String = olleh + +scala> str.map(_.toUpper) +val res7: String = HELLO + +scala> str.drop(3) +val res8: String = lo + +scala> str.slice(1, 4) +val res9: String = ell + +scala> val s: Seq[Char] = str +val s: Seq[Char] = hello +``` + +{% endtab %} +{% endtabs %} 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-2.13/trait-iterable.md b/_overviews/collections-2.13/trait-iterable.md index edc2ef2b1f..21b28e2282 100644 --- a/_overviews/collections-2.13/trait-iterable.md +++ b/_overviews/collections-2.13/trait-iterable.md @@ -14,7 +14,13 @@ permalink: /overviews/collections-2.13/:title.html At the top of the collection hierarchy is trait `Iterable`. All methods in this trait are defined in terms of an abstract method, `iterator`, which yields the collection's elements one by one. - def iterator: Iterator[A] +{% tabs trait-iterable_1 %} +{% tab 'Scala 2 and 3' for=trait-iterable_1 %} +```scala +def iterator: Iterator[A] +``` +{% endtab %} +{% endtabs %} Collection classes that implement `Iterable` just need to define this method; all other methods can be inherited from `Iterable`. @@ -31,119 +37,124 @@ Collection classes that implement `Iterable` just need to define this method; al * **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`, `className`, which give alternative ways of converting a collection to a string. -* **View** operation: A view is a collection that's evaluated lazily. You'll learn more about views in [later](views.html). +* **String** operations `mkString` and `addString` which give alternative ways of converting a collection to a string. +* **View** operation: A view is a collection that's evaluated lazily. You'll learn more about views in [later]({% link _overviews/collections-2.13/views.md %}). 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) +{% tabs trait-iterable_2 %} +{% tab 'Scala 2 and 3' for=trait-iterable_2 %} +``` +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) +``` +{% endtab %} +{% endtabs %} ### Operations in Class Iterable ### -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Abstract Method:** | | -| `xs.iterator` |An `iterator` that yields every element in `xs`.| -| **Other Iterators:** | | -| `xs foreach f` |Executes function `f` for every element of `xs`.| -| `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.| -| **Addition:** | | -| `xs concat ys`<br>(or `xs ++ ys`) |A collection consisting of the elements of both `xs` and `ys`. `ys` is a [IterableOnce](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html) collection, i.e., either an [Iterable](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) or an [Iterator](https://www.scala-lang.org/api/{{ site.scala-version }}/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.to(SortedSet)` | Generic conversion operation that takes a collection factory as parameter. | -| `xs.toList` |Converts the collection to a list. | -| `xs.toVector` |Converts the collection to a vector. | -| `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.| -| `xs.toSet` |Converts the collection to a set. | -| `xs.toSeq` |Converts the collection to a sequence. | -| `xs.toIndexedSeq` |Converts the collection to an indexed sequence. | -| `xs.toBuffer` |Converts the collection to a buffer. | -| `xs.toArray` |Converts the collection to an array. | -| **Copying:** | | -| `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.knownSize` |The number of elements, if this one takes constant time to compute, otherwise `-1`. | -| `xs.sizeCompare(ys)` |Returns a negative value if `xs` is shorter than the `ys` collection, a positive value if it is longer, and `0` if they have the same size. Works even if the collection is infinite, for example `LazyList.from(1) sizeCompare List(1, 2)` returns a positive value. | -| `xs.sizeCompare(n)` |Returns a negative value if `xs` is shorter than `n`, a positive value if it is longer, and `0` if it is of size `n`. Works even if the collection is infinite, for example `LazyList.from(1) sizeCompare 42` returns a positive value. | -| `xs.sizeIs < 42`, `xs.sizeIs != 42`, etc. |Provides a more convenient syntax for `xs.sizeCompare(42) < 0`, `xs.sizeCompare(42) != 0`, etc., respectively.| -| **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 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 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`.| -| `xs.groupMap(f)(g)`|Partition `xs` into a map of collections according to a discriminator function `f`, and applies the transformation function `g` to each element in a group.| -| `xs.groupMapReduce(f)(g)(h)`|Partition `xs` according to a discriminator function `f`, and then combine the results of applying the function `g` to each element in a group using the `h` function.| -| **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:** | | -| `xs.foldLeft(z)(op)` |Apply binary operation `op` between successive elements of `xs`, going left to right and starting with `z`.| -| `xs.foldRight(z)(op)` |Apply binary operation `op` between successive elements of `xs`, going right to left and ending with `z`.| -| `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`.| -| `xs.minOption` |Like `min` but returns `None` if `xs` is empty.| -| `xs.maxOption` |Like `max` but returns `None` if `xs` is empty.| -| **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`.| -| **Zippers:** | | -| `xs zip ys` |A collection of pairs of corresponding elements from `xs` and `ys`.| -| `xs.zipAll(ys, x, y)` |A collection 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 collection of pairs of elements from `xs` with their indices.| -| **Views:** | | -| `xs.view` |Produces a view over `xs`.| +| WHAT IT IS | WHAT IT DOES | +|-------------------------------------------| ------ | +| **Abstract Method:** | | +| `xs.iterator` |An `iterator` that yields every element in `xs`.| +| **Other Iterators:** | | +| `xs.foreach(f)` |Executes function `f` for every element of `xs`.| +| `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.| +| **Addition:** | | +| `xs.concat(ys)`<br>(or `xs ++ ys`) |A collection consisting of the elements of both `xs` and `ys`. `ys` is a [IterableOnce](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html) collection, i.e., either an [Iterable](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) or an [Iterator](https://www.scala-lang.org/api/{{ site.scala-version }}/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.to(SortedSet)` | Generic conversion operation that takes a collection factory as parameter. | +| `xs.toList` |Converts the collection to a list. | +| `xs.toVector` |Converts the collection to a vector. | +| `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.| +| `xs.toSet` |Converts the collection to a set. | +| `xs.toSeq` |Converts the collection to a sequence. | +| `xs.toIndexedSeq` |Converts the collection to an indexed sequence. | +| `xs.toBuffer` |Converts the collection to a buffer. | +| `xs.toArray` |Converts the collection to an array. | +| **Copying:** | | +| `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.knownSize` |The number of elements, if this one takes constant time to compute, otherwise `-1`. | +| `xs.sizeCompare(ys)` |Returns a negative value if `xs` is shorter than the `ys` collection, a positive value if it is longer, and `0` if they have the same size. Works even if the collection is infinite, for example `LazyList.from(1) sizeCompare List(1, 2)` returns a positive value. | +| `xs.sizeCompare(n)` |Returns a negative value if `xs` is shorter than `n`, a positive value if it is longer, and `0` if it is of size `n`. Works even if the collection is infinite, for example `LazyList.from(1) sizeCompare 42` returns a positive value. | +| `xs.sizeIs < 42`, `xs.sizeIs != 42`, etc. |Provides a more convenient syntax for `xs.sizeCompare(42) < 0`, `xs.sizeCompare(42) != 0`, etc., respectively.| +| **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.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.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`.| +| `xs.groupMap(f)(g)` |Partition `xs` into a map of collections according to a discriminator function `f`, and applies the transformation function `g` to each element in a group.| +| `xs.groupMapReduce(f)(g)(h)` |Partition `xs` according to a discriminator function `f`, and then combine the results of applying the function `g` to each element in a group using the `h` function.| +| **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:** | | +| `xs.foldLeft(z)(op)` |Apply binary operation `op` between successive elements of `xs`, going left to right and starting with `z`.| +| `xs.foldRight(z)(op)` |Apply binary operation `op` between successive elements of `xs`, going right to left and starting with `z`.| +| `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`.| +| `xs.minOption` |Like `min` but returns `None` if `xs` is empty.| +| `xs.maxOption` |Like `max` but returns `None` if `xs` is empty.| +| **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.| +| **Zippers:** | | +| `xs.zip(ys)` |A collection of pairs of corresponding elements from `xs` and `ys`.| +| `xs.zipAll(ys, x, y)` |A collection 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 collection of pairs of elements from `xs` with their indices.| +| **Views:** | | +| `xs.view` |Produces a view over `xs`.| In the inheritance hierarchy below `Iterable` you find three traits: [Seq](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html), [Set](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Set.html), and [Map](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html). `Seq` and `Map` implement the [PartialFunction](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/PartialFunction.html) trait with its `apply` and `isDefinedAt` methods, each implemented differently. `Set` gets its `apply` method from [SetOps](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SetOps.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`. +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-2.13/views.md b/_overviews/collections-2.13/views.md index 0b0e3f2c1e..6b0052c5e5 100644 --- a/_overviews/collections-2.13/views.md +++ b/_overviews/collections-2.13/views.md @@ -18,11 +18,23 @@ There are two principal ways to implement transformers. One is _strict_, that is As an example of a non-strict transformer consider the following implementation of a lazy map operation: - def lazyMap[T, U](iter: Iterable[T], f: T => U) = new Iterable[U] { - def iterator = iter.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. +{% tabs views_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=views_1 %} +```scala mdoc +def lazyMap[T, U](iter: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = iter.iterator.map(f) +} +``` +{% endtab %} +{% tab 'Scala 3' for=views_1 %} +```scala +def lazyMap[T, U](iter: Iterable[T], f: T => U) = new Iterable[U]: + def iterator = iter.iterator.map(f) +``` +{% endtab %} +{% endtabs %} + +Note that `lazyMap` constructs a new `Iterable` without stepping through all elements of the given collection `iter`. 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 `LazyList`, 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. @@ -30,42 +42,103 @@ To go from a collection to its view, you can use the `view` method on the collec 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) +{% tabs views_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=views_2 %} + +```scala +scala> val v = Vector(1 to 10: _*) +val v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +scala> v.map(_ + 1).map(_ * 2) +val res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +``` + +{% endtab %} +{% tab 'Scala 3' for=views_2 %} + +```scala +scala> val v = Vector((1 to 10)*) +val v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +scala> v.map(_ + 1).map(_ * 2) +val res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +``` + +{% endtab %} +{% endtabs %} 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)).to(Vector) - res12: scala.collection.immutable.Vector[Int] = - Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +{% tabs views_3 %} +{% tab 'Scala 2 and 3' for=views_3 %} + +```scala +scala> val w = v.view.map(_ + 1).map(_ * 2).to(Vector) +val w: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +``` + +{% endtab %} +{% endtabs %} Let's do this sequence of operations again, one by one: - scala> val vv = v.view - vv: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +{% tabs views_4 %} +{% tab 'Scala 2 and 3' for=views_4 %} + +```scala +scala> val vv = v.view +val vv: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +``` + +{% endtab %} +{% endtabs %} The application `v.view` gives you an `IndexedSeqView[Int]`, i.e. a lazily evaluated `IndexedSeq[Int]`. Like with `LazyList`, the `toString` operation of views does not force the view elements, that’s why the content of `vv` is shown as `IndexedSeqView(<not computed>)`. Applying the first `map` to the view gives: - scala> vv map (_ + 1) - res13: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +{% tabs views_5 %} +{% tab 'Scala 2 and 3' for=views_5 %} + +```scala +scala> vv.map(_ + 1) +val res13: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +``` +{% endtab %} +{% endtabs %} The result of the `map` is another `IndexedSeqView[Int]` value. 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 forced, however. Let's now apply the second `map` to the last result. - scala> res13 map (_ * 2) - res14: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +{% tabs views_6 %} +{% tab 'Scala 2 and 3' for=views_6 %} + +```scala +scala> res13.map(_ * 2) +val res14: scala.collection.IndexedSeqView[Int] = IndexedSeqView(<not computed>) +``` + +{% endtab %} +{% endtabs %} Finally, forcing the last result gives: - scala> res14.to(Vector) - res15: scala.collection.immutable.Vector[Int] = - Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +{% tabs views_7 %} +{% tab 'Scala 2 and 3' for=views_7 %} + +```scala +scala> res14.to(Vector) +val res15: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) +``` + +{% endtab %} +{% endtabs %} Both stored functions get applied as part of the execution of the `to` operation and a new vector is constructed. That way, no intermediate data structure is needed. @@ -84,16 +157,36 @@ These operations are documented as “always forcing the collection elements”. The main reason for using views 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 findPalindrome(s: Seq[String]) = s find isPalindrome +{% tabs views_8 %} +{% tab 'Scala 2 and 3' for=views_8 %} + +```scala +def isPalindrome(x: String) = x == x.reverse +def findPalindrome(s: Seq[String]) = s.find(isPalindrome) +``` + +{% endtab %} +{% endtabs %} -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 `findPalindrome`? Of course, you could write: +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 `findPalindrome`? Of course, you could write: - findPalindrome(words take 1000000) +{% tabs views_9 %} +{% tab 'Scala 2 and 3' for=views_9 %} +```scala +val palindromes = findPalindrome(words.take(1000000)) +``` +{% endtab %} +{% endtabs %} 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) +{% tabs views_10 %} +{% tab 'Scala 2 and 3' for=views_10 %} +```scala +val palindromes = findPalindrome(words.view.take(1000000)) +``` +{% endtab %} +{% endtabs %} 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. @@ -101,16 +194,50 @@ After having seen all these nifty uses of views you might wonder why have strict 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 { ... } +{% tabs views_11 class=tabs-scala-version %} +{% tab 'Scala 2' for=views_11 %} +```scala +val actors = for (i <- 1 to 10) yield actor { ... } +``` +{% endtab %} +{% tab 'Scala 3' for=views_11 %} +```scala +val actors = for i <- 1 to 10 yield actor { ... } +``` +{% endtab %} +{% endtabs %} 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 { ... }) +{% tabs views_12 %} +{% tab 'Scala 2 and 3' for=views_12 %} + +```scala +val actors = (1 to 10).map(i => actor { ... }) +``` + +{% endtab %} +{% endtabs %} 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 current Scala collections library has more regular rules. All collections except lazy lists 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 `to`. So the `actors` definition above would now behave as expected 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 { ... } +{% tabs views_13 class=tabs-scala-version %} +{% tab 'Scala 2' for=views_13 %} + +```scala +val actors = for (i <- (1 to 10).view) yield actor { ... } +``` + +{% endtab %} +{% tab 'Scala 3' for=views_13 %} + +```scala +val actors = for i <- (1 to 10).view yield actor { ... } +``` + +{% endtab %} +{% endtabs %} 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 purely functional code where collection transformations do not have side effects. What's best avoided is a mixture of views and operations that create new collections while also having side effects. diff --git a/_overviews/collections/arrays.md b/_overviews/collections/arrays.md index 019ac91248..637806b014 100644 --- a/_overviews/collections/arrays.md +++ b/_overviews/collections/arrays.md @@ -24,7 +24,7 @@ permalink: /overviews/collections/:title.html 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: +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) @@ -60,9 +60,9 @@ The `ArrayOps` object gets inserted automatically by the implicit conversion. So 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 by `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. +where `intArrayOps` is the implicit conversion that was inserted previously. This raises the question of 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 by `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. +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 to 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 of help from you. To illustrate the issue, consider the following attempt to write a generic method that creates an array. // this is wrong! def evenElems[T](xs: Vector[T]): Array[T] = { @@ -72,7 +72,7 @@ So now you know how arrays can be compatible with sequences and how they can sup 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: +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 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) diff --git a/_overviews/collections/concrete-immutable-collection-classes.md b/_overviews/collections/concrete-immutable-collection-classes.md index 95a76570d1..6324128e48 100644 --- a/_overviews/collections/concrete-immutable-collection-classes.md +++ b/_overviews/collections/concrete-immutable-collection-classes.md @@ -19,7 +19,7 @@ A [List](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/colle 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. +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 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 diff --git a/_overviews/collections/concrete-mutable-collection-classes.md b/_overviews/collections/concrete-mutable-collection-classes.md index bc7bf02567..108b531c9a 100644 --- a/_overviews/collections/concrete-mutable-collection-classes.md +++ b/_overviews/collections/concrete-mutable-collection-classes.md @@ -54,7 +54,7 @@ Just like an array buffer is useful for building arrays, and a list buffer is us ## Linked Lists -Linked lists are mutable sequences that consist of nodes which are linked with next pointers. They are supported by class [LinkedList](https://www.scala-lang.org/api/{{ site.scala-212-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. +Linked lists are mutable sequences that consist of nodes which are linked with next pointers. They are supported by class [LinkedList](https://www.scala-lang.org/api/{{ site.scala-212-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 @@ -85,7 +85,7 @@ Scala provides mutable queues in addition to immutable ones. You use a `mQueue` 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](https://www.scala-lang.org/api/{{ site.scala-212-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 `ClassTag` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.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 `ClassTag` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). ## Stacks diff --git a/_overviews/collections/creating-collections-from-scratch.md b/_overviews/collections/creating-collections-from-scratch.md index a7c1a7ff5b..2468bf9e27 100644 --- a/_overviews/collections/creating-collections-from-scratch.md +++ b/_overviews/collections/creating-collections-from-scratch.md @@ -40,7 +40,7 @@ Besides `apply`, every collection companion object also defines a member `empty` 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, +* `fill` and `tabulate`, which generate single or multidimensional 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. diff --git a/_overviews/collections/equality.md b/_overviews/collections/equality.md index c949d7aac5..bb9abc6f06 100644 --- a/_overviews/collections/equality.md +++ b/_overviews/collections/equality.md @@ -13,7 +13,7 @@ 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: +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 on 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} diff --git a/_overviews/collections/introduction.md b/_overviews/collections/introduction.md index d61806d127..5fc2e3f301 100644 --- a/_overviews/collections/introduction.md +++ b/_overviews/collections/introduction.md @@ -55,7 +55,7 @@ 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 +efficient. You might be able to do a little 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 diff --git a/_overviews/collections/iterators.md b/_overviews/collections/iterators.md index f08e65d5a3..78dfcc69f0 100644 --- a/_overviews/collections/iterators.md +++ b/_overviews/collections/iterators.md @@ -26,7 +26,7 @@ As always, for-expressions can be used as an alternate syntax for expressions in 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). +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 or 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: @@ -166,7 +166,7 @@ A lazy operation does not immediately compute all of its results. Instead, it co So the expression `(1 to 10).iterator.map(println)` would not print anything to the screen. The `map` method in this case doesn't apply its argument function to the values in the range, it returns a new `Iterator` that will do this as each one is requested. Adding `.toList` to the end of that expression will actually print the elements. -A consequence of this is that a method like `map` or `filter` won't necessarily apply its argument function to all of the input elements. The expression `(1 to 10).iterator.map(println).take(5).toList` would only print the values `1` to `5`, for instance, since those are only ones that will be requested from the `Iterator` returned by `map`. +A consequence of this is that a method like `map` or `filter` won't necessarily apply its argument function to all the input elements. The expression `(1 to 10).iterator.map(println).take(5).toList` would only print the values `1` to `5`, for instance, since those are only ones that will be requested from the `Iterator` returned by `map`. This is one of the reasons why it's important to only use pure functions as arguments to `map`, `filter`, `fold` and similar methods. Remember, a pure function has no side-effects, so one would not normally use `println` in a `map`. `println` is used to demonstrate laziness as it's not normally visible with pure functions. diff --git a/_overviews/collections/migrating-from-scala-27.md b/_overviews/collections/migrating-from-scala-27.md index d621c78899..5e1efc7822 100644 --- a/_overviews/collections/migrating-from-scala-27.md +++ b/_overviews/collections/migrating-from-scala-27.md @@ -12,7 +12,7 @@ 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: +Generally, the old functionality of Scala 2.7 collections has been left in place. Some features have been deprecated, which means they will be 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 @@ -38,7 +38,7 @@ Generally, the old functionality of Scala 2.7 collections has been left in place 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. +1. The previous `scala.collection.jcl` package is gone. This package tried to mimic aspects 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/trait-iterable.md b/_overviews/collections/trait-iterable.md index abc8051703..ac72783f41 100644 --- a/_overviews/collections/trait-iterable.md +++ b/_overviews/collections/trait-iterable.md @@ -62,6 +62,6 @@ Trait `Iterable` also adds some other methods to `Traversable` that can be imple In the inheritance hierarchy below Iterable you find three traits: [Seq](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/Seq.html), [Set](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/Set.html), and [Map](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/Map.html). `Seq` and `Map` implement the [PartialFunction](https://www.scala-lang.org/api/{{ site.scala-212-version }}/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/{{ site.scala-212-version }}/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`. +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 index 11aaa6b349..d2173cb789 100644 --- a/_overviews/collections/trait-traversable.md +++ b/_overviews/collections/trait-traversable.md @@ -25,7 +25,7 @@ The `foreach` method is meant to traverse all elements of the collection, and ap * **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 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`. +* **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 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. diff --git a/_overviews/collections/views.md b/_overviews/collections/views.md index dd3c128657..1798d77cf4 100644 --- a/_overviews/collections/views.md +++ b/_overviews/collections/views.md @@ -73,7 +73,7 @@ There are two reasons why you might want to consider using views. The first is p def isPalindrome(x: String) = x == x.reverse def findPalindrome(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 `findPalindrome`? Of course, you could write: +Now, assume you have a very long sequence of words, and you want to find a palindrome in the first million words of that sequence. Can you re-use the definition of `findPalindrome`? Of course, you could write: findPalindrome(words take 1000000) diff --git a/_overviews/compiler-options/index.md b/_overviews/compiler-options/index.md index c8d367c28f..c4fd52f010 100644 --- a/_overviews/compiler-options/index.md +++ b/_overviews/compiler-options/index.md @@ -25,17 +25,13 @@ title: Scala Compiler Options ## Introduction -Scala compiler `scalac` offers various **compiler options**, also referred to as **compiler flags**, to change how to compile your program. +The Scala compiler `scalac` offers various **compiler options**, or **flags**, that change the compiler's default behavior. Some options just generate more compiler output in the form of diagnostics or warnings, while others change the result of compilation. -Nowadays, most people are not running `scalac` from the command line. -Instead, they use sbt, an IDE, and other tools as their interface to the compiler. -Therefore they may not even have `scalac` installed, and won't think to do `man scalac`. +The Scala command `scala`, which runs scripts or compiled code, accepts the same options as the `scalac` compiler, plus a few more that determine how to run a program. -This page comes to the rescue for the people to find… - -* What compiler options `scalac` offers -* How to use compiler options +Options may be specified on the command line to `scalac` or in the configuration of a build tool or IDE. +The Scala distribution includes a `man` page. If Scala is installed as a system command, that documentation may be available from `man scalac`. ## How to use compiler options @@ -44,34 +40,47 @@ This page comes to the rescue for the people to find… ```bash scalac [ <options> ] <source files> ``` +Boolean flags are specified in the usual way: + +`scalac -Werror -Xlint Hello.scala` + +Options that require arguments use "colon" syntax: + +`scalac -Vprint:parser,typer` -E.g. `scalac -encoding utf8 -Xfatal-warnings Hello.scala` +Options that take just a single argument accept traditional syntax: -Default paths can be listed by running a command line tool: +`scalac -d /tmp` + +Conventionally, options have a prefix `-V` if they show "verbose" output; +`-W` to manage warnings; `-X` for extended options that modify tool behavior; +`-Y` for private options with limited support, where `Y` may suggest forking behavior. +Several options have historical aliases, such as `-Xfatal-warnings` for `-Werror`. + +In Scala 2, default paths can be listed by running a tool in the distribution: ``` scala scala.tools.util.PathResolver [ <options> ] ``` - - +That can help debug errors in options such as `--classpath`. ### Use compiler options with sbt - +Here is a typical configuration of the `scalacOptions` setting in `sbt`: ```scala -scalacOptions ++= Seq( - "-encoding", "utf8", // Option and arguments on same line - "-Xfatal-warnings", // New lines for each options - "-deprecation", - "-unchecked", +scalacOptions ++= Seq( // use ++= to add to existing options + "-encoding", "utf8", // if an option takes an arg, supply it on the same line + "-feature", // then put the next option on a new line for easy editing "-language:implicitConversions", - "-language:higherKinds", "-language:existentials", - "-language:postfixOps" -) + "-unchecked", + "-Werror", + "-Xlint", // exploit "trailing comma" syntax so you can add an option without editing this line +) // for "trailing comma", the closing paren must be on the next line ``` +The convention is always to append to the setting with `++=` and to supply one option per line. - +Normally the last option will have a trailing comma so that `git diff` is a bit cleaner when options are added. {% for category in site.data.compiler-options %} <h2>{{ category.category }}</h2> @@ -116,6 +125,15 @@ scalacOptions ++= Seq( {% endfor %} +### Targeting a version of the JVM + +Applications or libraries targeting the JVM may wish to specify a target version. + +The `-release` option specifies the target version, such as "8" or "18". + +Like the option for `javac`, it allows building against an earlier version of the JDK. It will compile against the API for that version and also output class files for that version. + +The deprecated option `-target` does not compile against the desired API, but only specifies a target class file format. ## Additional resources diff --git a/_overviews/compiler-options/optimizer.md b/_overviews/compiler-options/optimizer.md index cad7255280..5f35867bb5 100644 --- a/_overviews/compiler-options/optimizer.md +++ b/_overviews/compiler-options/optimizer.md @@ -28,7 +28,7 @@ The Scala compiler has included an inliner since version 2.0. Closure eliminatio The optimizer was re-written for Scala 2.12 to become more reliable and powerful – and to side-step the spelling issue by calling the new flag `-opt`. This post describes how to use the optimizer in Scala 2.12 and 2.13: what it does, how it works, and what are its limitations. -The options were simplified for 2.13.9, as described here. The [earlier version](https://www.lightbend.com/blog/scala-inliner-optimizer) of this article uses the traditional forms, which are still supported. +The options were simplified for 2.13.9. This page uses the simplified forms. ## Motivation @@ -55,7 +55,7 @@ However, even when staying within these constraints, some changes performed by t - Inlined methods disappear from call stacks. - This can lead to unexpected behaviors when using a debugger. - - Related: line numbers (stored in bytecode) are discarded when a method is inlined into a different classfile, which also impacts debugging experience. (This [could be improved](https://github.com/scala/scala-dev/issues/3) and is expected to [progress](https://github.com/lampepfl/dotty/pull/11492).) + - Related: line numbers (stored in bytecode) are discarded when a method is inlined into a different classfile, which also impacts debugging experience. (This [could be improved](https://github.com/scala/scala-dev/issues/3) and is expected to [progress](https://github.com/scala/scala3/pull/11492).) - Inlining a method can delay class loading of the class where the method is defined. diff --git a/_overviews/contribute/add-guides.md b/_overviews/contribute/add-guides.md index bbb43a9686..4840739cda 100644 --- a/_overviews/contribute/add-guides.md +++ b/_overviews/contribute/add-guides.md @@ -12,7 +12,7 @@ involved with complex tools like the compiler. ## Architecture -This documentation website is backed by an open-source [github repository](https://github.com/scala/docs.scala-lang), +This documentation website is backed by an open-source [GitHub repository](https://github.com/scala/docs.scala-lang), and is always contribution-ready. ### Content @@ -34,10 +34,10 @@ The website is statically generated from [Markdown](https://en.wikipedia.org/wik This workflow was chosen to help contributors to focus on writing helpful content, rather than on configuration and boilerplate. It also aids publishing a static site in a central location. -The markdown syntax being used supports [Maruku](https://github.com/bhollis/maruku) extensions, and has automatic +The Markdown syntax being used supports [Maruku](https://github.com/bhollis/maruku) extensions, and has automatic syntax highlighting, without the need for any tags. -Additionally [mdoc](https://github.com/scalameta/mdoc) is used during pull requests to validate Scala code blocks. +Additionally, [mdoc](https://github.com/scalameta/mdoc) is used during pull requests to validate Scala code blocks. To use this feature you must use the backtick notation as documented by mdoc, [see here](#code-blocks) for an example. @@ -61,7 +61,29 @@ on [docs.scala-lang.org][home]. And a paragraph, with a [link](https://www.scala-lang.org). -You can contribute code in a markdown document by either +Tables of contents will be automatically generated in a sidebar for your document, and syntax highlighting +is provided. + +### Criteria for Docs to be Accepted + +The goal of this documentation repository is to be highly curated, rather than the approach by other community-driven +documentation platforms, like wikis. Therefore, to be included on [docs.scala-lang.org][home], a document must: + +- **"fit in"** to the repository (_i.e.,_ it should not be a complete duplicate of another article), +- **be polished**, i.e. it must be thorough, complete, correct, and organized; written as an article to be understood + by many users. +- **be maintained**, if the document might require revisions from time to time, be prepared to keep it up to date, or +nominate someone to take ownership. + +If you have something you're thinking about contributing, or that you're thinking about writing in order to contribute +-- we'd love to consider it! Please don't hesitate to use GitHub issues and pull requests and the +`#scala-contributors` room [on Discord](https://discord.com/invite/scala) for any questions, concerns, +clarifications, etc. + +## Code blocks + +It's common for various kinds of documents to require code examples. +You can contribute code in a Markdown document by either - in-line by putting backticks around it, - surrounding by triple backticks, - or indenting it by 4 spaces, e.g.: @@ -70,6 +92,7 @@ You can contribute code in a markdown document by either inline example: `val x = 23` block example: + ```scala println("hello") ``` @@ -79,24 +102,169 @@ indented example: case class Foo(x: Int) ~~~ -Tables of contents will be automatically generated in a sidebar for your document, and syntax highlighting -is provided. +### Scala 2 vs Scala 3 -### Criteria for Docs to be Accepted +Our goal is to have a unified documentation that covers both Scala 2 and Scala 3. In many cases, the +code examples are the same in both Scala 2 and Scala 3, but sometimes there are some syntactic +differences. In some less common cases, a page may explain a concept that is new in Scala 3 and has +no equivalent in Scala 2, or a concept that has been removed in Scala 3. In all the cases, the +documentation should clearly "label" the code examples so that the readers know in which versions +of Scala they are valid. -The goal of this documentation repository is to be highly curated, rather than the approach by other community-driven -documentation platforms, like wikis. Therefore, to be included on [docs.scala-lang.org][home], a document must: +The following sections explain how to properly "label" the code examples. -- **"fit in"** to the repository (_i.e.,_ it should not be a complete duplicate of another article), -- **be polished**, i.e. it must be thorough, complete, correct, and organized; written as an article to be understood - by many users. -- **be maintained**, if the document might require revisions from time to time, be prepared to keep it up to date, or -nominate someone to take ownership. +#### Labelling the code snippets of a page documenting a concept available in both Scala 2 and Scala 3 -If you have something you're thinking about contributing, or that you're thinking about writing in order to contribute --- we'd love to consider it! Please don't hesitate to use GitHub issues and pull requests and the -`#scala-contributors` room [on Discord](https://discord.com/invite/scala) for any questions, concerns, -clarifications, etc. +When the content of a page not specific to Scala 2 or Scala 3, like for example our +[Hello World][hello-world] chapter of the Scala Book, the code snippets should show both the +Scala 2 and Scala 3 syntax. We achieve this by labelling the code snippets in tabs according +to the following rules: + +- if the idiomatic syntax is different in Scala 2 and Scala 3, we create two tabs, + “Scala 2” and “Scala 3”, showing the corresponding syntax +- if the code snippet is idiomatic in both Scala 2 and Scala 3, we create a single tab, + “Scala 2 and 3” +- if the code snippet is valid only in Scala 2 or Scala 3, we create a single tab, + “Scala 2 Only” or “Scala 3 Only” + +Here is an example of how you +can generate such tabs in Markdown with the `tabs` directive and class `tabs-scala-version`: + +<!-- {% raw %} --> +~~~liquid +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +object hello extends App { + println("Hello, World!") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +@main def hello() = println("Hello, World!") +``` +{% endtab %} + +{% endtabs %} +~~~ +<!-- {% endraw %} --> + +It is crucial that you use the `tabs-scala-version` class to benefit from some cool user interactions: +- all other Scala version tabs on the same page will also switch to current tab, whenever one is changed. +- the tab picked will be remembered across the site, and when the user returns to the page after some time. + +For code snippets that are valid in both Scala 2 and Scala 3, please use a single tab labelled +`'Scala 2 and 3'` (please note that the `tabs-scala-version` class is also dropped): + +<!-- {% raw %} --> +~~~liquid +{% tabs scala-2-and-3-demo %} +{% tab 'Scala 2 and 3' %} +```scala +List(1, 2, 3).map(x => x + 1).sum +``` +{% endtab %} +{% endtabs %} +~~~ +<!-- {% endraw %} --> + +For examples that only apply to either one of Scala 2 or 3, use the tabs `'Scala 2 Only'` and `'Scala 3 Only'`. + +If you have a particularly long tab, for readability you can indicate which tab group it belongs to with +a parameter `for=tab-group` as in this example: +<!-- {% raw %} --> +~~~liquid +{% tabs my-tab-group class=tabs-scala-version %} +... +{% tab 'Scala 3' for=my-tab-group %} +... +~~~ +<!-- {% endraw %} --> + +#### Labelling an entire page documenting a concept that is specific to a Scala version + +When the content of a page explains a concept that is new in Scala 3 and has no +equivalent in Scala 2 (e.g. [TASTy]({% link scala3/guides/tasty-overview.md %})), +or a concept that has been removed in Scala 3, we label the entire page instead +of labelling each code example. + +We achieve this by setting a couple of a attributes in the [YAML front +matter](https://jekyllrb.com/docs/front-matter/) of the Markdown file. For +instance, for a page that is specific to Scala 3: + +~~~ yaml +scala3: true +versionSpecific: true +~~~ + +Or, for a page that is specific to Scala 2: + +~~~ yaml +scala2: true +versionSpecific: true +~~~ + +Please note that when the entire page is labelled, its code examples do not +need to have tabs. + +### Typechecked Examples + +The site build process uses [mdoc](https://scalameta.org/mdoc/) to typecheck +code snippets in markdown. This is a great way to ensure the code snippets that +you're including typecheck and are valid. Here are a few quick tips to get +started: + +First, add `mdoc` after `scala` when you are creating a +code block. The `mdoc` modifier here will make sure that `mdoc` runs the code +snippet and ensures that it's valid. + +<div class="language-plaintext highlighter-rouge"> + <div class="highlight"> + <pre class="highlight"> + <code class="hljs scala">```scala mdoc +<span class="hljs-keyword">val</span> a = <span class="hljs-number">1</span> +```</code></pre></div></div> + +If you have a snippet that you expect to fail, you can also account for this by +using `mdoc:fail` for a compile error `mdoc:crash` for a runtime-error. + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:fail +<span class="hljs-keyword">val</span> b: <span class="hljs-type">String</span> = <span class="hljs-number">3</span> <span class="hljs-comment">// won't compile</span> +```</code></pre></div></div> + +Keep in mind that a single file is all compiled as a single unit, so you can't +redefine a variable that was defined above in another code snippet. _However_ +there are a couple ways to get around this. Firstly, you can use the `mdoc:nest` +modifier with will wrap the snippet in a `scala.Predef.locally{...}`. This will +essentially "hide" the snippet from the others. Another way around this is to +use the `mdoc:reset` modifier, which _resets_ and forgets about everything up +above. Here is an example using the various modifiers. + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc +<span class="hljs-keyword">import</span> java.time.<span class="hljs-type">Instant</span> + +<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">now</span></span>() = <span class="hljs-type">Instant</span>.now() +<span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">Foo</span> </span>{} +```</code></pre></div></div> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:nest +<span class="hljs-keyword">case</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">a: <span class="hljs-type">Int</span></span>) <span class="hljs-comment">// conflicts with Foo above, but it's nested so it's fine</span></span> +```</code></pre></div></div> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc +<span class="hljs-keyword">val</span> a = <span class="hljs-string">s"The time is <span class="hljs-subst">${now()}</span>"</span> <span class="hljs-comment">// still have access to the now method from above</span> +```</code></pre></div></div> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:reset +<span class="hljs-keyword">case</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">a: <span class="hljs-type">String</span></span>) <span class="hljs-comment">// forget the previous Foo's and start fresh</span></span> +```</code></pre></div></div> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc +<span class="hljs-keyword">val</span> myFoo = <span class="hljs-type">Foo</span>(<span class="hljs-string">"hi"</span>) <span class="hljs-comment">// now we only have access to the last Foo</span> +```</code></pre></div></div> ## Document Templates @@ -154,7 +322,7 @@ Once the tutorial is written, to aid user navigation their link must be added to the metadata of `/tutorials.md`. e.g. it could look like --- - layout: inner-page-parent + layout: root-index-layout title: Tutorials tutorials: @@ -165,12 +333,12 @@ the metadata of `/tutorials.md`. e.g. it could look like icon: code --- -You must also add the tutorial to the drop down list in the navigation bar. To do this, add an extra entry to +You must also add the tutorial to the drop-down list in the navigation bar. To do this, add an extra entry to `_data/doc-nav-header.yml`. i.e. --- - title: Getting Started - url: "/getting-started/index.html" + url: "/getting-started/install-scala.html" - title: Learn ... - title: Tutorials @@ -184,7 +352,7 @@ You must also add the tutorial to the drop down list in the navigation bar. To d ### Cheatsheets -Cheatsheets have a special layout, and the content is expected to be a markdown table. To contribute a cheatsheet, +Cheatsheets have a special layout, and the content is expected to be a Markdown table. To contribute a cheatsheet, you should use the following format: --- @@ -197,64 +365,9 @@ you should use the following format: |---------|---------| | content | more | -### Code blocks - -The site build process uses [mdoc](https://scalameta.org/mdoc/) to typecheck -code snippets in markdown. This is a great way to ensure the code snippets that -you're including typecheck and are valid. Here are a few quick tips to get -started: - -First, add `mdoc` after `scala` when you are creating a -code block. The `mdoc` modifier here will make sure that `mdoc` runs the code -snippet and ensures that it's valid. - -<div class="language-plaintext highlighter-rouge"> - <div class="highlight"> - <pre class="highlight"> - <code class="hljs scala">```scala mdoc -<span class="hljs-keyword">val</span> a = <span class="hljs-number">1</span> -```</code></pre></div></div> - -If you have a snippet that you expect to fail, you can also account for this by -using `mdoc:fail` for a compile error `mdoc:crash` for a runtime-error. - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:fail -<span class="hljs-keyword">val</span> b: <span class="hljs-type">String</span> = <span class="hljs-number">3</span> <span class="hljs-comment">// won't compile</span> -```</code></pre></div></div> - -Keep in mind that a single file is all compiled as a single unit, so you can't -redefine a variable that was defined above in another code snippet. _However_ -there are a couple ways to get around this. Firstly, you can use the `mdoc:nest` -modifier with will wrap the snippet in a `scala.Predef.locally{...}`. This will -essentially "hide" the snippet from the others. Another way around this is to -use the `mdoc:reset` modifier, which _resets_ and forgets about everything up -above. Here is an example using the various modifiers. - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc -<span class="hljs-keyword">import</span> java.time.<span class="hljs-type">Instant</span> - -<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">now</span></span>() = <span class="hljs-type">Instant</span>.now() -<span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">Foo</span> </span>{} -```</code></pre></div></div> - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:nest -<span class="hljs-keyword">case</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">a: <span class="hljs-type">Int</span></span>) <span class="hljs-comment">// conflicts with Foo above, but it's nested so it's fine</span></span> -```</code></pre></div></div> - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc -<span class="hljs-keyword">val</span> a = <span class="hljs-string">s"The time is <span class="hljs-subst">${now()}</span>"</span> <span class="hljs-comment">// still have access to the now method from above</span> -```</code></pre></div></div> - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc:reset -<span class="hljs-keyword">case</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">a: <span class="hljs-type">String</span></span>) <span class="hljs-comment">// forget the previous Foo's and start fresh</span></span> -```</code></pre></div></div> - -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code class="hljs scala">```scala mdoc -<span class="hljs-keyword">val</span> myFoo = <span class="hljs-type">Foo</span>(<span class="hljs-string">"hi"</span>) <span class="hljs-comment">// now we only have access to the last Foo</span> -```</code></pre></div></div> - [collections-overview]: {% link _overviews/collections-2.13/introduction.md %} [why-contribute]: {% link contribute.md %} [home]: {% link index.md %} [overviews-index]: {% link _overviews/index.md %} -[scala-3-reference]: https://docs.scala-lang.org/scala3/reference/overview.html +[scala-3-reference]: {{ site.scala3ref }} +[hello-world]: {% link _overviews/scala3-book/taste-hello-world.md %} diff --git a/_overviews/contribute/bug-reporting-guide.md b/_overviews/contribute/bug-reporting-guide.md index 6830c18ab9..20dd04546c 100644 --- a/_overviews/contribute/bug-reporting-guide.md +++ b/_overviews/contribute/bug-reporting-guide.md @@ -3,7 +3,7 @@ title: Bug Reporting Guide num: 8 --- -The Scala compiler and standard library bug tracker is located at [https://github.com/scala/bug](https://github.com/scala/bug), and for Scala 3, it is located at [github.com/lampepfl/dotty](https://github.com/lampepfl/dotty/issues). Before you submit a bug make sure that it is certainly a bug by following instructions +The Scala compiler and standard library bug tracker is located at [https://github.com/scala/bug](https://github.com/scala/bug), and for Scala 3, it is located at [github.com/scala/scala3](https://github.com/scala/scala3/issues). Before you submit a bug make sure that it is certainly a bug by following instructions in [Is it a Bug?](#is-it-a-bug). ## Is it a Bug? @@ -34,7 +34,7 @@ If you have a code snippet that is resulting in bytecode which you believe is be 1. Gradually remove parts from the original failing code snippet until you believe you have the simplest representation of your problem. - 2. Ensure that you have decoupled your code snippet from any library that could be introducing the incorrect behavior. One way to achieve this is to try to recompile the offending code snippet in isolation, outside of the context of any complex build environment. If your code depends on some strictly Java library and source code is available for it, make sure that the latter is also minimized. + 2. Ensure that you have decoupled your code snippet from any library that could be introducing the incorrect behavior. One way to achieve this is to try to recompile the offending code snippet in isolation, outside the context of any complex build environment. If your code depends on some strictly Java library and source code is available for it, make sure that the latter is also minimized. 3. Make sure you are compiling your project from a clean slate. Your problem could be related to separate compilation, which is difficult to detect without a clean build with new `.class` files. @@ -42,7 +42,7 @@ If you have a code snippet that is resulting in bytecode which you believe is be 5. If you want to file an improvement in the issue tracker please discuss it first on one of the mailing lists. They offer much bigger audience than issue tracker. The latter is not suitable for long discussions. -* Keep in mind that the behavior you are witnessing could be intended. Good formal resources for verifying whether or not the language behavior is intended is either in the [Scala Improvement Proposal Documents][sips] or in the [Scala Language Specification](https://www.scala-lang.org/files/archive/spec/2.13/). If in doubt, you may always ask on the [Community Category](https://contributors.scala-lang.org/c/community) or [Stack Overflow](https://stackoverflow.com/questions/tagged/scala). +* Keep in mind that the behavior you are witnessing could be intended. Good formal resources for verifying whether the language behavior is intended is either in the [Scala Improvement Proposal Documents][sips] or in the [Scala Language Specification](https://www.scala-lang.org/files/archive/spec/2.13/). If in doubt, you may always ask on the [Community Category](https://contributors.scala-lang.org/c/community) or [Stack Overflow](https://stackoverflow.com/questions/tagged/scala). In general, if you find yourself stuck on any of these steps, asking on [Scala Contributors](https://contributors.scala-lang.org/) can be helpful: @@ -65,7 +65,7 @@ If you cannot find your issue in the issue tracker, create a new bug. The detail Please make sure to fill in as many fields as possible. Make sure you've indicated the following: - 1. **Exact Scala version** that you are using. For example, `2.13.8` or `3.1.2-RC1`. If the bug happens in multiple versions indicate all of them. + 1. **Exact Scala version** that you are using. For example, `2.13.16` or `3.3.4`. If the bug happens in multiple versions indicate all of them. 2. **The component** that is affected by the bug. For example, the Standard Library, Scaladoc, etc. 3. **Labels** related to your issue. For example, if you think your issue is related to the typechecker, and if you have successfully minimized your issue, label your bug as "typechecker" and "minimized". Issue tracker will suggest names for existing labels as you type them so try not to create duplicates. 4. **Running environment**. Are you running on Linux? Windows? What JVM version are you using? diff --git a/_overviews/contribute/codereviews.md b/_overviews/contribute/codereviews.md index be8e66ba46..cb49220627 100644 --- a/_overviews/contribute/codereviews.md +++ b/_overviews/contribute/codereviews.md @@ -19,7 +19,7 @@ own pull requests. * Attach comments to particular lines or regions they pertain to whenever possible. * Short code examples are often more descriptive than prose. * If you have thoroughly reviewed the PR and thought through all angles, LGTM (Looks Good To Me) is the preferred acceptance response. -* Additional reviews should try to offer additional insights: "I also thought about it from this angle and it still looks good.." +* Additional reviews should try to offer additional insights: "I also thought about it from this angle, and it still looks good.." * Above all, remember that the people you are reviewing might be reviewing your PRs one day too. * If you are receiving the review, consider that the advice is being given to make you, and Scala, better rather than as a negative critique. Assume the best, rather than the worst. @@ -28,7 +28,7 @@ own pull requests. <div class="container"> <div class="row"> <div class="span4 doc-block"> - <h4><a href="https://github.com/scala/scala/pulls">lampepfl/dotty</a></h4> + <h4><a href="https://github.com/scala/scala3/pulls">scala/scala3</a></h4> <p>Scala 3 bug fixes and changes in the language, core libs and included tools.</p> </div> <div class="span4 doc-block"> @@ -39,10 +39,10 @@ own pull requests. <div class="row"> <div class="span4 doc-block"> <h4><a href="https://github.com/scala/scala-lang/pulls">scala/scala-lang</a></h4> - <p>The Scala language web site.</p> + <p>The Scala language website.</p> </div> <div class="span4 doc-block"> - <h4><a href="https://github.com/scala/docs.scala-lang.org/pulls">scala/docs.scala-lang.org</a></h4> + <h4><a href="https://github.com/scala/docs.scala-lang/pulls">scala/docs.scala-lang.org</a></h4> <p>Scala documentation site.</p> </div> </div> diff --git a/_overviews/contribute/documentation.md b/_overviews/contribute/documentation.md index bd93b7fbd3..469396e40c 100644 --- a/_overviews/contribute/documentation.md +++ b/_overviews/contribute/documentation.md @@ -22,7 +22,7 @@ The Scala API documentation lives with the scala project source code. There are * [Log issues for missing scaladoc documentation][report-api-doc-bugs] - Please *follow the issue submission process closely* to help prevent duplicate issues being created. -* [Claim Scaladoc Issues and Provide Documentation][scala-standard-library-api-documentation] - please claim issues prior to working on a specific scaladoc task to prevent duplication of effort. If you sit on an issue for too long without submitting a pull request, it will revert back to unassigned and you will need to re-claim it. +* [Claim Scaladoc Issues and Provide Documentation][scala-standard-library-api-documentation] - please claim issues prior to working on a specific scaladoc task to prevent duplication of effort. If you sit on an issue for too long without submitting a pull request, it will revert to unassigned, and you will need to re-claim it. * You can also just [submit new Scaladoc][scala-standard-library-api-documentation] without creating an issue, but please look to see if there is an issue already submitted for your task and claim it if there is. If not, please post your intention to work on a specific scaladoc task on [Scala Contributors](https://contributors.scala-lang.org/) so that people know what you are doing. @@ -42,7 +42,7 @@ without creating an issue, but please look to see if there is an issue already s and more Please read [Add New Guides/Tutorials][add-guides] through before embarking on changes. The site uses -the [Jekyll](https://jekyllrb.com/) markdown engine so you will need to follow the instructions to get that running as well. +the [Jekyll](https://jekyllrb.com/) Markdown engine, so you will need to follow the instructions to get that running as well. ### Updating scala-lang.org diff --git a/_overviews/contribute/guide.md b/_overviews/contribute/guide.md index 33330f511d..f5307a325a 100644 --- a/_overviews/contribute/guide.md +++ b/_overviews/contribute/guide.md @@ -23,7 +23,7 @@ Just to name a few common reasons: The main Scala project consists of the standard Scala library, the Scala reflection and macros library, the Scala compiler and the Scaladoc tool. This means there's plenty to choose from when deciding what to work on. -Typically the scaladoc tool provides a low entry point for new committers, so it is a good first step into contributing. +Typically, the scaladoc tool provides a low entry point for new committers, so it is a good first step into contributing. On the [Scala bug tracker](https://github.com/scala/bug) you will find the bugs that you could pick up. Once you decided on a ticket to look at, see the next step on how to proceed further. @@ -34,7 +34,7 @@ unencumbered by copyrights or patents. ### Bug-fix Check List > Originally these steps cover the [Scala 2 compiler](https://github.com/scala/scala), but they also are relevant to -> the [Scala 3 compiler](https://github.com/lampepfl/dotty). +> the [Scala 3 compiler](https://github.com/scala/scala3). This is the impatient developer's checklist for the steps to submit a bug-fix pull request to the Scala project. For more information, description and justification for the steps, follow the links in that step. Further specific instructions for the release of Scala you are targeting can be found in the `CONTRIBUTING.md` file for that [GitHub branch](https://github.com/scala/scala) @@ -47,13 +47,13 @@ This is the impatient developer's checklist for the steps to submit a bug-fix pu 5. [Fix the bug, or implement the new small feature][hackers-implement], include new tests (yes, for bug fixes too). 6. [Test, rinse][hackers-test] and [test some more][partest-guide] until [all the tests pass][hackers-verify]. 7. [Commit your changes][hackers-commit] to your feature branch in your fork. Please choose your commit message based on the [Git Hygiene](https://github.com/scala/scala#user-content-git-hygiene) section of the Scala project README. -8. If necessary [re-write git history](https://git-scm.com/book/en/Git-Branching-Rebasing) so that [commits are organized by major steps to the fix/feature]( +8. If necessary [re-write git history](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) so that [commits are organized by major steps to the fix/feature]( https://github.com/scala/scala#git-hygiene). For bug fixes, a single commit is requested, for features several commits may be desirable (but each separate commit must compile and pass all tests) 9. [Submit a pull request][hackers-submit]. 10. [Work with a reviewer](https://github.com/scala/scala#reviewing) to [get your pull request merged in][hackers-review]. 11. Celebrate! -Need more information or a little more hand-holding for the first one? We got you covered: take a read through the entire [Hacker Guide][hackers] (or the [equivalent Scala 3 Contributing Guide][scala3-hackers]) for an example of implementing a new feature (some of the steps can be skipped for bug fixes, this will be obvious from reading it, but many of the steps here will help with bug fixes too). +Need more information or a little more hand-holding for the first one? We got you covered: take a read through the entire [Hacker Guide][hackers] (or the [equivalent Scala 3 Contributing Guide][scala3-hackers]) for an example of implementing a new feature (some steps can be skipped for bug fixes, this will be obvious from reading it, but many of the steps here will help with bug fixes too). ### Larger Changes, New Features @@ -68,7 +68,7 @@ the Scala project source tree. The hacker's guides ([Scala 2][hackers], or [Scal [community-tickets]: {% link _overviews/contribute/index.md %}#community-tickets [bug-reporting-guide]: {% link _overviews/contribute/bug-reporting-guide.md %} [bug-report-check-dupes]: {% link _overviews/contribute/bug-reporting-guide.md %}#please-check-before-reporting-a-bug -[scala3-hackers]: {% link _overviews/scala3-contribution/procedures-intro.md %} +[scala3-hackers]: {% link _overviews/contribute/scala3.md %} [contrib-forum]: https://contributors.scala-lang.org/ [why-its-a-good-idea]: {% link _overviews/contribute/scala-internals.md %}#why-its-a-good-idea [hackers-connect]: {% link _overviews/contribute/hacker-guide.md %}#1-connect diff --git a/_overviews/contribute/hacker-guide.md b/_overviews/contribute/hacker-guide.md index 2a70714f53..ea77feee0d 100644 --- a/_overviews/contribute/hacker-guide.md +++ b/_overviews/contribute/hacker-guide.md @@ -27,7 +27,7 @@ One approach would be to go the [Scala 2 bug tracker](https://github.com/scala/b Sometimes it's appealing to hack alone and not to have to interact with others. However, in the context a big project such as Scala, there might be better ways. There are people in the Scala community who have spent years accumulating knowledge about Scala libraries and internals. They might provide unique insights and, what's even better, direct assistance in their areas, so it is not only advantageous, but recommended to communicate with the community about your new patch. -Typically bug fixes and new features start out as an idea or an experiment posted on one of [our forums](https://scala-lang.org/community/index.html#forums) to find out how people feel +Typically, bug fixes and new features start out as an idea or an experiment posted on one of [our forums](https://scala-lang.org/community/index.html#forums) to find out how people feel about things you want to implement. People proficient in certain areas of Scala usually monitor forums and discussion rooms, so you'll often get some help by posting a message. But the most efficient way to connect is to mention in your message one of the people responsible for maintaining the aspect of Scala which you wish to contribute to. @@ -38,7 +38,7 @@ In our running example, since Martin is the person who submitted the string inte As alluded to earlier, one must also choose an appropriate avenue to discuss the issue. Typically, one would use the [Scala Contributor's Forum][contrib-forum], as there are post categories devoted to discussions about the core internal design and implementation of the Scala system. In this example, the issue was previously discussed on the (now unused) scala-user mailing list, at the time, -we would have posted to the [the (now unused) scala-user mailing list](https://groups.google.com/group/scala-user) about our issue: +we would have posted to [the (now unused) scala-user mailing list](https://groups.google.com/group/scala-user) about our issue: <img src="{{ site.baseurl }}/resources/img/01-post.png" alt="Posting to scala-user" class="centerclear" /> <img src="{{ site.baseurl }}/resources/img/02-post.png" alt="Response from Martin" class="centerclear" /> @@ -51,9 +51,9 @@ Hacking Scala begins with creating a branch for your work item. To develop Scala and [GitHub](https://github.com/). This section of the guide provides a short walkthrough, but if you are new to Git, it probably makes sense to familiarize yourself with Git first. We recommend -* the [Git Pro](https://git-scm.com/book/en/) online book. +* the [Git Pro](https://git-scm.com/book/en/v2) online book. * the help page on [Forking a Git Repository](https://help.github.com/articles/fork-a-repo). -* this great training tool [LearnGitBranching](https://pcottle.github.io/learnGitBranching/). One hour hands-on training helps more than 1000 hours reading. +* this great training tool [LearnGitBranching](https://pcottle.github.io/learnGitBranching/). One-hour hands-on training helps more than 1000 hours reading. ### Fork @@ -67,8 +67,8 @@ If you're new to Git, don't be afraid of messing up-- there is no way you can co ### Clone If everything went okay, you will be redirected to your own fork at `https://github.com/user-name/scala`, where `username` -is your GitHub user name. You might find it helpful to read [https://help.github.com/fork-a-repo/](https://help.github.com/fork-a-repo/), -which covers some of the things that will follow below. Then, _clone_ your repository (i.e. pull a copy from GitHub to your local machine) by running the following on the command line: +is your GitHub username. You might find it helpful to read [https://help.github.com/fork-a-repo/](https://help.github.com/fork-a-repo/), +which covers some things that will follow below. Then, _clone_ your repository (i.e. pull a copy from GitHub to your local machine) by running the following on the command line: 16:35 ~/Projects$ git clone https://github.com/xeno-by/scala Cloning into 'scala'... @@ -96,7 +96,7 @@ Since in our example, we're going to fix an existing bug 16:39 ~/Projects/scala (master)$ git checkout -b ticket/6725 Switched to a new branch 'ticket/6725' -If you are new to Git and branching, read the [Branching Chapter](https://git-scm.com/book/en/Git-Branching) in the Git Pro book. +If you are new to Git and branching, read the [Branching Chapter](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) in the Git Pro book. ### Build @@ -129,7 +129,7 @@ We recognise that there exist preferences towards specific IDE/editor experience ## 3. Hack When hacking on your topic of choice, you'll be modifying Scala, compiling it and testing it on relevant input files. -Typically you would want to first make sure that your changes work on a small example and afterwards verify that nothing break +Typically, you would want to first make sure that your changes work on a small example and afterwards verify that nothing break by running a comprehensive test suite. We'll start by creating a `sandbox` directory (`./sandbox` is listed in the .gitignore of the Scala repository), which will hold a single test file and its compilation results. First, let's make sure that @@ -200,12 +200,12 @@ The [Scala Collections Guide][collections-intro] is more general, covering the s ##### The Scala Compiler -Documentation about the internal workings of the Scala compiler is scarce, and most of the knowledge is passed around by forum ([Scala Contributors](https://contributors.scala-lang.org/) forum), chat-rooms (see `#scala-contributors` on [Discord][discord-contrib]), ticket, or word of mouth. However the situation is steadily improving. Here are the resources that might help: +Documentation about the internal workings of the Scala compiler is scarce, and most of the knowledge is passed around by forum ([Scala Contributors](https://contributors.scala-lang.org/) forum), chat-rooms (see `#scala-contributors` on [Discord][discord-contrib]), ticket, or word of mouth. However, the situation is steadily improving. Here are the resources that might help: * [Compiler internals videos by Martin Odersky](https://www.scala-lang.org/old/node/598.html) are quite dated, but still very useful. In this three-video series Martin explains the general architecture of the compiler, and the basics of the front-end, which later became the `scala-reflect` module's API. * [Reflection documentation][reflect-overview] describes fundamental data structures (like `Tree`s, `Symbol`s, and `Types`) that - are used to represent Scala programs and operations defined on then. Since much of the compiler has been factored out and made accessible via the `scala-reflect` module, all of the fundamentals needed for reflection are the same for the compiler. + are used to represent Scala programs and operations defined on then. Since much of the compiler has been factored out and made accessible via the `scala-reflect` module, all the fundamentals needed for reflection are the same for the compiler. * [Scala compiler corner](https://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/) contains extensive documentation about most of the post-typer phases (i.e. the backend) in the Scala compiler. * [Scala Contributors](https://contributors.scala-lang.org/), a forum which hosts discussions about the core @@ -306,7 +306,7 @@ This means your change is backward or forward binary incompatible with the speci ### Verify Now to make sure that my fix doesn't break anything I need to run the test suite. The Scala test suite uses [JUnit](https://junit.org/junit4/) and [partest][partest-guide], a tool we wrote for testing Scala. -Run `sbt test` and `sbt partest` to run all of the JUnit and partest tests, respectively. +Run `sbt test` and `sbt partest` to run all the JUnit and partest tests, respectively. `partest` (not `sbt partest`) also allows you to run a subset of the tests using wildcards: 18:52 ~/Projects/scala/sandbox (ticket/6725)$ cd ../test @@ -333,11 +333,11 @@ Let's go into each of these points in more detail. ### Commit -The [Git Basics](https://git-scm.com/book/en/Git-Basics) chapter in the Git online book covers most of the basic workflow during this stage. +The [Git Basics](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) chapter in the Git online book covers most of the basic workflow during this stage. There are two things you should know here: 1. Commit messages are often the only way to understand the intentions of authors of code written a few years ago. Thus, writing a quality is of utmost importance. The more context you provide for the change you've introduced, the larger the chance that some future maintainer understand your intentions. Consult [the pull request policies](https://github.com/scala/scala/blob/2.12.x/CONTRIBUTING.md) for more information about the desired style of your commits. -2. Keeping Scala's git history clean is also important. Therefore we won't accept pull requests for bug fixes that have more than one commit. For features, it is okay to have several commits, but all tests need to pass after every single commit. To clean up your commit structure, you want to [rewrite history](https://git-scm.com/book/en/Git-Branching-Rebasing) using `git rebase` so that your commits are against the latest revision of `master`. +2. Keeping Scala's git history clean is also important. Therefore we won't accept pull requests for bug fixes that have more than one commit. For features, it is okay to have several commits, but all tests need to pass after every single commit. To clean up your commit structure, you want to [rewrite history](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) using `git rebase` so that your commits are against the latest revision of `master`. Once you are satisfied with your work, synced with `master` and cleaned up your commits you are ready to submit a patch to the central Scala repository. Before proceeding make sure you have pushed all of your local changes to your fork on GitHub. @@ -358,7 +358,7 @@ Once you are satisfied with your work, synced with `master` and cleaned up your ### Submit -Now, we must simply submit our proposed patch. Navigate to your branch in GitHub (for me it was `https://github.com/xeno-by/scala/tree/ticket/6725`) +Now, we must simply submit our proposed patch. Navigate to your branch in GitHub (for me, it was `https://github.com/xeno-by/scala/tree/ticket/6725`) and click the pull request button to submit your patch as a pull request to Scala. If you've never submitted patches to Scala, you will need to sign the contributor license agreement, which [can be done online](https://www.lightbend.com/contribute/cla/scala) within a few minutes. diff --git a/_overviews/contribute/inclusive-language-guide.md b/_overviews/contribute/inclusive-language-guide.md index 375d988158..d32b5144a8 100644 --- a/_overviews/contribute/inclusive-language-guide.md +++ b/_overviews/contribute/inclusive-language-guide.md @@ -90,7 +90,7 @@ This list is neither comprehensive nor definitive, and it can evolve over time. * **blacklist/whitelist** \ While the etymology of these words has no relation to racism, their use suggests an association between the color black and some form of badness or exclusion, and between the color white and some form of goodness or inclusion. Prefer alternatives when possible. - Several alternatives have been proposed but none sticks as "the one". We suggest using the pair *excludelist*/*includelist*, as it is generic enough to replace all uses of *blacklist*/*whitelist*. + Several alternatives have been proposed but none sticks as "the one". We suggest using the pair *denylist*/*allowlist* or the pair *excludelist*/*includelist*, as these are generic enough to replace most uses of *blacklist*/*whitelist*. * **master/slave** \ Never use *slave*. Never use *master* in conjunction with *slave*. @@ -116,10 +116,16 @@ We encourage you to research inclusive language guidelines applicable to your do You may want to use automated software like [In Solidarity](https://github.com/apps/in-solidarity) to steer contributors away from loaded words. +## Dysphemism + +Dysphemisms, the opposite of euphemisms, can be disturbingly violent if you are not used to them. +Examples include the English expressions "pull the trigger" (enforce a decision) and "bite the bullet" (endure hardship). +Prefer the direct meaning instead. + ## Backward compatibility Sometimes, we have existing code, APIs or commands that do not follow the above recommendations. -It is generally advisable to perform renamings to address the issue, but that should not be done to the detriment of backward compatibility (in particular, backward binary compatibility of libraries). +It is generally advisable to perform renaming to address the issue, but that should not be done to the detriment of backward compatibility (in particular, backward binary compatibility of libraries). Deprecated aliases should be retained when possible. Sometimes, it is not possible to preserve backward compatibility through renaming; for example for methods intended to be overridden by user-defined subclasses. diff --git a/_overviews/contribute/index.md b/_overviews/contribute/index.md index da197e233e..1daa8cc13b 100644 --- a/_overviews/contribute/index.md +++ b/_overviews/contribute/index.md @@ -1,17 +1,145 @@ --- -title: Introduction +title: Becoming a Scala OSS Contributor num: 1 + +explore_resources: + - title: Who can contribute? + description: "Open source is for everyone! If you are reading this you are already a contributor..." + icon: "fa fa-hand-sparkles" + link: "#who-can-contribute-to-open-source" + - title: Why should I contribute? + description: "Giving back to the community has many benefits..." + icon: "fa fa-circle-question" + link: "#why-should-i-contribute-to-open-source" + - title: How can I contribute? + description: "From friendly documentation to coding a bug-fix, there is lots to do..." + icon: "fa fa-clipboard-list" + link: "#how-can-i-contribute-to-open-source" + - title: Where should I contribute? + description: "If you are already using OSS, or are curious about projects, you can begin right away..." + icon: "fa fa-check-to-slot" + link: "#how-do-i-choose-where-to-contribute" + +compiler_resources: + - title: "Join the Compiler Issue Spree" + description: "A tri-weekly event where you can get mentored on the compiler. Register for participation here." + icon: "fa fa-clipboard-user" + link: https://airtable.com/app94nwzow5R6W1O6/pagvjIzxYnqTTlhwY/form + - title: "Compiler Academy videos" + description: "In-depth tours of the Scala 3 compiler's internals, aimed to help you get started." + icon: "fa fa-circle-play" + link: https://www.youtube.com/channel/UCIH0OgqE54-KEvYDg4LRhKQ + - title: "Scala 3 contributing guide" + description: "Guide to the Scala 3 Compiler and fixing an issue" + icon: "fa fa-code-merge" + link: https://dotty.epfl.ch/docs/contributing/index.html + +spree_resources: + - title: "Scala open source sprees" + description: "Learn about the next upcoming community spree" + icon: "fa fa-hand-holding-heart" + link: "https://github.com/scalacenter/sprees" + - title: "Upcoming conferences" + description: "See upcoming Scala conferences where you can meet open source maintainers." + icon: "fa fa-calendar-check" + link: "https://www.scala-lang.org/events/" + +scala_resources: + - title: Documentation + description: "Library API docs, new guides on docs.scala-lang.org, and help with scala-lang.org." + icon: fa fa-book + link: /contribute/documentation.html + - title: Bug fixes + description: "Issues with the tools, core libraries and compiler. Also, you can help us by reporting bugs." + icon: fa fa-bug + link: /contribute/guide.html + - title: Code Reviews + description: "Review pull requests against scala/scala, scala/scala3, scala/scala-lang, scala/docs.scala-lang, and others." + icon: fa fa-eye + link: /contribute/codereviews.html + - title: Core Libraries + description: "Update and expand the capabilities of the core (and associated) Scala libraries." + icon: fa fa-clipboard + link: /contribute/corelibs.html + - title: IDE and Build Tools + description: "Enhance the Scala tools with features for build tools, IDE plug-ins and other related projects." + icon: fa fa-terminal + link: /contribute/tools.html + - title: Compiler/Language + description: "Larger language features and compiler enhancements including language specification and SIPs." + icon: fa fa-cogs + link: /contribute/guide.html#larger-changes-new-features + +library_resources: + - title: Library Authors Guide + description: "Lists all the tools that library authors should setup to publish and document their libraries." + icon: "fa fa-book" + link: "/overviews/contributors/index.html" + - title: Make Projects more Inclusive + description: "How you can write code and documentation that welcomes all" + icon: "fa fa-door-open" + link: "inclusive-language-guide.html" + - title: Create a Welcoming Community + description: "Our code of conduct is practical agreement for a healthy community" + icon: "fa fa-handshake-simple" + link: "https://scala-lang.org/conduct" + - title: Binary Compatability Guide + description: "Evolve your library over time, giving users the confidence to upgrade safely." + icon: "fa fa-puzzle-piece" + link: "/overviews/core/binary-compatibility-for-library-authors.html" --- -### Why You Should Contribute To Scala +Welcome to the guide on contributing to all parts of Scala's open-source ecosystem! + +## Newcomers' FAQ + +If you are reading this page, we welcome you, regardless of your background, to begin contributing to Scala's +open-source ecosystem. We have answered some common questions for you below: + +{% include inner-documentation-sections.html links=page.explore_resources %} + +## Ways to start today + +### Join the nearest open source spree + +The [Scala Center](https://scala.epfl.ch) hosts open source sprees, colocated with other Scala events. +In the spree, regular project maintainers will mentor you to create your first contribution to the project. + +{% include inner-documentation-sections.html links=page.spree_resources %} + +### So you want to improve the Scala 3 compiler... + +The [Scala 3 compiler](https://github.com/scala/scala3) is an open source project. +If you are curious about contributing but don't know how to begin, the [Scala Center](https://scala.epfl.ch) +runs the **Scala Compiler Academy** project to onboard and educate new people to the project. You can join the regular +**Compiler Issue Spree**, watch in-depth videos, and read the contributing guide: + +{% include inner-documentation-sections.html links=page.compiler_resources %} + +#### Which areas are perfect for newcomers? +- Adding new linting options, which help enforce cleaner code. +- Improving the clarity of error messages, so that the user understands better what went wrong. +- Add IDE quick-fix actions to error messages, e.g. PR [scala/scala3#18314](https://github.com/scala/scala3/pull/18314). + +### So you want to write a library... + +Read these guides if you are a maintainer of a library, or are thinking of starting a new project: + +{% include inner-documentation-sections.html links=page.library_resources %} + +### Want to improve Scala itself? The Scala programming language is an open source project with a very diverse community, where people from all over the world contribute their work, with everyone benefiting from friendly help and advice, and -kindly helping others in return. So why not join the Scala community and help -everyone make things better? +kindly helping others in return. + +Read on to learn how to join the Scala community and help +everyone make things better. + +## Contributing to the Scala project **What Can I Do?** -That depends on what you want to contribute. Below are some getting started resources for different contribution domains. Please read all of the documentation and follow all the links from the topic pages below before attempting to contribute, as many of the questions you have will already be answered. +That depends on what you want to contribute. Below are some getting started resources for different contribution domains. Please read all the documentation and follow all the links from the topic pages below before attempting to contribute, as many of the questions you have will already be answered. ### Reporting bugs @@ -23,7 +151,7 @@ how to efficiently report a bug. Coordination of contribution efforts takes place on [Scala Contributors](https://contributors.scala-lang.org/). -{% include column-list-of-items.html collection=site.contribute_resources %} +{% include inner-documentation-sections.html links=page.scala_resources %} ### Guidelines @@ -32,12 +160,12 @@ When contributing, please follow: * The [Scala Code of Conduct](https://scala-lang.org/conduct/) * The [Inclusive Language Guide][inclusive-language-guide] -### Community Tickets +### Community tickets -All issues can be found in the [Scala bug tracker](https://github.com/scala/bug), or the [Scala 3 issue tracker](https://github.com/lampepfl/dotty/issues). Most issues are labeled +All issues can be found in the [Scala bug tracker](https://github.com/scala/bug), or the [Scala 3 issue tracker](https://github.com/scala/scala3/issues). Most issues are labeled to make it easier to find issues you are interested in. -### Tools and Libraries +### Tools and libraries The Scala ecosystem includes a great many diverse open-source projects with their own maintainers and community of contributors. Helping out @@ -47,7 +175,7 @@ other projects, see the [Libraries and Tools section](https://scala-lang.org/community/#community-libraries-and-tools) on our Community page. -### Scala Community Build +### Scala community build The Scala community build enables the Scala compiler team to build and test a corpus of @@ -64,5 +192,96 @@ open-source Scala library or tool, please visit the for guidelines on what projects are suitable for the community build and how projects can be added. +## Your questions, answered + +{% capture backButton %} +<p> + <a href="#newcomers-faq"> + <i class="fa-solid fa-angle-up"></i> + <span> return to FAQ</span> + </a> +</p> +{% endcapture %} + +### Who can contribute to open source? +{{backButton}} +- **Everyone:** No matter your skills or background, non-technical or otherwise, there is always + [some way](#how-can-i-contribute-to-open-source) you can contribute to a project. +- **Community organisers:** Communities often form around open source projects, perhaps you would like to help grow a + community. +- **Scala learners:** If you are at the start of your Scala journey, once you have a basic understanding of everyday + Scala programming, becoming familiar with open source code will show you new techniques, helping you to improve + your expertise. +- **Got a cool idea?** Perhaps you have gained confidence in your skills and are looking to give back to the community, + start a new project that fills that perfect niche, or maybe is the life-changing tool everyone never knew they needed. + +### Why should I contribute to open source? +{{backButton}} +- **The world is built on OSS:** + Open Source Software (OSS) libraries are the flesh on top of the bone structure of the core language itself. + They power vast majority of the commercial and non-commercial projects out there alike. +- **Become more visible:** + Contributing is a great way to strengthen your CV. It's also good from the community standpoint: if you do it + consistently, with time, you get to know people, and people get to know you. Such a networking can lead to all + sorts of opportunities. +- **Learn by doing something practical:** Contributing to open source libraries is a great way to learn Scala. + A standard practice in open source software is code review – which means you are going to get expert feedback + about your code. Learning together with feedback from competent people is much faster than making all the + mistakes and figuring them out alone. +- **Have fun and help out:** Finally, by contributing you improve the projects you are using yourself. Being a part of + a maintainer team can be a source of personal satisfaction, and working on an innovative library can be a lot of fun. + +The above benefits are something good to achieve regardless of your level of experience. + +### How can I contribute to open source? +{{backButton}} +- **Documentation:** Often it is outdated, incomplete, or with mistakes. If you see a way to improve the + documentation for a project you are using, you should consider if the project is accepting contributions, + in which case you can submit a pull request to include your changes. +- **Building community:** All projects have users, and users come together to form communities. Managing and growing + communities takes coordination and effort. +- **Issue minimization:** Many of the reported issues found on a project's issue tracker are hard to reproduce and the + reproduction involves a lot of code. However, it is very frequently the case that only a tiny fraction of the + reported setup and code is necessary to reproduce the issue. More reproduction code means more work for the + maintainer to fix an issue. You can help them considerably by investigating already reported issues in an attempt + to make their reproduction as small as possible. +- **Issue reproduction:** Some reported issues lack reproduction instructions at all! If a maintainer can't + reproduce it, they won't be able to fix it. Pinning down exact conditions that make an issue manifest is another + way to contribute. +- **Fixing a bug:** If you are comfortable with reproducing an issue, perhaps you would like to trace its + origin in code, and even try to build a solution that prevents the issue from occurring. +- **Adding a feature:** Sometimes projects maintain lists of planned or requested features, and you could assist + in bringing those ideas to reality. Although please beware - you should only do this if the core maintainers + have already approved the idea for the feature, they are not obligated to accept your additions! +- **Feel free to ask for help:** While implementing or fixing the feature, it is important to ask for help early + when you feel stuck. Even if your code doesn't work, don't hesitate to submit a pull request while stating clearly + that you need help. More information about the guidelines of good contribution you can find in the + [talk by Seth Tisue](https://youtu.be/DTUpSTrnI-0) on how to be a good contributor. +- **Open-source your own project:** Do you have a pet project you are working on? Is there anything you're working + on at work parts of which are generic enough that you can share them online? Open-sourcing your work is a way to + solve a problem for other programmers who may also have it. If you are interested in going open-source, the + [Library Author's Guide](https://docs.scala-lang.org/overviews/contributors/index.html) is an + excellent resource on how to get started. + +### How do I choose where to contribute? +{{backButton}} +- **Ask yourself, what am I using?** The best project to contribute to is the one that you are using yourself. + Take an inventory of your work and hobby projects: what OSS libraries do they use? Have you ever encountered bugs in + them? Or have you ever wanted a certain feature implemented? Pick a bug and a feature and commit to fixing or + implementing it. Clone the project you are trying to improve, figure out how the tests are written and run there. + Write a test for your feature or bug. +- **Try out an awesome library:** [Scaladex](https://index.scala-lang.org/awesome) is a great place to find new + libraries. If you are passionate about contributing but don't see any attractive opportunities to contribute + to projects you are already using, try learning a new Scala library, push it to its limits and see where it can + be improved. For best results, spend a lot of time with the library to get a feel of what's important + and what can improve. +- **Lookout for announcements:** You may want to keep an eye on the Scala Center + [LinkedIn](https://www.linkedin.com/company/scala-center/) and [Bluesky](https://bsky.app/profile/scala-lang.org) or [X](https://x.com/scala_lang) to stay up-to-date with the possible contribution opportunities. For example, every year, the Scala Center participates + in the Google Summer of Code program where you are paid to work on open source Scala projects over the course + of summer. +{{backButton}} + + + [bug-reporting-guide]: {% link _overviews/contribute/bug-reporting-guide.md %} [inclusive-language-guide]: {% link _overviews/contribute/inclusive-language-guide.md %} diff --git a/_overviews/contribute/scala-internals.md b/_overviews/contribute/scala-internals.md index f3b1de54de..738746f9d3 100644 --- a/_overviews/contribute/scala-internals.md +++ b/_overviews/contribute/scala-internals.md @@ -54,7 +54,7 @@ Even if you do not wish to post on [Scala Contributors][scala-contributors], ple anyway, as posting to the forum is *not* criteria for it to be accepted. For smaller, self-contained bugs it is especially less important to make a post, however larger issues or features take more time to consider accepting them. For large contributions we strongly recommend that you do to notify of your intention, which will help you determine if -there is large community support for your change, making it more likely that your large contribution will accepted, +there is large community support for your change, making it more likely that your large contribution will be accepted, before you spend a long time implementing it. [scala-contributors]: https://contributors.scala-lang.org diff --git a/_overviews/contribute/scala-standard-library-api-documentation.md b/_overviews/contribute/scala-standard-library-api-documentation.md index a6c812b7e4..27f2093d93 100644 --- a/_overviews/contribute/scala-standard-library-api-documentation.md +++ b/_overviews/contribute/scala-standard-library-api-documentation.md @@ -39,7 +39,7 @@ package objects for important packages (these often get overlooked for documenta and are a good place for API overviews). If you find an issue, please log it in the [Scala bug tracker](https://github.com/scala/bug), -(or else the [Scala 3 issue tracker](https://github.com/lampepfl/dotty/issues) for Scala 3 library additions) +(or else the [Scala 3 issue tracker](https://github.com/scala/scala3/issues) for Scala 3 library additions) **after making sure it is not already logged as an issue**. To help with disambiguation, please use the following format for issue title: @@ -81,7 +81,7 @@ new API documentation to save time, effort, mistakes and repetition. * [Scaladoc for library authors][scaladoc-lib-authors] covers the use of scaladoc tags, markdown and other features. * [Scaladoc's interface][scaladoc-interface] - covers all of the features of Scaladoc's interface, e.g. switching between + covers all the features of Scaladoc's interface, e.g. switching between companions, browsing package object documentation, searching, token searches and so on. * Prior to commit, be sure to read @@ -92,11 +92,11 @@ new API documentation to save time, effort, mistakes and repetition. message formats, noting *present tense*, *length limits* and that it must merge cleanly. Remember that the title of the pull request will become the commit message when merged. **Also**, be sure to assign one or more reviewers to the PR, if this is - not possible for you, you could mention a user in **in the pull request comments**. + not possible for you, you could mention a user **in the pull request comments**. ### Extra Requirements for Scaladoc Documentation Commits -Although some of the requirements for bug fix pull requests are not needed for +Although some requirements for bug fix pull requests are not needed for API documentation commits, here are the step by step requirements to ensure your API documentation PR is merged in smoothly: diff --git a/_overviews/contribute/scala3.md b/_overviews/contribute/scala3.md new file mode 100644 index 0000000000..2501012a1e --- /dev/null +++ b/_overviews/contribute/scala3.md @@ -0,0 +1,13 @@ +--- +title: Contribute to Scala 3 +description: This page describes the format of the contribution guide for the Scala 3 compiler. +num: 14 +redirect_from: /scala3/guides/contribution/contribution-intro.html +--- +Thank you for wanting to contribute to Scala 3! + +Dotty is an open-source project, and as such, we welcome contributions from the community to help us make it even better. + +If you are interested in contributing to Scala 3, please visit the project [developer website](https://dotty.epfl.ch/docs/contributing/index.html), where you will find all the information you need to get started. We encourage everyone, regardless of their level of expertise, to contribute to Scala 3, as there are many ways to help, from fixing bugs and implementing new features to improving documentation and testing. + +If you have any questions, please feel free to ask them on the [Contributors Forum](https://contributors.scala-lang.org/c/scala-3/scala-3-contributors/9). diff --git a/_overviews/contributors/index.md b/_overviews/contributors/index.md index 7a901fa48e..c482c6f8dc 100644 --- a/_overviews/contributors/index.md +++ b/_overviews/contributors/index.md @@ -22,7 +22,7 @@ that the license and copyright notices are preserved. For the record, Scala itse Once you have chosen a license, *apply* it to your project by creating a `LICENSE` file in the root directory of your project with the license contents or a link to it. This file usually indicates who owns the copyright. -In our example of [LICENSE file](https://github.com/scalacenter/library-example/blob/master/LICENSE), we have +In our example of [LICENSE file](https://github.com/scalacenter/library-example/blob/main/LICENSE), we have written that all the contributors (as per the Git log) own the copyright. ## Host the Source Code @@ -31,15 +31,15 @@ We recommend sharing the source code of your library by hosting it on a public [ hosting site such as [GitHub](https://github.com), [Bitbucket](https://bitbucket.org) or [GitLab](https://gitlab.com). In our example, we use GitHub. -Your project should include a [README](https://github.com/scalacenter/library-example/blob/master/README.md) file +Your project should include a [README](https://github.com/scalacenter/library-example/blob/main/README.md) file including a description of what the library does and some documentation (or links to the documentation). You should take care of putting only source files under version control. For instance, artifacts generated by the build system should *not* be versioned. You can instruct Git to ignore such files by adding them to a -[.gitignore](https://github.com/scalacenter/library-example/blob/master/.gitignore) file. +[.gitignore](https://github.com/scalacenter/library-example/blob/main/.gitignore) file. In case you are using sbt, make sure your repository has a -[project/build.properties](https://github.com/scalacenter/library-example/blob/master/project/build.properties) +[project/build.properties](https://github.com/scalacenter/library-example/blob/main/project/build.properties) file indicating the sbt version to use, so that people (or tools) working on your repository will automatically use the correct sbt version. @@ -49,9 +49,9 @@ The first reason for setting up a continuous integration (CI) server is to syste Examples of CI servers that are free for open source projects are [GitHub Actions](https://github.com/features/actions), [Travis CI](https://travis-ci.com), [Drone](https://drone.io) or [AppVeyor](https://appveyor.com). -Our example uses Github Actions. This feature is enabled by default on GitHub repositories. You can verify if that is +Our example uses GitHub Actions. This feature is enabled by default on GitHub repositories. You can verify if that is the case in the *Actions* section of the *Settings* tab of the repository. -If *Disable all actions* is checked, then Actions are not enabled and you can activate them +If *Disable all actions* is checked, then Actions are not enabled, and you can activate them by selecting *Allow all actions*, *Allow local actions only* or *Allow select actions*. With Actions enabled, you can create a *workflow definition file*. A **workflow** is an automated procedure, @@ -71,20 +71,23 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 # Retrieve the content of the repository - - uses: actions/setup-java@v2 # Set up a jdk + - uses: actions/checkout@v3 # Retrieve the content of the repository + - uses: actions/setup-java@v3 # Set up a jdk with: distribution: temurin java-version: 8 + cache: sbt # Cache the artifacts downloaded by sbt accross CI runs - name: unit tests # Custom action consisting of a shell command run: sbt +test ~~~ -This workflow is called *Continuous integration* and it will run every time one +This workflow is called *Continuous integration*, and it will run every time one or more commits are pushed to the repository. It contains only one job called *ci*, which will run on an Ubuntu runner and that is composed of three -actions. The action `setup-scala` installs the sbt launcher in the runner. Then -the job runs `sbt +test`, which loads the sbt version specified in +actions. The action `setup-java` installs a JDK and caches the library dependencies +downloaded by sbt so that they are not downloaded again everytime the CI runs. + +Then, the job runs `sbt +test`, which loads the sbt version specified in `project/build.properties`, and runs the project tests using the Scala version defined in the file `build.sbt`. @@ -92,11 +95,11 @@ The workflow above will run at any push to any branch of the repository. You can specify the branch or add more triggers such as pull requests, releases, tags or schedules. More information about workflow triggers is available [here](https://docs.github.com/en/actions/reference/events-that-trigger-workflows). -while the `setup-scala` action is hosted [in this -repository](https://github.com/olafurpg/setup-scala). +while the `setup-java` action is hosted [in this +repository](https://github.com/actions/setup-java). For reference, here is our complete [workflow example -file](https://github.com/scalacenter/library-example/blob/master/.github/.workflows/ci.yml). +file](https://github.com/scalacenter/library-example/blob/main/.github/workflows/ci.yml). ## Publish a Release @@ -143,8 +146,8 @@ and [sbt-pgp](https://www.scala-sbt.org/sbt-pgp/) plugins to publish your artifa dependencies to your `project/plugins.sbt` file: ~~~ scala -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") ~~~ And make sure your build fulfills the [Sonatype requirements](https://central.sonatype.org/publish/requirements) @@ -178,7 +181,7 @@ credentials += Credentials("Sonatype Nexus Repository Manager", "(Sonatype password)") ~~~ -(Put your actual user name and password in place of `(Sonatype user name)` and `(Sonatype password)`) +(Put your actual username and password in place of `(Sonatype user name)` and `(Sonatype password)`) **Never** check this file into version control. @@ -186,7 +189,7 @@ Last, we recommend using the [sbt-dynver](https://github.com/dwijnand/sbt-dynver of your releases. Add the following dependency to your `project/plugins.sbt` file: ~~~ scala -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") +addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1") ~~~ And make sure your build does **not** define the `version` setting. @@ -231,6 +234,15 @@ Continuous publication addresses these issues by delegating the publication proc follows: any contributor with write access to the repository can cut a release by pushing a Git tag, the CI server first checks that the tests pass and then runs the publication commands. +We achieve this by replacing the plugins `sbt-pgp`, `sbt-sonatype`, and `sbt-dynver` with `sbt-ci-release`, in the file `project/plugins.sbt`: + +{% highlight diff %} +- addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") +- addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") +- addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1") ++ addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") +{% endhighlight %} + The remaining sections show how to setup GitHub Actions for continuous publication on Sonatype. You can find instructions for Travis CI in the [sbt-ci-release](https://github.com/olafurpg/sbt-ci-release) plugin documentation. @@ -295,31 +307,36 @@ gpg --armor --export %LONG_ID% #### Publish From the CI Server -On GitHub Actions, you can define a workflow to publish the library when a tag is pushed: +On GitHub Actions, you can define a workflow to publish the library when a tag starting with “v” is pushed: -~~~ yaml +{% highlight yaml %} +{% raw %} # .github/workflows/publish.yml name: Continuous publication on: push: - branches: ['**'] tags: [v*] jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetch all tags, required to compute the release version + - uses: actions/setup-java@v3 with: - fetch-depth: 0 # fetch all tags, required for sbt-dynver - - uses: olafurpg/setup-scala@v12 + distribution: temurin + java-version: 8 + cache: sbt - run: sbt ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} -~~~ +{% endraw %} +{% endhighlight %} The `env` statement exposes the secrets you defined earlier to the publication process through environment variables. @@ -333,15 +350,17 @@ $ git tag v0.2.0 $ git push origin v0.2.0 ~~~ +This will trigger the workflow, which will ultimately invoke `sbt ci-release`, which will perform a `publishSigned` followed by a `sonatypeRelease`. + ## Cross-Publish -If you have written a library, you probably want it to be usable from several Scala major versions (e.g., 2.11.x, -2.12.x, 2.13.x, etc.). +If you have written a library, you probably want it to be usable from several Scala major versions (e.g., +2.12.x, 2.13.x, 3.x, etc.). Define the versions you want to support in the `crossScalaVersions` setting, in your `build.sbt` file: ~~~ scala -crossScalaVersions := Seq("2.13.6", "2.12.14") +crossScalaVersions := Seq("3.3.0", "2.13.12", "2.12.18") scalaVersion := crossScalaVersions.value.head ~~~ @@ -367,26 +386,24 @@ an sbt-site to GitHub Pages. ### Create the Documentation Site -In this example we choose to use [Paradox](https://developer.lightbend.com/docs/paradox/current/index.html) -because it runs on the JVM and thus doesn’t require setting up another VM on your system (in contrast with +In this example we choose to use [Paradox](https://github.com/lightbend/paradox) +because it runs on the JVM and thus doesn't require setting up another VM on your system (in contrast with most other documentation generators, which are based on Ruby, Node.js or Python). To install Paradox and sbt-site, add the following lines to your `project/plugins.sbt` file: ~~~ scala -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") -addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.9.2") +addSbtPlugin("com.github.sbt" % "sbt-site-paradox" % "1.5.0") ~~~ And then add the following configuration to your `build.sbt` file: {% highlight scala %} -enablePlugins(ParadoxPlugin, ParadoxSitePlugin) +enablePlugins(ParadoxSitePlugin, SitePreviewPlugin) Paradox / sourceDirectory := sourceDirectory.value / "documentation" {% endhighlight %} -The `ParadoxPlugin` is responsible of generating the website, and the `ParadoxSitePlugin` provides -integration with `sbt-site`. +The `ParadoxSitePlugin` provides a task `makeSite` that generates a website using [Paradox](https://github.com/lightbend/paradox), and the `SitePreviewPlugin` provides handy tasks when working on the website content, to preview the result in your browser. The second line is optional, it defines the location of the website source files. In our case, in `src/documentation`. @@ -395,6 +412,7 @@ uses the library name as title, shows a short sentence describing the purpose of snippet for adding the library to a build definition: {% highlight markdown %} +{% raw %} # Library Example A library that does nothing. @@ -413,6 +431,7 @@ libraryDependencies += "ch.epfl.scala" %% "library-example" % "$project.version$ * [Getting Started](getting-started.md) * [Reference](reference.md) @@@ +{% endraw %} {% endhighlight %} Note that in our case we rely on a variable substitution mechanism to inject the correct version number @@ -454,6 +473,8 @@ code fences have been updated to also include the result of evaluating the Scala Another approach consists in embedding fragments of Scala source files that are part of a module which is compiled by your build. For instance, given the following test in file `src/test/ch/epfl/scala/Usage.scala`: +{% tabs usage-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ~~~ scala package ch.epfl.scala @@ -470,16 +491,37 @@ object Usage extends Scalaprops { } ~~~ +{% endtab %} +{% tab 'Scala 3' %} +~~~ scala +package ch.epfl.scala + +import scalaprops.{Property, Scalaprops} + +object Usage extends Scalaprops: + + val testDoNothing = +// #do-nothing + Property.forAll: (x: Int) => + Example.doNothing(x) == x +// #do-nothing + +end Usage +~~~ +{% endtab %} +{% endtabs %} You can embed the fragment surrounded by the `#do-nothing` identifiers with the `@@snip` Paradox directive, as shown in the `src/documentation/reference.md` file: {% highlight markdown %} +{% raw %} # Reference The `doNothing` function takes anything as parameter and returns it unchanged: @@snip [Usage.scala]($root$/src/test/scala/ch/epfl/scala/Usage.scala) { #do-nothing } +{% endraw %} {% endhighlight %} The resulting documentation looks like the following: @@ -516,7 +558,7 @@ The `@scaladoc` directive will produce a link to the `/api/ch/epfl/scala/Example Add the `sbt-ghpages` plugin to your `project/plugins.sbt`: ~~~ scala -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") +addSbtPlugin("com.github.sbt" % "sbt-ghpages" % "0.8.0") ~~~ And add the following configuration to your `build.sbt`: @@ -552,7 +594,7 @@ can browse it at [https://scalacenter.github.io/library-example/](https://scalac You can extend `.github/workflows/publish.yml` to automatically publish documentation to GitHub pages. To do so, add another job: -```yml +```yaml # .github/workflows/publish.yml name: Continuous publication @@ -561,8 +603,14 @@ jobs: publishSite: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: olafurpg/setup-scala@v12 + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + cache: sbt - name: Generate site run: sbt makeSite - uses: JamesIves/github-pages-deploy-action@4.1.3 @@ -585,7 +633,7 @@ Add a `CONTRIBUTING.md` file to your repository, answering the following questio What are the coding practices to follow? Where are the tests and how to run them? For reference, you can read our minimal example of -[`CONTRIBUTING.md` file](https://github.com/scalacenter/library-example/blob/master/CONTRIBUTING.md). +[`CONTRIBUTING.md` file](https://github.com/scalacenter/library-example/blob/main/CONTRIBUTING.md). ### Issue Labels @@ -629,19 +677,19 @@ jobs: From the user point of view, upgrading to a new version of a library should be a smooth process. Possibly, it should even be a “non-event”. -Breaking changes and migration steps should be thoroughly documented, and a we recommend following the +Breaking changes and migration steps should be thoroughly documented, and we recommend following the [semantic versioning](/overviews/core/binary-compatibility-for-library-authors.html#versioning-scheme---communicating-compatibility-breakages) policy. -The [MiMa](https://github.com/lightbend/migration-manager) tool can help you checking that you don’t +The [MiMa](https://github.com/lightbend/migration-manager) tool can help you to check that you don't break this versioning policy. Add the `sbt-mima-plugin` to your build with the following, in your `project/plugins.sbt` file: ~~~ scala -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.2") ~~~ -Configure it as follow, in `build.sbt`: +Configure it as follows, in `build.sbt`: ~~~ scala mimaPreviousArtifacts := previousStableVersion.value.map(organization.value %% name.value % _).toSet diff --git a/_overviews/core/actors-migration-guide.md b/_overviews/core/actors-migration-guide.md deleted file mode 100644 index c82aecc3e5..0000000000 --- a/_overviews/core/actors-migration-guide.md +++ /dev/null @@ -1,583 +0,0 @@ ---- -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](actors.html) -library is deprecated. Already in Scala 2.10.0 the default actor library is -[Akka](https://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](https://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](https://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](https://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](https://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. - -### 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](https://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](https://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means) -and partially by [actor monitoring](https://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](https://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](https://www.scala-lang.org/downloads) - and can be configured by these [instructions](https://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](https://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](https://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](https://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](https://people.epfl.ch/vojin.jovanovic) and [Philipp Haller](https://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 deleted file mode 100644 index 33a8e40590..0000000000 --- a/_overviews/core/actors.md +++ /dev/null @@ -1,506 +0,0 @@ ---- -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](https://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` 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`](https://www.scala-lang.org/api/current/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-213-collections.md b/_overviews/core/architecture-of-scala-213-collections.md index 4a49e9b42a..1d8da0859d 100644 --- a/_overviews/core/architecture-of-scala-213-collections.md +++ b/_overviews/core/architecture-of-scala-213-collections.md @@ -70,6 +70,8 @@ because we want them to return collection types that are unknown yet. For instance, consider the signature of the `map` operation on `List[A]` and `Vector[A]`: +{% tabs factoring_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=factoring_1 %} ~~~ scala trait List[A] { def map[B](f: A => B): List[B] @@ -79,6 +81,17 @@ trait Vector[A] { def map[B](f: A => B): Vector[B] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=factoring_1 %} +~~~ scala +trait List[A]: + def map[B](f: A => B): List[B] + +trait Vector[A]: + def map[B](f: A => B): Vector[B] +~~~ +{% endtab %} +{% endtabs %} To generalize the type signature of `map` we have to abstract over the resulting *collection type constructor*. @@ -86,6 +99,8 @@ the resulting *collection type constructor*. A slightly different example is `filter`. Consider its type signature on `List[A]` and `Map[K, V]`: +{% tabs factoring_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=factoring_2 %} ~~~ scala trait List[A] { def filter(p: A => Boolean): List[A] @@ -95,6 +110,17 @@ trait Map[K, V] { def filter(p: ((K, V)) => Boolean): Map[K, V] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=factoring_2 %} +~~~ scala +trait List[A]: + def filter(p: A => Boolean): List[A] + +trait Map[K, V]: + def filter(p: ((K, V)) => Boolean): Map[K, V] +~~~ +{% endtab %} +{% endtabs %} To generalize the type signature of `filter` we have to abstract over the resulting *collection type*. @@ -112,9 +138,13 @@ on the `Iterable[A]` collection type. Here is the header of trait `IterableOps`: +{% tabs abstracting_1 %} +{% tab 'Scala 2 and 3' for=abstracting_1 %} ~~~ scala trait IterableOps[+A, +CC[_], +C] { … } ~~~ +{% endtab %} +{% endtabs %} The type parameter `A` stands for the element type of the iterable, the type parameter `CC` stands for the collection type constructor @@ -123,21 +153,36 @@ and the type parameter `C` stands for the collection type. This allows us to define the signature of `filter` and `map` like so: +{% tabs abstracting_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstracting_2 %} ~~~ scala trait IterableOps[+A, +CC[_], +C] { def filter(p: A => Boolean): C = … def map[B](f: A => B): CC[B] = … } ~~~ +{% endtab %} +{% tab 'Scala 3' for=abstracting_2 %} +~~~ scala +trait IterableOps[+A, +CC[_], +C]: + def filter(p: A => Boolean): C = … + def map[B](f: A => B): CC[B] = … +~~~ +{% endtab %} +{% endtabs %} Leaf collection types appropriately instantiate the type parameters. For instance, in the case of `List[A]` we want `CC` to be `List` and `C` to be `List[A]`: +{% tabs abstracting_3 %} +{% tab 'Scala 2 and 3' for=abstracting_3 %} ~~~ scala trait List[+A] extends Iterable[A] with IterableOps[A, List, List[A]] ~~~ +{% endtab %} +{% endtabs %} ## Four branches of templates traits ## @@ -149,19 +194,33 @@ parameter whereas `Map[K, V]` takes two type parameters. To support collection types constructors with two types parameters we have another template trait named `MapOps`: +{% tabs fourBranches_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=fourBranches_1 %} ~~~ scala trait MapOps[K, +V, +CC[_, _], +C] extends IterableOps[(K, V), Iterable, C] { def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = … } -~~~ +~~~ +{% endtab %} +{% tab 'Scala 3' for=fourBranches_1 %} +~~~ scala +trait MapOps[K, +V, +CC[_, _], +C] extends IterableOps[(K, V), Iterable, C]: + def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = … +~~~ +{% endtab %} +{% endtabs %} And then `Map[K, V]` can extend this trait and appropriately instantiate its type parameters: +{% tabs fourBranches_2 %} +{% tab 'Scala 2 and 3' for=fourBranches_2 %} ~~~ scala trait Map[K, V] extends Iterable[(K, V)] with MapOps[K, V, Map, Map[K, V]] ~~~ +{% endtab %} +{% endtabs %} Note that the `MapOps` trait inherits from `IterableOps` so that operations defined in `IterableOps` are also available in `MapOps`. Also note that @@ -169,6 +228,8 @@ the collection type constructor passed to the `IterableOps` trait is `Iterable`. This means that `Map[K, V]` inherits two overloads of the `map` operation: +{% tabs fourBranches_3 %} +{% tab 'Scala 2 and 3' for=fourBranches_3 %} ~~~ scala // from MapOps def map[K2, V2](f: ((K, V)) => (K2, V2)): Map[K2, V2] @@ -176,6 +237,8 @@ def map[K2, V2](f: ((K, V)) => (K2, V2)): Map[K2, V2] // from IterableOps def map[B](f: ((K, V)) => B): Iterable[B] ~~~ +{% endtab %} +{% endtabs %} At use-site, when you call the `map` operation, the compiler selects one of the two overloads. If the function passed as argument to `map` returns a pair, @@ -196,9 +259,18 @@ operations defined in `IterableOps` don’t match the type signature of a more concrete collection type: `SortedSet[A]`. In that case the type signature of the `map` operation is the following: +{% tabs fourBranches_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=fourBranches_4 %} ~~~ scala def map[B](f: A => B)(implicit ord: Ordering[B]): SortedSet[B] ~~~ +{% endtab %} +{% tab 'Scala 3' for=fourBranches_4 %} +~~~ scala +def map[B](f: A => B)(using ord: Ordering[B]): SortedSet[B] +~~~ +{% endtab %} +{% endtabs %} The difference with the signature we have in `IterableOps` is that here we need an implicit `Ordering` instance for the type of elements. @@ -206,24 +278,36 @@ we need an implicit `Ordering` instance for the type of elements. Like for `Map`, `SortedSet` needs a specialized template trait with overloads for transformation operations: +{% tabs fourBranches_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=fourBranches_5 %} ~~~ scala trait SortedSetOps[A, +CC[_], +C] extends IterableOps[A, Set, C] { - def map[B](f: A => B)(implicit ord: Ordering[B]): CC[B] = … - } ~~~ +{% endtab %} +{% tab 'Scala 3' for=fourBranches_5 %} +~~~ scala +trait SortedSetOps[A, +CC[_], +C] extends IterableOps[A, Set, C]: + def map[B](f: A => B)(using ord: Ordering[B]): CC[B] = … +~~~ +{% endtab %} +{% endtabs %} And then collection types that inherit the `SortedSetOps` trait appropriately instantiate its type parameters: +{% tabs fourBranches_6 %} +{% tab 'Scala 2 and 3' for=fourBranches_6 %} ~~~ scala trait SortedSet[A] extends SortedSetOps[A, SortedSet, SortedSet[A]] ~~~ +{% endtab %} +{% endtabs %} Last, there is a fourth kind of collection that requires a specialized template trait: `SortedMap[K, V]`. This type of collection has two type parameters and -needs an implicit ordering instance on the type of keys. Therefore we have a +needs an implicit ordering instance on the type of keys. Therefore, we have a `SortedMapOps` template trait that provides the appropriate overloads. In total, we’ve seen that we have four branches of template traits: @@ -260,11 +344,21 @@ non-strict `View`. For the record, a `View` “describes” an operation applied to a collection but does not evaluate its result until the `View` is effectively traversed. Here is the (simplified) definition of `View`: +{% tabs nonStrict_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=nonStrict_1 %} ~~~ scala trait View[+A] extends Iterable[A] with IterableOps[A, View, View[A]] { def iterator: Iterator[A] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=nonStrict_1 %} +~~~ scala +trait View[+A] extends Iterable[A], IterableOps[A, View, View[A]]: + def iterator: Iterator[A] +~~~ +{% endtab %} +{% endtabs %} A `View` is an `Iterable` that has only one abstract method returning an `Iterator` for traversing its elements. The `View` elements are @@ -276,6 +370,8 @@ Now that we are more familiar with the hierarchy of the template traits, we can a look at the actual implementation of some operations. Consider for instance the implementations of `filter` and `map`: +{% tabs operations_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=operations_1 %} ~~~ scala trait IterableOps[+A, +CC[_], +C] { @@ -289,6 +385,22 @@ trait IterableOps[+A, +CC[_], +C] { protected def from[E](it: IterableOnce[E]): CC[E] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=operations_1 %} +~~~ scala +trait IterableOps[+A, +CC[_], +C]: + + def filter(pred: A => Boolean): C = + fromSpecific(View.Filter(this, pred)) + + def map[B](f: A => B): CC[B] = + from(View.Map(this, f)) + + protected def fromSpecific(coll: IterableOnce[A]): C + protected def from[E](it: IterableOnce[E]): CC[E] +~~~ +{% endtab %} +{% endtabs %} Let’s detail the implementation of `filter`, step by step: @@ -306,6 +418,8 @@ iterable whose element type `E` is arbitrary. Actually, the `from` operation is not defined directly in `IterableOps` but is accessed via an (abstract) `iterableFactory` member: +{% tabs operations_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=operations_2 %} ~~~ scala trait IterableOps[+A, +CC[_], +C] { @@ -313,24 +427,47 @@ trait IterableOps[+A, +CC[_], +C] { def map[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(this, f)) - } ~~~ +{% endtab %} +{% tab 'Scala 3' for=operations_2 %} +~~~ scala +trait IterableOps[+A, +CC[_], +C]: + + def iterableFactory: IterableFactory[CC] + + def map[B](f: A => B): CC[B] = + iterableFactory.from(View.Map(this, f)) +~~~ +{% endtab %} +{% endtabs %} This `iterableFactory` member is implemented by concrete collections and typically refer to their companion object, which provides factory methods to create concrete collection instances. Here is an excerpt of the definition of `IterableFactory`: +{% tabs operations_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=operations_3 %} ~~~ scala trait IterableFactory[+CC[_]] { def from[A](source: IterableOnce[A]): CC[A] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=operations_3 %} +~~~ scala +trait IterableFactory[+CC[_]]: + def from[A](source: IterableOnce[A]): CC[A] +~~~ +{% endtab %} +{% endtabs %} Last but not least, as explained in the above sections, since we have four branches of template traits, we have four corresponding branches of factories. For instance, here are the relevant parts of code of the `map` operation implementation in `MapOps`: +{% tabs operations_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=operations_4 %} ~~~ scala trait MapOps[K, +V, +CC[_, _], +C] extends IterableOps[(K, V), Iterable, C] { @@ -347,11 +484,28 @@ trait MapFactory[+CC[_, _]] { def from[K, V](it: IterableOnce[(K, V)]): CC[K, V] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=operations_4 %} +~~~ scala +trait MapOps[K, +V, +CC[_, _], +C] + extends IterableOps[(K, V), Iterable, C]: + + def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = + mapFactory.from(View.Map(this, f)) + + // Similar to iterableFactory, but for Map collection types + def mapFactory: MapFactory[CC] + +trait MapFactory[+CC[_, _]]: + def from[K, V](it: IterableOnce[(K, V)]): CC[K, V] +~~~ +{% endtab %} +{% endtabs %} ## When a strict evaluation is preferable (or unavoidable) ## In the previous sections we explained that the “strictness” of concrete collections -should be preserved by default operation implementations. However in some cases this +should be preserved by default operation implementations. However, in some cases this leads to less efficient implementations. For instance, `partition` has to perform two traversals of the underlying collection. In some other case (e.g. `groupBy`) it is simply not possible to implement the operation without evaluating the collection @@ -361,6 +515,8 @@ For those cases, we also provide ways to implement operations in a strict mode. The pattern is different: instead of being based on a `View`, it is based on a `Builder`. Here is an outline of the `Builder` trait: +{% tabs builders_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=builders_1 %} ~~~ scala package scala.collection.mutable @@ -369,6 +525,17 @@ trait Builder[-A, +C] { def result(): C } ~~~ +{% endtab %} +{% tab 'Scala 3' for=builders_1 %} +~~~ scala +package scala.collection.mutable + +trait Builder[-A, +C]: + def addOne(elem: A): this.type + def result(): C +~~~ +{% endtab %} +{% endtabs %} Builders are generic in both the element type `A` and the type of collection they return, `C`. @@ -381,6 +548,8 @@ to get a builder resulting in a collection of the same type but with a different type of elements. The following code shows the relevant parts of `IterableOps` and `IterableFactory` to build collections in both strict and non-strict modes: +{% tabs builders_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=builders_2 %} ~~~ scala trait IterableOps[+A, +CC[_], +C] { def iterableFactory: IterableFactory[CC] @@ -393,8 +562,22 @@ trait IterableFactory[+CC[_]] { def newBuilder[A]: Builder[A, CC[A]] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=builders_2 %} +~~~ scala +trait IterableOps[+A, +CC[_], +C]: + def iterableFactory: IterableFactory[CC] + protected def fromSpecific(coll: IterableOnce[A]): C + protected def newSpecificBuilder: Builder[A, C] + +trait IterableFactory[+CC[_]]: + def from[A](source: IterableOnce[A]): CC[A] + def newBuilder[A]: Builder[A, CC[A]] +~~~ +{% endtab %} +{% endtabs %} -Note that, in general, an operation that doesn’t *have to* be strict should +Note that, in general, an operation that doesn't *have to* be strict should be implemented in a non-strict mode, otherwise it would lead to surprising behaviour when used on a non-strict concrete collection (you can read more about that statement in diff --git a/_overviews/core/architecture-of-scala-collections.md b/_overviews/core/architecture-of-scala-collections.md index 76bcde648f..74f2bd1b98 100644 --- a/_overviews/core/architecture-of-scala-collections.md +++ b/_overviews/core/architecture-of-scala-collections.md @@ -217,7 +217,7 @@ 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 +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 @@ -270,7 +270,7 @@ 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: +factory, where `A` is a type parameter, is: CanBuildFrom[Set[_], A, Set[A]] @@ -646,7 +646,7 @@ 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 +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 @@ -747,7 +747,7 @@ 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 +their expected types. Its implementation requires a little 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 @@ -789,7 +789,7 @@ storing the strings "abc", "abd", "al", "all" and "xy" would look like this: A sample patricia trie: -<img src="{{ site.baseurl }}/resources/images/patricia.png" width="550"> +<img src="{{ site.baseurl }}/resources/images/patricia.png" alt="Patricia trie" width="550"> To find the node corresponding to the string "abc" in this trie, simply follow the subtree labeled "a", proceed from there to the @@ -979,14 +979,14 @@ provided by the `empty` method, which is the last method defined in } } -We'll now turn to the companion object `PrefixMap`. In fact it is not +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 +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: diff --git a/_overviews/core/binary-compatibility-of-scala-releases.md b/_overviews/core/binary-compatibility-of-scala-releases.md index cadac052e8..f72d3979fd 100644 --- a/_overviews/core/binary-compatibility-of-scala-releases.md +++ b/_overviews/core/binary-compatibility-of-scala-releases.md @@ -7,31 +7,80 @@ 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`](https://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. +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`](https://docs.oracle.com/javase/8/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/lightbend/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). +We check binary compatibility automatically with [MiMa](https://github.com/lightbend/mima). 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). + +Note that for Scala.js and Scala Native, binary compatibility issues result in errors at build time, as opposed to run-time exceptions. +They happen during their respective "linking" phases: `{fast,full}LinkJS` for Scala.js and `nativeLink` for Scala Native. #### Forward and Back -We distinguish forward and backward 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. Forward compatibility allows you to compile on new versions and run on older ones. +We distinguish forward and backward compatibility (think of these as properties of a sequence of versions, not of an individual version). Maintaining backward compatibility means code compiled on an older version will link with code compiled with newer ones. Forward compatibility allows you to compile on new versions and run on older ones. + +Thus, backward compatibility precludes the removal of (non-private) methods, as older versions could call them, not knowing they would be removed, whereas forward 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 source file). + +#### Guarantees and Versioning +For Scala 2, the *minor* version is the *third* number in a version, e.g., 16 in v2.13.16. +The major version is the second number, which is 13 in our example. + +Scala 2 up to 2.13.16 guarantees both backward and forward compatibility across *minor* releases within a single major release. +This is about to change now that [SIP-51 has been accepted](https://docs.scala-lang.org/sips/drop-stdlib-forwards-bin-compat.html), future Scala 2.13 releases may be backward compatible only. + +For Scala 3, the minor version is the *second* number in a version, e.g., 2 in v3.2.1. +The third number is the *patch* version. +The major version is always 3. + +Scala 3 guarantees both backward and forward compatibility across *patch* releases within a single minor release (enforcing forward binary compatibility is helpful to maintain source compatibility). +In particular, this applies within an entire [Long-Term-Support (LTS) series](https://www.scala-lang.org/blog/2022/08/17/long-term-compatibility-plans.html) such as Scala 3.3.x. + +Scala 3 also guarantees *backward* compatibility across *minor* releases in the entire 3.x series, but not forward compatibility. +This means that libraries compiled with any Scala 3.x version can be used in projects compiled with any Scala 3.y version with y >= x. -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). +In addition, Scala 3.x provides backward binary compatibility with respect to Scala 2.13.y. +Libraries compiled with Scala 2.13.y can be used in projects using Scala 3.x. +This policy does not apply to experimental Scala 2 features, which notably includes *macros*. -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. +In general, none of those guarantees apply to *experimental* features and APIs. -#### 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. +#### Checking +For the Scala library artifacts (`scala-library`, `scala-reflect` and `scala3-library`), these guarantees are mechanically checked with [MiMa](https://github.com/lightbend/mima). -In short, we recommend that library authors use [MiMa](https://github.com/lightbend/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. +The *policies* above extend to libraries compiled by particular Scala compiler versions. +Every effort is made to preserve the binary compatibility of artifacts produced by the compiler. +*However*, that cannot be mechanically checked. +It is therefore possible, due to bugs or unforeseen consequences, that recompiling a library with a different compiler version affects its binary API. +We cannot *guarantee* that it will never happen. + +We recommend that library authors use [MiMa](https://github.com/lightbend/mima) themselves to verify compatibility of minor versions before releasing. + +#### TASTy and Pickle Compatibility +*Binary* compatibility is a concept relevant at link time of the target platform (JVM, Scala.js or Scala Native). +TASTy and Pickle compatibility are similar but apply at *compile* time for the Scala compiler. +TASTy applies to Scala 3, Pickle to Scala 2. + +If a library was compiled with an older version of the compiler, we say that the library is backward TASTy/Pickle compatible if it can be used within an application compiled with a newer compiler version. +Likewise, forward TASTy/Pickle compatibility goes in the other direction. + +The same policies as for binary compatibility apply to TASTy/Pickle compatibility, although they are not mechanically checked. + +Library authors may automatically check TASTy/Pickle backward compatibility for their libraries using [TASTy-MiMa](https://github.com/scalacenter/tasty-mima). +Disclaimer: TASTy-MiMa is a young project. +At this point, you are likely going to run into bugs. +Please report issues you find to its issue tracker. #### 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 -- the `scala.reflect.internal` and `scala.reflect.io` packages, as scala-reflect is still experimental, and +We guarantee backward compatibility of the `"org.scala-lang" % "scala-library" % "2.N.x"` and `"org.scala-lang" % "scala-reflect" % "2.N.x"` artifacts, except for +- the `scala.reflect.internal` and `scala.reflect.io` packages, as scala-reflect is experimental, and - the `scala.runtime` package, which contains classes used by generated code at runtime. We also strongly discourage relying on the stability of `scala.concurrent.impl`, `scala.sys.process.*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.) +We guarantee backward compatibility of the `"org.scala-lang" % "scala3-library_3" % "3.x.y"` artifact. +Forward compatibility is only guaranteed for `3.N.y` within a given `N`. + +We enforce *backward* (but not forward) 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, from Scala 2.11 until Scala 2.13.0-M1, `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.) +#### Build Tools +Build tools like sbt and mill have assumptions about backward binary compatibility built in. +They build a graph of a project's dependencies and select the most recent versions that are needed. +To learn more, see the page on [library dependencies](https://www.scala-sbt.org/1.x/docs/Library-Dependencies.html) in the sbt documentation. diff --git a/_overviews/core/collections-migration-213.md b/_overviews/core/collections-migration-213.md index 68c5247774..76cd202cd3 100644 --- a/_overviews/core/collections-migration-213.md +++ b/_overviews/core/collections-migration-213.md @@ -15,6 +15,8 @@ The most important changes in the Scala 2.13 collections library are: - Transformation methods no longer have an implicit `CanBuildFrom` parameter. This makes the library easier to understand (in source code, Scaladoc, and IDE code completion). It also makes compiling user code more efficient. - The type hierarchy is simplified. `Traversable` no longer exists, only `Iterable`. - The `to[Collection]` method was replaced by the `to(Collection)` method. + - The `toC` methods are strict by convention and yield the default collection type where applicable. For example, `Iterator.continually(42).take(10).toSeq` produces a `List[Int]` and without the limit would not. + - `toIterable` is deprecated wherever defined. For `Iterator`, in particular, prefer `to(LazyList)`. - Views have been vastly simplified and work reliably now. They no longer extend their corresponding collection type, for example, an `IndexedSeqView` no longer extends `IndexedSeq`. - `collection.breakOut` no longer exists, use `.view` and `.to(Collection)` instead. - Immutable hash sets and hash maps have a new implementation (`ChampHashSet` and `ChampHashMap`, based on the ["CHAMP" encoding](https://michael.steindorfer.name/publications/oopsla15.pdf)). @@ -27,7 +29,7 @@ The most important changes in the Scala 2.13 collections library are: ## Tools for migrating and cross-building -The [scala-collection-compat](https://github.com/scala/scala-collection-compat) is a library released for 2.11, 2.12 and 2.13 that provides some of the new APIs from Scala 2.13 for the older versions. This simplifies cross-building projects. +The [scala-collection-compat](https://github.com/scala/scala-collection-compat) is a library released for 2.11, 2.12 and 2.13 that provides some new APIs from Scala 2.13 for the older versions. This simplifies cross-building projects. The module also provides [migration rules](https://github.com/scala/scala-collection-compat#migration-tool) for [scalafix](https://scalacenter.github.io/scalafix/docs/users/installation.html) that can update a project's source code to work with the 2.13 collections library. @@ -40,7 +42,7 @@ a method such as `orderFood(xs: _*)` the varargs parameter `xs` must be an immut [SLS 6.6]: https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#function-applications -Therefore any method signature in Scala 2.13 which includes `scala.Seq`, varargs, or `scala.IndexedSeq` is going +Therefore, any method signature in Scala 2.13 which includes `scala.Seq`, varargs, or `scala.IndexedSeq` is going to have a breaking change in API semantics (as the immutable sequence types require more — immutability — than the not-immutable types). For example, users of a method like `def orderFood(order: Seq[Order]): Seq[Food]` would previously have been able to pass in an `ArrayBuffer` of `Order`, but cannot in 2.13. @@ -66,7 +68,7 @@ We recommend using `import scala.collection`/`import scala.collection.immutable` `collection.Seq`/`immutable.Seq`. We recommend against using `import scala.collection.Seq`, which shadows the automatically imported `scala.Seq`, -because even if it's a oneline change it causes name confusion. For code generation or macros the safest option +because even if it's a one-line change it causes name confusion. For code generation or macros the safest option is using the fully-qualified `_root_.scala.collection.Seq`. As an example, the migration would look something like this: @@ -79,7 +81,7 @@ object FoodToGo { } ~~~ -However users of this code in Scala 2.13 would also have to migrate, as the result type is source-incompatible +However, users of this code in Scala 2.13 would also have to migrate, as the result type is source-incompatible with any `scala.Seq` (or just `Seq`) usage in their code: ~~~ scala @@ -231,7 +233,7 @@ Other notable changes are: You can make this conversion explicit by writing `f _` or `f(_)` instead of `f`. scala> Map(1 -> "a").map(f _) res10: scala.collection.immutable.Map[Int,String] = ChampHashMap(2 -> a) - - `View`s have been completely redesigned and we expect their usage to have a more predictable evaluation model. + - `View`s have been completely redesigned, and we expect their usage to have a more predictable evaluation model. You can read more about the new design [here](https://scala-lang.org/blog/2017/11/28/view-based-collections.html). - `mutable.ArraySeq` (which wraps an `Array[AnyRef]` in 2.12, meaning that primitives were boxed in the array) can now wrap boxed and unboxed arrays. `mutable.ArraySeq` in 2.13 is in fact equivalent to `WrappedArray` in 2.12, there are specialized subclasses for primitive arrays. Note that a `mutable.ArraySeq` can be used either way for primitive arrays (TODO: document how). `WrappedArray` is deprecated. - There is no "default" `Factory` (previously known as `[A, C] => CanBuildFrom[Nothing, A, C]`): use `Factory[A, Vector[A]]` explicitly instead. diff --git a/_overviews/core/custom-collection-operations.md b/_overviews/core/custom-collection-operations.md index 25f792fd7a..f6d4f08d34 100644 --- a/_overviews/core/custom-collection-operations.md +++ b/_overviews/core/custom-collection-operations.md @@ -29,6 +29,8 @@ as parameter, or an `Iterable[A]` if you need more than one traversal. For instance, say we want to implement a `sumBy` operation that sums the elements of a collection after they have been transformed by a function: +{% tabs sumBy_1 %} +{% tab 'Scala 2 and 3' for=sumBy_1 %} ~~~ scala case class User(name: String, age: Int) @@ -36,10 +38,14 @@ val users = Seq(User("Alice", 22), User("Bob", 20)) println(users.sumBy(_.age)) // “42” ~~~ +{% endtab %} +{% endtabs %} + +{% tabs sumBy_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=sumBy_2 %} We can define the `sumBy` operation as an extension method, using an [implicit class](/overviews/core/implicit-classes.html), so that it can be called like a method: - ~~~ scala import scala.collection.IterableOnce @@ -54,15 +60,35 @@ implicit class SumByOperation[A](coll: IterableOnce[A]) { } } ~~~ - Unfortunately, this extension method does not work with values of type `String` and not even with `Array`. This is because these types are not part of the Scala collections hierarchy. They can be converted to proper collection types, though, but the extension method will not work directly on `String` and `Array` because that would require applying two implicit conversions in a row. +{% endtab %} +{% tab 'Scala 3' for=sumBy_2 %} + +We can define the `sumBy` operation as an extension method so that it can be called like a method: +~~~ scala +import scala.collection.IterableOnce + +extension [A](coll: IterableOnce[A]) + def sumBy[B: Numeric](f: A => B): B = + val it = coll.iterator + var result = f(it.next()) + while it.hasNext do + result = summon[Numeric[B]].plus(result, f(it.next())) + result +~~~ +{% endtab %} +{% endtabs %} + ### Consuming any type that is *like* a collection +{% tabs sumBy_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=sumBy_3 %} + If we want the `sumBy` to work on any type that is *like* a collection, such as `String` and `Array`, we have to add another indirection level: @@ -81,11 +107,34 @@ The type `IsIterable[Repr]` has implicit instances for all types `Repr` that can to `IterableOps[A, Iterable, C]` (for some element type `A` and some collection type `C`). There are instances for actual collection types and also for `String` and `Array`. +{% endtab %} +{% tab 'Scala 3' for=sumBy_3 %} + +We expect the `sumBy` to work on any type that is *like* a collection, such as `String` +and `Array`. Fortunately, the type `IsIterable[Repr]` has implicit instances for all types `Repr` that can be converted +to `IterableOps[A, Iterable, C]` (for some element type `A` and some collection type `C`) and there are +instances for actual collection types and also for `String` and `Array`. + +~~~ scala +import scala.collection.generic.IsIterable + +extension [Repr](repr: Repr)(using iter: IsIterable[Repr]) + def sumBy[B: Numeric](f: iter.A => B): B = + val coll = iter(repr) + ... // same as before +~~~ + +{% endtab %} +{% endtabs %} + ### Consuming a more specific collection than `Iterable` In some cases we want (or need) the receiver of the operation to be more specific than `Iterable`. For instance, some operations make sense only on `Seq` but not on `Set`. +{% tabs sumBy_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=sumBy_4 %} + In such a case, again, the most straightforward solution would be to take as parameter a `Seq` instead of an `Iterable` or an `IterableOnce`, but this would work only with *actual* `Seq` values. If you want to support `String` and `Array` values you have to use `IsSeq` instead. `IsSeq` is similar to @@ -95,6 +144,20 @@ Using `IsSeq` is also required to make your operation work on `SeqView` values, does not extend `Seq`. Similarly, there is an `IsMap` type that makes operations work with both `Map` and `MapView` values. +{% endtab %} +{% tab 'Scala 3' for=sumBy_4 %} + +In such a case, again, the most straightforward solution would be to take as parameter a `Seq` instead +of an `Iterable` or an `IterableOnce`. Similarly to `IsIterable`, `IsSeq` provides a +conversion to `SeqOps[A, Iterable, C]` (for some types `A` and `C`). + +`IsSeq` also make your operation works on `SeqView` values, because `SeqView` +does not extend `Seq`. Similarly, there is an `IsMap` type that makes operations work with +both `Map` and `MapView` values. + +{% endtab %} +{% endtabs %} + ## Producing any collection This situation happens when a library provides an operation that produces a collection while leaving the @@ -105,6 +168,8 @@ Such a type class is typically used to create arbitrary test data. Our goal is to define a `collection` operation that generates arbitrary collections containing arbitrary values. Here is an example of use of `collection`: +{% tabs Gen_1 %} +{% tab 'Scala 2 and 3' for=Gen_1 %} ~~~ scala> collection[List, Int].get res0: List[Int] = List(606179450, -1479909815, 2107368132, 332900044, 1833159330, -406467525, 646515139, -575698977, -784473478, -1663770602) @@ -115,18 +180,33 @@ res1: LazyList[Boolean] = LazyList(_, ?) scala> collection[Set, Int].get res2: Set[Int] = HashSet(-1775377531, -1376640531, -1009522404, 526943297, 1431886606, -1486861391) ~~~ +{% endtab %} +{% endtabs %} A very basic definition of `Gen[A]` could be the following: +{% tabs Gen_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=Gen_2 %} ```scala mdoc trait Gen[A] { /** Get a generated value of type `A` */ def get: A } ``` +{% endtab %} +{% tab 'Scala 3' for=Gen_2 %} +```scala +trait Gen[A]: + /** Get a generated value of type `A` */ + def get: A +``` +{% endtab %} +{% endtabs %} And the following instances can be defined: +{% tabs Gen_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=Gen_3 %} ```scala mdoc import scala.util.Random @@ -150,6 +230,29 @@ object Gen { } ``` +{% endtab %} +{% tab 'Scala 3' for=Gen_3 %} +```scala +import scala.util.Random + +object Gen: + + /** Generator of `Int` values */ + given Gen[Int] with + def get: Int = Random.nextInt() + + /** Generator of `Boolean` values */ + given Gen[Boolean] with + def get: Boolean = Random.nextBoolean() + + /** Given a generator of `A` values, provides a generator of `List[A]` values */ + given[A: Gen]: Gen[List[A]] with + def get: List[A] = + if Random.nextInt(100) < 10 then Nil + else summon[Gen[A]].get :: get +``` +{% endtab %} +{% endtabs %} The last definition (`list`) generates a value of type `List[A]` given a generator of values of type `A`. We could implement a generator of `Vector[A]` or `Set[A]` as @@ -160,6 +263,8 @@ can decide which collection type they want to produce. To achieve that we have to use `scala.collection.Factory`: +{% tabs Gen_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=Gen_4 %} ~~~ scala trait Factory[-A, +C] { @@ -177,6 +282,27 @@ trait Factory[-A, +C] { def newBuilder: Builder[A, C] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=Gen_4 %} +~~~ scala +trait Factory[-A, +C]: + + /** @return A collection of type `C` containing the same elements + * as the source collection `it`. + * @param it Source collection + */ + def fromSpecific(it: IterableOnce[A]): C + + /** Get a Builder for the collection. For non-strict collection + * types this will use an intermediate buffer. + * Building collections with `fromSpecific` is preferred + * because it can be lazy for lazy collections. + */ + def newBuilder: Builder[A, C] +end Factory +~~~ +{% endtab %} +{% endtabs %} The `Factory[A, C]` trait provides two ways of building a collection `C` from elements of type `A`: @@ -193,6 +319,8 @@ In practice, it is recommended to [not eagerly evaluate the elements of the coll Finally, here is how we can implement a generator of arbitrary collection types: +{% tabs Gen_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=Gen_5 %} ~~~ scala import scala.collection.Factory @@ -211,6 +339,22 @@ implicit def collection[CC[_], A](implicit } } ~~~ +{% endtab %} +{% tab 'Scala 3' for=Gen_5 %} +~~~ scala +import scala.collection.Factory + +given[CC[_], A: Gen](using Factory[A, CC[A]]): Gen[CC[A]] with + def get: CC[A] = + val lazyElements = + LazyList.unfold(()) { _ => + if Random.nextInt(100) < 10 then None + else Some((summon[Gen[A]].get, ())) + } + summon[Factory[A, CC[A]]].fromSpecific(lazyElements) +~~~ +{% endtab %} +{% endtabs %} The implementation uses a lazy source collection of a random size (`lazyElements`). Then it calls the `fromSpecific` method of the `Factory` to build the collection @@ -225,10 +369,14 @@ For instance, we want to implement an `intersperse` operation that can be applie any sequence and returns a sequence with a new element inserted between each element of the source sequence: +{% tabs intersperse_1 %} +{% tab 'Scala 2 and 3' for=intersperse_1 %} ~~~ scala List(1, 2, 3).intersperse(0) == List(1, 0, 2, 0, 3) "foo".intersperse(' ') == "f o o" ~~~ +{% endtab %} +{% endtabs %} When we call it on a `List`, we want to get back another `List`, and when we call it on a `String` we want to get back another `String`, and so on. @@ -236,12 +384,15 @@ a `String` we want to get back another `String`, and so on. Building on what we’ve learned from the previous sections, we can start defining an extension method using `IsSeq` and producing a collection by using an implicit `Factory`: +{% tabs intersperse_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=intersperse_2 %} ~~~ scala -import scala.collection.{ AbstractIterator, AbstractView, Factory, SeqOps } +import scala.collection.{ AbstractIterator, AbstractView, Factory } import scala.collection.generic.IsSeq -class IntersperseOperation[A](seqOps: SeqOps[A, Iterable, _]) { - def intersperse[B >: A, That](sep: B)(implicit factory: Factory[B, That]): That = +class IntersperseOperation[Repr](coll: Repr, seq: IsSeq[Repr]) { + def intersperse[B >: seq.A, That](sep: B)(implicit factory: Factory[B, That]): That = { + val seqOps = seq(coll) factory.fromSpecific(new AbstractView[B] { def iterator = new AbstractIterator[B] { val it = seqOps.iterator @@ -254,18 +405,45 @@ class IntersperseOperation[A](seqOps: SeqOps[A, Iterable, _]) { } } }) + } } -implicit def IntersperseOperation[Repr](coll: Repr)(implicit seq: IsSeq[Repr]): IntersperseOperation[seq.A] = - new IntersperseOperation(seq(coll)) +implicit def IntersperseOperation[Repr](coll: Repr)(implicit seq: IsSeq[Repr]): IntersperseOperation[Repr] = + new IntersperseOperation(coll, seq) +~~~ +{% endtab %} +{% tab 'Scala 3' for=intersperse_2 %} +~~~ scala +import scala.collection.{ AbstractIterator, AbstractView, Factory } +import scala.collection.generic.IsSeq + +extension [Repr](coll: Repr)(using seq: IsSeq[Repr]) + def intersperse[B >: seq.A, That](sep: B)(using factory: Factory[B, That]): That = + val seqOps = seq(coll) + factory.fromSpecific(new AbstractView[B]: + def iterator = new AbstractIterator[B]: + val it = seqOps.iterator + var intersperseNext = false + def hasNext = intersperseNext || it.hasNext + def next() = + val elem = if intersperseNext then sep else it.next() + intersperseNext = !intersperseNext && it.hasNext + elem + ) ~~~ +{% endtab %} +{% endtabs %} However, if we try it we get the following behaviour: +{% tabs intersperse_3 %} +{% tab 'Scala 2 and 3' for=intersperse_3 %} ~~~ scala> List(1, 2, 3).intersperse(0) res0: Array[Int] = Array(1, 0, 2, 0, 3) ~~~ +{% endtab %} +{% endtabs %} We get back an `Array` although the source collection was a `List`! Indeed, there is nothing that constrains the result type of `intersperse` to depend on the receiver type. @@ -274,6 +452,8 @@ To produce a collection whose type depends on a source collection, we have to us `scala.collection.BuildFrom` (formerly known as `CanBuildFrom`) instead of `Factory`. `BuildFrom` is defined as follows: +{% tabs intersperse_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=intersperse_4 %} ~~~ scala trait BuildFrom[-From, -A, +C] { /** @return a collection of type `C` containing the same elements @@ -287,11 +467,29 @@ trait BuildFrom[-From, -A, +C] { def newBuilder(from: From): Builder[A, C] } ~~~ +{% endtab %} +{% tab 'Scala 3' for=intersperse_4 %} +~~~ scala +trait BuildFrom[-From, -A, +C]: + /** @return a collection of type `C` containing the same elements + * (of type `A`) as the source collection `it`. + */ + def fromSpecific(from: From)(it: IterableOnce[A]): C + + /** @return a Builder for the collection type `C`, containing + * elements of type `A`. + */ + def newBuilder(from: From): Builder[A, C] +~~~ +{% endtab %} +{% endtabs %} `BuildFrom` has similar operations to `Factory`, but they take an additional `from` parameter. Before explaining how implicit instances of `BuildFrom` are resolved, let’s first have a look at how you can use it. Here is the implementation of `intersperse` based on `BuildFrom`: +{% tabs intersperse_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=intersperse_5 %} ~~~ scala import scala.collection.{ AbstractView, BuildFrom } import scala.collection.generic.IsSeq @@ -308,13 +506,32 @@ class IntersperseOperation[Repr, S <: IsSeq[Repr]](coll: Repr, seq: S) { implicit def IntersperseOperation[Repr](coll: Repr)(implicit seq: IsSeq[Repr]): IntersperseOperation[Repr, seq.type] = new IntersperseOperation(coll, seq) ~~~ +{% endtab %} +{% tab 'Scala 3' for=intersperse_5 %} +~~~ scala +import scala.collection.{ AbstractIterator, AbstractView, BuildFrom } +import scala.collection.generic.IsSeq + +extension [Repr](coll: Repr)(using seq: IsSeq[Repr]) + def intersperse[B >: seq.A, That](sep: B)(using bf: BuildFrom[Repr, B, That]): That = + val seqOps = seq(coll) + bf.fromSpecific(coll)(new AbstractView[B]: + // same as before + ) +~~~ +{% endtab %} +{% endtabs %} Note that we track the type of the receiver collection `Repr` in the `IntersperseOperation` class. Now, consider what happens when we write the following expression: +{% tabs intersperse_6 %} +{% tab 'Scala 2 and 3' for=intersperse_6 %} ~~~ scala List(1, 2, 3).intersperse(0) ~~~ +{% endtab %} +{% endtabs %} An implicit parameter of type `BuildFrom[Repr, B, That]` has to be resolved by the compiler. The type `Repr` is constrained by the receiver type (here, `List[Int]`) and the type `B` is @@ -329,5 +546,5 @@ be `List[Int]`. as parameter, - To also support `String`, `Array` and `View`, use `IsIterable`, - To produce a collection given its type, use a `Factory`, -- To produce a collection based on the type of a source collection and the type of elements of the collection +- To produce a collection based on the type of source collection and the type of elements of the collection to produce, use `BuildFrom`. diff --git a/_overviews/core/custom-collections.md b/_overviews/core/custom-collections.md index ab0432376a..6164ec3af2 100644 --- a/_overviews/core/custom-collections.md +++ b/_overviews/core/custom-collections.md @@ -27,15 +27,21 @@ to choose `Seq` because our collection can contain duplicates and iteration order is determined by insertion order. However, some [properties of `Seq`](/overviews/collections/seqs.html) are not satisfied: +{% tabs notCapped_1 %} +{% tab 'Scala 2 and 3' for=notCapped_1 %} ~~~ scala (xs ++ ys).size == xs.size + ys.size ~~~ +{% endtab %} +{% endtabs %} Consequently, the only sensible choice as a base collection type is `collection.immutable.Iterable`. ### First version of `Capped` class ### +{% tabs capped1_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=capped1_1 %} ~~~ scala import scala.collection._ @@ -72,11 +78,54 @@ class Capped1[A] private (val capacity: Int, val length: Int, offset: Int, elems elem } } - + override def className = "Capped1" } ~~~ +{% endtab %} +{% tab 'Scala 3' for=capped1_1 %} +~~~scala +import scala.collection.* + +class Capped1[A] private (val capacity: Int, val length: Int, offset: Int, elems: Array[Any]) + extends immutable.Iterable[A]: + self => + + def this(capacity: Int) = + this(capacity, length = 0, offset = 0, elems = Array.ofDim(capacity)) + + def appended[B >: A](elem: B): Capped1[B] = + val newElems = Array.ofDim[Any](capacity) + Array.copy(elems, 0, newElems, 0, capacity) + val (newOffset, newLength) = + if length == capacity then + newElems(offset) = elem + ((offset + 1) % capacity, length) + else + newElems(length) = elem + (offset, length + 1) + Capped1[B](capacity, newLength, newOffset, newElems) + end appended + + inline def :+ [B >: A](elem: B): Capped1[B] = appended(elem) + + def apply(i: Int): A = elems((i + offset) % capacity).asInstanceOf[A] + + def iterator: Iterator[A] = new AbstractIterator[A]: + private var current = 0 + def hasNext = current < self.length + def next(): A = + val elem = self(current) + current += 1 + elem + end iterator + + override def className = "Capped1" +end Capped1 +~~~ +{% endtab %} +{% endtabs %} The above listing presents the first version of our capped collection implementation. It will be refined later. The class `Capped1` has a @@ -100,33 +149,37 @@ the `offset`. These two methods, `appended` and `apply`, implement the specific behavior of the `Capped1` collection type. In addition to them, we have to implement `iterator` to make the generic collection operations -(such as `foldLeft`, `count`, etc.) work on `Capped` collections. +(such as `foldLeft`, `count`, etc.) work on `Capped1` collections. Here we implement it by using indexed access. Last, we override `className` to return the name of the collection, -“Capped1”. This name is used by the `toString` operation. +`“Capped1”`. This name is used by the `toString` operation. Here are some interactions with the `Capped1` collection: +{% tabs capped1_2 %} +{% tab 'Scala 2 and 3' for=capped1_2 %} ~~~ scala -scala> new Capped1(capacity = 4) -res0: Capped1[Nothing] = Capped1() +scala> val c0 = new Capped1(capacity = 4) +val c0: Capped1[Nothing] = Capped1() -scala> res0 :+ 1 :+ 2 :+ 3 -res1: Capped1[Int] = Capped1(1, 2, 3) +scala> val c1 = c0 :+ 1 :+ 2 :+ 3 +val c1: Capped1[Int] = Capped1(1, 2, 3) -scala> res1.length -res2: Int = 3 +scala> c1.length +val res2: Int = 3 -scala> res1.lastOption -res3: Option[Int] = Some(3) +scala> c1.lastOption +val res3: Option[Int] = Some(3) -scala> res1 :+ 4 :+ 5 :+ 6 -res4: Capped1[Int] = Capped1(3, 4, 5, 6) +scala> val c2 = c1 :+ 4 :+ 5 :+ 6 +val c2: Capped1[Int] = Capped1(3, 4, 5, 6) -scala> res4.take(3) -res5: collection.immutable.Iterable[Int] = List(3, 4, 5) +scala> val c3 = c2.take(3) +val c3: collection.immutable.Iterable[Int] = List(3, 4, 5) ~~~ +{% endtab %} +{% endtabs %} You can see that if we try to grow the collection with more than four elements, the first elements are dropped (see `res4`). The operations @@ -144,7 +197,13 @@ 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 `Capped1`, maybe like this: - def take(count: Int): Capped1 = … +{% tabs take_signature %} +{% tab 'Scala 2 and 3' for=take_signature %} +```scala +def take(count: Int): Capped1 = … +``` +{% endtab %} +{% endtabs %} This would do the job for `take`. But what about `drop`, or `filter`, or `init`? In fact there are over fifty methods on collections that return @@ -155,6 +214,8 @@ effect, as shown in the next section. ### Second version of `Capped` class ### +{% tabs capped2_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=capped2_1 %} ~~~ scala import scala.collection._ @@ -191,6 +252,44 @@ class Capped2Factory(capacity: Int) extends IterableFactory[Capped2] { } } ~~~ +{% endtab %} +{% tab 'Scala 3' for=capped2_1 %} +~~~ scala +class Capped2[A] private(val capacity: Int, val length: Int, offset: Int, elems: Array[Any]) + extends immutable.Iterable[A], + IterableOps[A, Capped2, Capped2[A]]: + self => + + def this(capacity: Int) = // as before + + def appended[B >: A](elem: B): Capped2[B] = // as before + inline def :+[B >: A](elem: B): Capped2[B] = // as before + def apply(i: Int): A = // as before + + def iterator: Iterator[A] = // as before + + override def className = "Capped2" + override val iterableFactory: IterableFactory[Capped2] = Capped2Factory(capacity) + override protected def fromSpecific(coll: IterableOnce[A]): Capped2[A] = iterableFactory.from(coll) + override protected def newSpecificBuilder: mutable.Builder[A, Capped2[A]] = iterableFactory.newBuilder + override def empty: Capped2[A] = iterableFactory.empty +end Capped2 + +class Capped2Factory(capacity: Int) extends IterableFactory[Capped2]: + + def from[A](source: IterableOnce[A]): Capped2[A] = + (newBuilder[A] ++= source).result() + + def empty[A]: Capped2[A] = Capped2[A](capacity) + + def newBuilder[A]: mutable.Builder[A, Capped2[A]] = + new mutable.ImmutableBuilder[A, Capped2[A]](empty): + def addOne(elem: A): this.type = + elems = elems :+ elem; this +end Capped2Factory +~~~ +{% endtab %} +{% endtabs %} The Capped class needs to inherit not only from `Iterable`, but also from its implementation trait `IterableOps`. This is shown in the @@ -229,31 +328,35 @@ With the refined implementation of the [`Capped2` class](#second-version-of-capp the transformation operations work now as expected, and the `Capped2Factory` class provides seamless conversions from other collections: +{% tabs capped2_2 %} +{% tab 'Scala 2 and 3' for=capped2_2 %} ~~~ scala scala> object Capped extends Capped2Factory(capacity = 4) defined object Capped scala> Capped(1, 2, 3) -res0: Capped2[Int] = Capped2(1, 2, 3) +val res0: Capped2[Int] = Capped2(1, 2, 3) scala> res0.take(2) -res1: Capped2[Int] = Capped2(1, 2) +val res1: Capped2[Int] = Capped2(1, 2) scala> res0.filter(x => x % 2 == 1) -res2: Capped2[Int] = Capped2(1, 3) +val res2: Capped2[Int] = Capped2(1, 3) scala> res0.map(x => x * x) -res3: Capped2[Int] = Capped2(1, 4, 9) +val res3: Capped2[Int] = Capped2(1, 4, 9) scala> List(1, 2, 3, 4, 5).to(Capped) -res4: Capped2[Int] = Capped2(2, 3, 4, 5) +val res4: Capped2[Int] = Capped2(2, 3, 4, 5) ~~~ +{% endtab %} +{% endtabs %} This implementation now behaves correctly, but we can still improve a few things: - since our collection is strict, we can take advantage - of the better performance offered by + of the better performance offered by strict implementations of transformation operations, - since our `fromSpecific`, `newSpecificBuilder` and `empty` operation just forward to the `iterableFactory` member, @@ -262,6 +365,8 @@ a few things: ### Final version of `Capped` class ### +{% tabs capped_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=capped_1 %} ~~~ scala import scala.collection._ @@ -324,6 +429,69 @@ class CappedFactory(capacity: Int) extends IterableFactory[Capped] { } ~~~ +{% endtab %} +{% tab 'Scala 3' for=capped_1 %} +~~~ scala +import scala.collection.* + +final class Capped[A] private (val capacity: Int, val length: Int, offset: Int, elems: Array[Any]) + extends immutable.Iterable[A], + IterableOps[A, Capped, Capped[A]], + IterableFactoryDefaults[A, Capped], + StrictOptimizedIterableOps[A, Capped, Capped[A]]: + self => + + def this(capacity: Int) = + this(capacity, length = 0, offset = 0, elems = Array.ofDim(capacity)) + + def appended[B >: A](elem: B): Capped[B] = + val newElems = Array.ofDim[Any](capacity) + Array.copy(elems, 0, newElems, 0, capacity) + val (newOffset, newLength) = + if length == capacity then + newElems(offset) = elem + ((offset + 1) % capacity, length) + else + newElems(length) = elem + (offset, length + 1) + Capped[B](capacity, newLength, newOffset, newElems) + end appended + + inline def :+ [B >: A](elem: B): Capped[B] = appended(elem) + + def apply(i: Int): A = elems((i + offset) % capacity).asInstanceOf[A] + + def iterator: Iterator[A] = view.iterator + + override def view: IndexedSeqView[A] = new IndexedSeqView[A]: + def length: Int = self.length + def apply(i: Int): A = self(i) + + override def knownSize: Int = length + + override def className = "Capped" + + override val iterableFactory: IterableFactory[Capped] = new CappedFactory(capacity) + +end Capped + +class CappedFactory(capacity: Int) extends IterableFactory[Capped]: + + def from[A](source: IterableOnce[A]): Capped[A] = + source match + case capped: Capped[?] if capped.capacity == capacity => capped.asInstanceOf[Capped[A]] + case _ => (newBuilder[A] ++= source).result() + + def empty[A]: Capped[A] = Capped[A](capacity) + + def newBuilder[A]: mutable.Builder[A, Capped[A]] = + new mutable.ImmutableBuilder[A, Capped[A]](empty): + def addOne(elem: A): this.type = { elems = elems :+ elem; this } + +end CappedFactory +~~~ +{% endtab %} +{% endtabs %} That is it. The final [`Capped` class](#final-version-of-capped-class): @@ -345,33 +513,58 @@ methods (such as `iterator` in our case), if any. ## RNA sequences ## -To start with the second 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 +To start with the second example, say you want to create a new immutable sequence type for RNA strands. +These are sequences of bases A (adenine), U (uracil), G (guanine), and C +(cytosine). The definitions for bases are set up as shown in the +listing of RNA bases below: - 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 immutable 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. +{% tabs Base_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=Base_1 %} +~~~ scala +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) +} +~~~ 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 + +You can see in the above example 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. +{% endtab %} +{% tab 'Scala 3' for=Base_1 %} +~~~ scala +enum Base: + case A, U, G, C + +object Base: + val fromInt: Int => Base = values + val toInt: Base => Int = _.ordinal +~~~ + +Every base is defined as a case of the `Base` enum. `Base` has a companion object +that defines two functions that map between bases and the integers 0 to 3. + +The `toInt` function is implemented by delegating to the `ordinal` method defined on `Base`, +which is automatically defined because `Base` is an enum. Each enum case will have a unique `ordinal` value. +The reverse function, `fromInt`, is implemented as an array. This makes use of the fact that +arrays *are* functions because they inherit from the `Function1` trait. + +{% endtab %} +{% endtabs %} + 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 @@ -383,51 +576,104 @@ representation. ### First version of RNA strands class ### - import collection.mutable - import collection.immutable.{ IndexedSeq, IndexedSeqOps } +{% tabs RNA1_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=RNA1_1 %} +~~~ scala +import collection.mutable +import collection.immutable.{ IndexedSeq, IndexedSeqOps } - final class RNA1 private ( - val groups: Array[Int], - val length: Int - ) extends IndexedSeq[Base] - with IndexedSeqOps[Base, IndexedSeq, RNA1] { +final class RNA1 private ( + val groups: Array[Int], + val length: Int +) extends IndexedSeq[Base] + with IndexedSeqOps[Base, IndexedSeq, RNA1] { - import RNA1._ + import RNA1._ - def apply(idx: Int): Base = { - if (idx < 0 || length <= idx) - throw new IndexOutOfBoundsException - Base.fromInt(groups(idx / N) >> (idx % N * S) & M) - } + def apply(idx: Int): Base = { + if (idx < 0 || length <= idx) + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + } - override protected def fromSpecific(coll: IterableOnce[Base]): RNA1 = - fromSeq(coll.iterator.toSeq) - override protected def newSpecificBuilder: mutable.Builder[Base, RNA1] = - iterableFactory.newBuilder[Base].mapResult(fromSeq) - override def empty: RNA1 = fromSeq(Seq.empty) - override def className = "RNA1" - } + override protected def fromSpecific(coll: IterableOnce[Base]): RNA1 = + fromSeq(coll.iterator.toSeq) + override protected def newSpecificBuilder: mutable.Builder[Base, RNA1] = + iterableFactory.newBuilder[Base].mapResult(fromSeq) + override def empty: RNA1 = fromSeq(Seq.empty) + override def className = "RNA1" +} - object RNA1 { +object RNA1 { - // Number of bits necessary to represent group - private val S = 2 + // Number of bits necessary to represent group + private val S = 2 - // Number of groups that fit in an Int - private val N = 32 / S + // Number of groups that fit in an Int + private val N = 32 / S - // Bitmask to isolate a group - private val M = (1 << S) - 1 + // Bitmask to isolate a group + private val M = (1 << S) - 1 - def fromSeq(buf: collection.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 fromSeq(buf: collection.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) - } + def apply(bases: Base*) = fromSeq(bases) +} +~~~ +{% endtab %} +{% tab 'Scala 3' for=RNA1_1 %} +~~~ scala +import collection.mutable +import collection.immutable.{ IndexedSeq, IndexedSeqOps } + +final class RNA1 private +( val groups: Array[Int], + val length: Int +) extends IndexedSeq[Base], + IndexedSeqOps[Base, IndexedSeq, RNA1]: + + import RNA1.* + + def apply(idx: Int): Base = + if idx < 0 || length <= idx then + throw IndexOutOfBoundsException() + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + + override protected def fromSpecific(coll: IterableOnce[Base]): RNA1 = + fromSeq(coll.iterator.toSeq) + override protected def newSpecificBuilder: mutable.Builder[Base, RNA1] = + iterableFactory.newBuilder[Base].mapResult(fromSeq) + override def empty: RNA1 = fromSeq(Seq.empty) + override def className = "RNA1" +end RNA1 + +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: collection.Seq[Base]): RNA1 = + val groups = new Array[Int]((buf.length + N - 1) / N) + for i <- 0 until buf.length do + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA1(groups, buf.length) + + def apply(bases: Base*) = fromSeq(bases) +end RNA1 +~~~ +{% endtab %} +{% endtabs %} The [RNA strands class listing](#first-version-of-rna-strands-class) above presents the first version of this @@ -484,14 +730,22 @@ 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[Base] = List(A, G, U, A) +{% tabs RNA1_2 %} +{% tab 'Scala 2 and 3' for=RNA1_2 %} + +```scala +scala> val xs = List(A, G, U, A) +val xs: List[Base] = List(A, G, U, A) + +scala> RNA1.fromSeq(xs) +val res1: RNA1 = RNA1(A, G, U, A) - scala> RNA1.fromSeq(xs) - res1: RNA1 = RNA1(A, G, U, A) +scala> val rna1 = RNA1(A, U, G, G, C) +val rna1: RNA1 = RNA1(A, U, G, G, C) +``` - scala> val rna1 = RNA1(A, U, G, G, C) - rna1: RNA1 = RNA1(A, U, G, G, C) +{% endtab %} +{% endtabs %} Also note that the type parameters of the `IndexedSeqOps` trait that we inherit from are: `Base`, `IndexedSeq` and `RNA1`. The first one @@ -507,11 +761,19 @@ third one is `RNA1`. This means that operations like `map` or Here is an example showing the usage of `take` and `filter`: - scala> rna1.take(3) - res5: RNA1 = RNA1(A, U, G) +{% tabs RNA1_3 %} +{% tab 'Scala 2 and 3' for=RNA1_3 %} + +```scala +scala> val rna1_2 = rna1.take(3) +val rna1_2: RNA1 = RNA1(A, U, G) + +scala> val rna1_3 = rna1.filter(_ != U) +val rna1_3: RNA1 = RNA1(A, G, G, C) +``` - scala> rna1.filter(_ != U) - res6: RNA1 = RNA1(A, G, G, C) +{% endtab %} +{% endtabs %} ### Dealing with map and friends ### @@ -523,14 +785,22 @@ 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) +{% tabs RNA1_4 %} +{% tab 'Scala 2 and 3' for=RNA1_4 %} - scala> rna map { case A => U case b => b } - res7: RNA = RNA(U, U, G, G, C) +```scala +scala> val rna = RNA(A, U, G, G, C) +val rna: RNA = RNA(A, U, G, G, C) - scala> rna ++ rna - res8: RNA = RNA(A, U, G, G, C, A, U, G, G, C) +scala> rna.map { case A => U case b => b } +val res7: RNA = RNA(U, U, G, G, C) + +scala> rna ++ rna +val res8: RNA = RNA(A, U, G, G, C, A, U, G, G, C) +``` + +{% endtab %} +{% endtabs %} 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 @@ -538,26 +808,42 @@ 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) +{% tabs RNA1_5 %} +{% tab 'Scala 2 and 3' for=RNA1_5 %} + +```scala +scala> rna.map(Base.toInt) +val res2: IndexedSeq[Int] = Vector(0, 1, 2, 2, 3) + +scala> rna ++ List("missing", "data") +val res3: IndexedSeq[java.lang.Object] = + Vector(A, U, G, G, C, missing, data) +``` - scala> rna ++ List("missing", "data") - res3: IndexedSeq[java.lang.Object] = - Vector(A, U, G, G, C, missing, data) +{% endtab %} +{% endtabs %} This is what you'd expect in the ideal case. But this is not what the [`RNA1` class](#first-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 rna1 = RNA1(A, U, G, G, C) - rna1: RNA1 = RNA1(A, U, G, G, C) +{% tabs RNA1_6 %} +{% tab 'Scala 2 and 3' for=RNA1_6 %} - scala> rna1 map { case A => U case b => b } - res0: IndexedSeq[Base] = Vector(U, U, G, G, C) +```scala +scala> val rna1 = RNA1(A, U, G, G, C) +val rna1: RNA1 = RNA1(A, U, G, G, C) - scala> rna1 ++ rna1 - res1: IndexedSeq[Base] = Vector(A, U, G, G, C, A, U, G, G, C) +scala> rna1.map { case A => U case b => b } +val res0: IndexedSeq[Base] = Vector(U, U, G, G, C) + +scala> rna1 ++ rna1 +val res1: IndexedSeq[Base] = Vector(A, U, G, G, C, A, U, G, G, C) +``` + +{% endtab %} +{% endtabs %} 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 @@ -566,7 +852,13 @@ method (or of `++`, which has a similar signature). The `map` method is originally defined in class `scala.collection.IterableOps` with the following signature: - def map[B](f: A => B): CC[B] +{% tabs map_signature %} +{% tab 'Scala 2 and 3' for=map_signature %} +```scala +def map[B](f: A => B): CC[B] +``` +{% endtab %} +{% endtabs %} Here `A` is the type of elements of the collection, and `CC` is the type constructor passed as a second parameter to the `IterableOps` trait. @@ -576,38 +868,84 @@ this is why we always get a `Vector` as a result. ### Second version of RNA strands class ### - import scala.collection.{ View, mutable } - import scala.collection.immutable.{ IndexedSeq, IndexedSeqOps } - - final class RNA2 private (val groups: Array[Int], val length: Int) - extends IndexedSeq[Base] with IndexedSeqOps[Base, IndexedSeq, RNA2] { - - import RNA2._ - - def apply(idx: Int): Base = // as before - override protected def fromSpecific(coll: IterableOnce[Base]): RNA2 = // as before - override protected def newSpecificBuilder: mutable.Builder[Base, RNA2] = // as before - - // Overloading of `appended`, `prepended`, `appendedAll`, - // `prependedAll`, `map`, `flatMap` and `concat` to return an `RNA2` - // when possible - def concat(suffix: IterableOnce[Base]): RNA2 = - fromSpecific(iterator ++ suffix.iterator) - // symbolic alias for `concat` - @inline final def ++ (suffix: IterableOnce[Base]): RNA2 = concat(suffix) - def appended(base: Base): RNA2 = - fromSpecific(new View.Appended(this, base)) - def appendedAll(suffix: IterableOnce[Base]): RNA2 = - concat(suffix) - def prepended(base: Base): RNA2 = - fromSpecific(new View.Prepended(base, this)) - def prependedAll(prefix: IterableOnce[Base]): RNA2 = - fromSpecific(prefix.iterator ++ iterator) - def map(f: Base => Base): RNA2 = - fromSpecific(new View.Map(this, f)) - def flatMap(f: Base => IterableOnce[Base]): RNA2 = - fromSpecific(new View.FlatMap(this, f)) - } +{% tabs RNA2_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=RNA2_1 %} +~~~ scala +import scala.collection.{ View, mutable } +import scala.collection.immutable.{ IndexedSeq, IndexedSeqOps } + +final class RNA2 private (val groups: Array[Int], val length: Int) + extends IndexedSeq[Base] with IndexedSeqOps[Base, IndexedSeq, RNA2] { + + import RNA2._ + + def apply(idx: Int): Base = // as before + override protected def fromSpecific(coll: IterableOnce[Base]): RNA2 = // as before + override protected def newSpecificBuilder: mutable.Builder[Base, RNA2] = // as before + override def empty: RNA2 = // as before + override def className = "RNA2" + + // Overloading of `appended`, `prepended`, `appendedAll`, + // `prependedAll`, `map`, `flatMap` and `concat` to return an `RNA2` + // when possible + def concat(suffix: IterableOnce[Base]): RNA2 = + fromSpecific(iterator ++ suffix.iterator) + // symbolic alias for `concat` + @inline final def ++ (suffix: IterableOnce[Base]): RNA2 = concat(suffix) + def appended(base: Base): RNA2 = + fromSpecific(new View.Appended(this, base)) + def appendedAll(suffix: IterableOnce[Base]): RNA2 = + concat(suffix) + def prepended(base: Base): RNA2 = + fromSpecific(new View.Prepended(base, this)) + def prependedAll(prefix: IterableOnce[Base]): RNA2 = + fromSpecific(prefix.iterator ++ iterator) + def map(f: Base => Base): RNA2 = + fromSpecific(new View.Map(this, f)) + def flatMap(f: Base => IterableOnce[Base]): RNA2 = + fromSpecific(new View.FlatMap(this, f)) +} +~~~ +{% endtab %} +{% tab 'Scala 3' for=RNA2_1 %} +~~~ scala +import scala.collection.{ View, mutable } +import scala.collection.immutable.{ IndexedSeq, IndexedSeqOps } + +final class RNA2 private (val groups: Array[Int], val length: Int) + extends IndexedSeq[Base], IndexedSeqOps[Base, IndexedSeq, RNA2]: + + import RNA2.* + + def apply(idx: Int): Base = // as before + override protected def fromSpecific(coll: IterableOnce[Base]): RNA2 = // as before + override protected def newSpecificBuilder: mutable.Builder[Base, RNA2] = // as before + override def empty: RNA2 = // as before + override def className = "RNA2" + + // Overloading of `appended`, `prepended`, `appendedAll`, + // `prependedAll`, `map`, `flatMap` and `concat` to return an `RNA2` + // when possible + def concat(suffix: IterableOnce[Base]): RNA2 = + fromSpecific(iterator ++ suffix.iterator) + // symbolic alias for `concat` + inline final def ++ (suffix: IterableOnce[Base]): RNA2 = concat(suffix) + def appended(base: Base): RNA2 = + fromSpecific(View.Appended(this, base)) + def appendedAll(suffix: IterableOnce[Base]): RNA2 = + concat(suffix) + def prepended(base: Base): RNA2 = + fromSpecific(View.Prepended(base, this)) + def prependedAll(prefix: IterableOnce[Base]): RNA2 = + fromSpecific(prefix.iterator ++ iterator) + def map(f: Base => Base): RNA2 = + fromSpecific(View.Map(this, f)) + def flatMap(f: Base => IterableOnce[Base]): RNA2 = + fromSpecific(View.FlatMap(this, f)) +end RNA2 +~~~ +{% endtab %} +{% endtabs %} To address this shortcoming, you need to overload the methods that return an `IndexedSeq[B]` for the case where `B` is known to be `Base`, @@ -622,9 +960,11 @@ collection is strict, we could take advantage of the better performance offered in transformation operations. Also, if we try to convert an `Iterable[Base]` into an `RNA2` it fails: -~~~ +{% tabs RNA2_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=RNA2_2 %} +~~~scala scala> val bases: Iterable[Base] = List(A, U, C, C) -bases: Iterable[Base] = List(A, U, C, C) +val bases: Iterable[Base] = List(A, U, C, C) scala> bases.to(RNA2) ^ @@ -632,9 +972,28 @@ scala> bases.to(RNA2) found : RNA2.type required: scala.collection.Factory[Base,?] ~~~ +{% endtab %} +{% tab 'Scala 3' for=RNA2_2 %} +~~~scala +scala> val bases: Iterable[Base] = List(A, U, C, C) +val bases: Iterable[Base] = List(A, U, C, C) + +scala> bases.to(RNA2) +-- [E007] Type Mismatch Error: ------------------------------------------------- +1 |bases.to(RNA2) + | ^^^^ + | Found: RNA2.type + | Required: scala.collection.Factory[Base, Any] + | + | longer explanation available when compiling with `-explain` +~~~ +{% endtab %} +{% endtabs %} ### Final version of RNA strands class ### +{% tabs RNA_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=RNA_1 %} ~~~ scala import scala.collection.{ AbstractIterator, SpecificIterableFactory, StrictOptimizedSeqOps, View, mutable } import scala.collection.immutable.{ IndexedSeq, IndexedSeqOps } @@ -723,6 +1082,94 @@ object RNA extends SpecificIterableFactory[Base, RNA] { } } ~~~ +{% endtab %} +{% tab 'Scala 3' for=RNA_1 %} +~~~ scala +import scala.collection.{ AbstractIterator, SpecificIterableFactory, StrictOptimizedSeqOps, View, mutable } +import scala.collection.immutable.{ IndexedSeq, IndexedSeqOps } + +final class RNA private +( val groups: Array[Int], + val length: Int +) extends IndexedSeq[Base], + IndexedSeqOps[Base, IndexedSeq, RNA], + StrictOptimizedSeqOps[Base, IndexedSeq, RNA]: + rna => + + import RNA.* + + // Mandatory implementation of `apply` in `IndexedSeqOps` + def apply(idx: Int): Base = + if idx < 0 || length <= idx then + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + + // Mandatory overrides of `fromSpecific`, `newSpecificBuilder`, + // and `empty`, from `IterableOps` + override protected def fromSpecific(coll: IterableOnce[Base]): RNA = + RNA.fromSpecific(coll) + override protected def newSpecificBuilder: mutable.Builder[Base, RNA] = + RNA.newBuilder + override def empty: RNA = RNA.empty + + // Overloading of `appended`, `prepended`, `appendedAll`, `prependedAll`, + // `map`, `flatMap` and `concat` to return an `RNA` when possible + def concat(suffix: IterableOnce[Base]): RNA = + strictOptimizedConcat(suffix, newSpecificBuilder) + inline final def ++ (suffix: IterableOnce[Base]): RNA = concat(suffix) + def appended(base: Base): RNA = + (newSpecificBuilder ++= this += base).result() + def appendedAll(suffix: Iterable[Base]): RNA = + strictOptimizedConcat(suffix, newSpecificBuilder) + def prepended(base: Base): RNA = + (newSpecificBuilder += base ++= this).result() + def prependedAll(prefix: Iterable[Base]): RNA = + (newSpecificBuilder ++= prefix ++= this).result() + def map(f: Base => Base): RNA = + strictOptimizedMap(newSpecificBuilder, f) + def flatMap(f: Base => IterableOnce[Base]): RNA = + strictOptimizedFlatMap(newSpecificBuilder, f) + + // Optional re-implementation of iterator, + // to make it more efficient. + override def iterator: Iterator[Base] = new AbstractIterator[Base]: + private var i = 0 + private var b = 0 + def hasNext: Boolean = i < rna.length + def next(): Base = + b = if i % N == 0 then groups(i / N) else b >>> S + i += 1 + Base.fromInt(b & M) + + override def className = "RNA" +end RNA + +object RNA extends SpecificIterableFactory[Base, 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: collection.Seq[Base]): RNA = + val groups = new Array[Int]((buf.length + N - 1) / N) + for i <- 0 until buf.length do + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA(groups, buf.length) + + // Mandatory factory methods: `empty`, `newBuilder` + // and `fromSpecific` + def empty: RNA = fromSeq(Seq.empty) + + def newBuilder: mutable.Builder[Base, RNA] = + mutable.ArrayBuffer.newBuilder[Base].mapResult(fromSeq) + + def fromSpecific(it: IterableOnce[Base]): RNA = it match + case seq: collection.Seq[Base] => fromSeq(seq) + case _ => fromSeq(mutable.ArrayBuffer.from(it)) +end RNA +~~~ +{% endtab %} +{% endtabs %} The final [`RNA` class](#final-version-of-rna-strands-class): @@ -771,7 +1218,7 @@ storing the strings "abc", "abd", "al", "all" and "xy" would look like this: A sample patricia trie: -<img src="{{ site.baseurl }}/resources/images/patricia.png" width="550"> +<img src="{{ site.baseurl }}/resources/images/patricia.png" alt="Patricia trie" width="550"> To find the node corresponding to the string "abc" in this trie, simply follow the subtree labeled "a", proceed from there to the @@ -793,17 +1240,35 @@ of a map that's implemented as a Patricia trie. We call the map a 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] = PrefixMap((abc,0), (abd,1), (al,2), (all,3), (xy,4)) +{% tabs prefixMap_1 %} +{% tab 'Scala 2 and 3' for=prefixMap_1 %} + +```scala +scala> val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, + "all" -> 3, "xy" -> 4) +val m: PrefixMap[Int] = PrefixMap((abc,0), (abd,1), (al,2), (all,3), (xy,4)) +``` + +{% endtab %} +{% endtabs %} Then calling `withPrefix` on `m` will yield another prefix map: - scala> m withPrefix "a" - res14: PrefixMap[Int] = PrefixMap((bc,0), (bd,1), (l,2), (ll,3)) +{% tabs prefixMap_2 %} +{% tab 'Scala 2 and 3' for=prefixMap_2 %} + +```scala +scala> m.withPrefix("a") +val res14: PrefixMap[Int] = PrefixMap((bc,0), (bd,1), (l,2), (ll,3)) +``` + +{% endtab %} +{% endtabs %} ### Patricia trie implementation ### +{% tabs prefixMap_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=prefixMap_3 %} ~~~ scala import scala.collection._ import scala.collection.mutable.{ GrowableBuilder, Builder } @@ -818,18 +1283,18 @@ class PrefixMap[A] def get(s: String): Option[A] = if (s.isEmpty) value - else suffixes get (s(0)) flatMap (_.get(s substring 1)) + else suffixes.get(s(0)).flatMap(_.get(s.substring(1))) def withPrefix(s: String): PrefixMap[A] = if (s.isEmpty) this else { val leading = s(0) - suffixes get leading match { + suffixes.get(leading) match { case None => suffixes = suffixes + (leading -> empty) case _ => } - suffixes(leading) withPrefix (s substring 1) + suffixes(leading).withPrefix(s.substring(1)) } def iterator: Iterator[(String, A)] = @@ -844,7 +1309,7 @@ class PrefixMap[A] def subtractOne(s: String): this.type = { if (s.isEmpty) { val prev = value; value = None; prev } - else suffixes get (s(0)) flatMap (_.remove(s substring 1)) + else suffixes.get(s(0)).flatMap(_.remove(s.substring(1))) this } @@ -864,7 +1329,7 @@ class PrefixMap[A] // Members declared in scala.collection.IterableOps override protected def fromSpecific(coll: IterableOnce[(String, A)]): PrefixMap[A] = PrefixMap.from(coll) override protected def newSpecificBuilder: mutable.Builder[(String, A), PrefixMap[A]] = PrefixMap.newBuilder - + override def className = "PrefixMap" } @@ -892,6 +1357,91 @@ object PrefixMap { } ~~~ +{% endtab %} +{% tab 'Scala 3' for=prefixMap_3 %} +~~~ scala +import scala.collection.* +import scala.collection.mutable.{ GrowableBuilder, Builder } + +class PrefixMap[A] + extends mutable.Map[String, A], + mutable.MapOps[String, A, mutable.Map, PrefixMap[A]], + StrictOptimizedIterableOps[(String, A), mutable.Iterable, PrefixMap[A]]: + + private var suffixes: immutable.Map[Char, PrefixMap[A]] = immutable.Map.empty + private var value: Option[A] = None + + def get(s: String): Option[A] = + if s.isEmpty then value + else suffixes.get(s(0)).flatMap(_.get(s.substring(1))) + + def withPrefix(s: String): PrefixMap[A] = + if s.isEmpty then this + else + val leading = s(0) + suffixes.get(leading) match + case None => + suffixes = suffixes + (leading -> empty) + case _ => + suffixes(leading).withPrefix(s.substring(1)) + + def iterator: Iterator[(String, A)] = + (for v <- value.iterator yield ("", v)) ++ + (for (chr, m) <- suffixes.iterator + (s, v) <- m.iterator yield (chr +: s, v)) + + def addOne(kv: (String, A)): this.type = + withPrefix(kv._1).value = Some(kv._2) + this + + def subtractOne(s: String): this.type = + if s.isEmpty then { val prev = value; value = None; prev } + else suffixes.get(s(0)).flatMap(_.remove(s.substring(1))) + this + + // Overloading of transformation methods that should return a PrefixMap + def map[B](f: ((String, A)) => (String, B)): PrefixMap[B] = + strictOptimizedMap(PrefixMap.newBuilder, f) + def flatMap[B](f: ((String, A)) => IterableOnce[(String, B)]): PrefixMap[B] = + strictOptimizedFlatMap(PrefixMap.newBuilder, f) + + // Override `concat` and `empty` methods to refine their return type + override def concat[B >: A](suffix: IterableOnce[(String, B)]): PrefixMap[B] = + strictOptimizedConcat(suffix, PrefixMap.newBuilder) + override def empty: PrefixMap[A] = PrefixMap() + + // Members declared in scala.collection.mutable.Clearable + override def clear(): Unit = suffixes = immutable.Map.empty + // Members declared in scala.collection.IterableOps + override protected def fromSpecific(coll: IterableOnce[(String, A)]): PrefixMap[A] = PrefixMap.from(coll) + override protected def newSpecificBuilder: mutable.Builder[(String, A), PrefixMap[A]] = PrefixMap.newBuilder + + override def className = "PrefixMap" +end PrefixMap + +object PrefixMap: + def empty[A] = new PrefixMap[A] + + def from[A](source: IterableOnce[(String, A)]): PrefixMap[A] = + source match + case pm: PrefixMap[A @unchecked] => pm + case _ => (newBuilder ++= source).result() + + def apply[A](kvs: (String, A)*): PrefixMap[A] = from(kvs) + + def newBuilder[A]: mutable.Builder[(String, A), PrefixMap[A]] = + mutable.GrowableBuilder[(String, A), PrefixMap[A]](empty) + + import scala.language.implicitConversions + + implicit def toFactory[A](self: this.type): Factory[(String, A), PrefixMap[A]] = + new Factory[(String, A), PrefixMap[A]]: + def fromSpecific(it: IterableOnce[(String, A)]): PrefixMap[A] = self.from(it) + def newBuilder: mutable.Builder[(String, A), PrefixMap[A]] = self.newBuilder +end PrefixMap +~~~ +{% endtab %} +{% endtabs %} The previous listing shows the definition of `PrefixMap`. The map has keys of type `String` and the values are of parametric type `A`. It extends @@ -968,7 +1518,7 @@ However, in all these cases, to build the right kind of collection you need to start with an empty collection of that kind. This is provided by the `empty` method, which simply returns a fresh `PrefixMap`. -We'll now turn to the companion object `PrefixMap`. In fact it is not +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 an implicit @@ -980,15 +1530,23 @@ can not because a `Factory` fixes the type of collection elements, whereas `PrefixMap` has a polymorphic type of values). The two convenience methods are `empty` and `apply`. The same methods are -present for all other collections in Scala's collection framework so +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] = PrefixMap(hello -> 5, hi -> 2) +{% tabs prefixMap_4 %} +{% tab 'Scala 2 and 3' for=prefixMap_4 %} + +```scala +scala> PrefixMap("hello" -> 5, "hi" -> 2) +val res0: PrefixMap[Int] = PrefixMap(hello -> 5, hi -> 2) + +scala> res0 += "foo" -> 3 +val res1: res0.type = PrefixMap(hello -> 5, hi -> 2, foo -> 3) +``` - scala> res0 += "foo" -> 3 - res1: res0.type = PrefixMap(hello -> 5, hi -> 2, foo -> 3) +{% endtab %} +{% endtabs %} ## Summary ## diff --git a/_overviews/core/futures.md b/_overviews/core/futures.md index d12dd29fde..9f01a43710 100644 --- a/_overviews/core/futures.md +++ b/_overviews/core/futures.md @@ -14,7 +14,8 @@ permalink: /overviews/core/:title.html ## Introduction Futures provide a way to reason about performing many operations -in parallel-- in an efficient and non-blocking way. +in parallel -- in an efficient and non-blocking way. + A [`Future`](https://www.scala-lang.org/api/current/scala/concurrent/Future.html) 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. @@ -40,18 +41,34 @@ environment to resize itself if necessary to guarantee progress. A typical future looks like this: +{% tabs futures-00 %} +{% tab 'Scala 2 and 3' for=futures-00 %} val inverseFuture: Future[Matrix] = Future { fatMatrix.inverse() // non-blocking long lasting computation }(executionContext) +{% endtab %} +{% endtabs %} Or with the more idiomatic: +{% tabs futures-01 class=tabs-scala-version %} + +{% tab 'Scala 2' for=futures-01 %} implicit val ec: ExecutionContext = ... val inverseFuture : Future[Matrix] = Future { fatMatrix.inverse() } // ec is implicitly passed +{% endtab %} + +{% tab 'Scala 3' for=futures-01 %} + given ExecutionContext = ... + val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() + } // execution context is implicitly passed +{% endtab %} +{% endtabs %} Both code snippets delegate the execution of `fatMatrix.inverse()` to an `ExecutionContext` and embody the result of the computation in `inverseFuture`. @@ -80,11 +97,11 @@ only if each blocking call is wrapped inside a `blocking` call (more on that bel 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 number of available processors +By default, the `ExecutionContext.global` sets the parallelism level of its underlying fork-join pool to the number of available processors ([Runtime.availableProcessors](https://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.minThreads - defaults to `1` * 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` @@ -93,7 +110,10 @@ The parallelism level will be set to `numThreads` as long as it remains within ` As stated above the `ForkJoinPool` can increase the number 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 +{% tabs futures-02 class=tabs-scala-version %} + +{% tab 'Scala 2' for=futures-02 %} + import scala.concurrent.{ Future, ExecutionContext } import scala.concurrent.forkjoin._ // the following is equivalent to `implicit val ec = ExecutionContext.global` @@ -118,10 +138,40 @@ As explained in the `ForkJoinPool` API, this is only possible if the pool is exp } ) } +{% endtab %} +{% tab 'Scala 3' for=futures-02 %} + import scala.concurrent.{ Future, ExecutionContext } + import scala.concurrent.forkjoin.* + + // the following is equivalent to `given ExecutionContext = 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 + } + ) + } +{% endtab %} + +{% endtabs %} Fortunately the concurrent package provides a convenient way for doing so: +{% tabs blocking %} +{% tab 'Scala 2 and 3' for=blocking %} import scala.concurrent.Future import scala.concurrent.blocking @@ -131,26 +181,43 @@ Fortunately the concurrent package provides a convenient way for doing so: // ... } } +{% endtab %} +{% endtabs %} Note that `blocking` is a general construct that will be discussed more in depth [below](#blocking-inside-a-future). -Last but not least, you must remember that the `ForkJoinPool` is not designed for long lasting blocking operations. +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: +{% tabs futures-03 class=tabs-scala-version %} + +{% tab 'Scala 2' for=futures-03 %} implicit val ec = ExecutionContext.global - for( i <- 1 to 32000 ) { + for (i <- 1 to 32000) { Future { blocking { Thread.sleep(999999) } } } +{% endtab %} +{% tab 'Scala 3' for=futures-03 %} + given ExecutionContext = ExecutionContext.global + for i <- 1 to 32000 do + Future { + blocking { + Thread.sleep(999999) + } + } +{% endtab %} + +{% endtabs %} -If you need to wrap long lasting blocking operations we recommend using a dedicated `ExecutionContext`, for instance by wrapping a Java `Executor`. +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 @@ -158,26 +225,43 @@ If you need to wrap long lasting blocking operations we recommend using a dedica Using the `ExecutionContext.fromExecutor` method you can wrap a Java `Executor` into an `ExecutionContext`. For instance: +{% tabs executor class=tabs-scala-version %} + +{% tab 'Scala 2' for=executor %} ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) +{% endtab %} +{% tab 'Scala 3' for=executor %} + ExecutionContext.fromExecutor(ThreadPoolExecutor( /* your configuration */ )) +{% endtab %} + +{% endtabs %} ### Synchronous Execution Context One might be tempted to have an `ExecutionContext` that runs computations within the current thread: +{% tabs bad-example %} +{% tab 'Scala 2 and 3' for=bad-example %} val currentThreadExecutionContext = ExecutionContext.fromExecutor( new Executor { // Do not do this! - def execute(runnable: Runnable) { runnable.run() } + def execute(runnable: Runnable) = runnable.run() }) +{% endtab %} +{% endtabs %} This should be avoided as it introduces non-determinism in the execution of your future. +{% tabs bad-example-2 %} +{% tab 'Scala 2 and 3' for=bad-example-2 %} Future { doSomething }(ExecutionContext.global).map { doSomethingElse }(currentThreadExecutionContext) +{% endtab %} +{% endtabs %} 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](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback should not be both. @@ -200,7 +284,7 @@ Completion can take one of two forms: 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. +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 @@ -219,6 +303,9 @@ 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: +{% tabs futures-04 class=tabs-scala-version %} + +{% tab 'Scala 2' for=futures-04 %} import scala.concurrent._ import ExecutionContext.Implicits.global @@ -226,6 +313,17 @@ a request to obtain a list of friends of a particular user: val f: Future[List[Friend]] = Future { session.getFriends() } +{% endtab %} +{% tab 'Scala 3' for=futures-04 %} + import scala.concurrent.* + import ExecutionContext.Implicits.global + + val session = socialNetwork.createSessionFor("user", credentials) + val f: Future[List[Friend]] = Future { + session.getFriends() + } +{% endtab %} +{% endtabs %} Above, we first import the contents of the `scala.concurrent` package to make the type `Future` visible. @@ -238,8 +336,8 @@ 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 +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. @@ -251,10 +349,14 @@ 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: +{% tabs futures-04b %} +{% tab 'Scala 2 and 3' for=futures-04b %} val session = null val f: Future[List[Friend]] = Future { session.getFriends() } +{% endtab %} +{% endtabs %} The line `import ExecutionContext.Implicits.global` above imports the default global execution context. @@ -270,16 +372,20 @@ 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 +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. +{% tabs futures-04c %} +{% tab 'Scala 2 and 3' for=futures-04c %} val firstOccurrence: Future[Int] = Future { val source = scala.io.Source.fromFile("myText.txt") source.toSeq.indexOfSlice("myKeyword") } +{% endtab %} +{% endtabs %} ### Callbacks @@ -291,7 +397,7 @@ 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-- +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 @@ -323,32 +429,63 @@ 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: +a `List[String]` -- a list of recent textual posts: +{% tabs futures-05 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-05 %} import scala.util.{Success, Failure} val f: Future[List[String]] = Future { - session.getRecentPosts + session.getRecentPosts() } - f onComplete { + f.onComplete { case Success(posts) => for (post <- posts) println(post) case Failure(t) => println("An error has occurred: " + t.getMessage) } +{% endtab %} +{% tab 'Scala 3' for=futures-05 %} + import scala.util.{Success, Failure} + + val f: Future[List[String]] = Future { + session.getRecentPosts() + } + + f.onComplete { + case Success(posts) => for post <- posts do println(post) + case Failure(t) => println("An error has occurred: " + t.getMessage) + } +{% endtab %} +{% endtabs %} The `onComplete` method is general in the sense that it allows the client to handle the result of both failed and successful future computations. In the case where only successful results need to be handled, the `foreach` callback can be used: +{% tabs futures-06 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-06 %} val f: Future[List[String]] = Future { - session.getRecentPosts + session.getRecentPosts() } - f foreach { posts => - for (post <- posts) println(post) + for { + posts <- f + post <- posts + } println(post) +{% endtab %} +{% tab 'Scala 3' for=futures-06 %} + val f: Future[List[String]] = Future { + session.getRecentPosts() } + for + posts <- f + post <- posts + do println(post) +{% endtab %} +{% endtabs %} + `Future`s provide a clean way of handling only failed results using the `failed` projection which converts a `Failure[Throwable]` to a `Success[Throwable]`. An example of doing this is provided in the @@ -358,15 +495,19 @@ 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: +{% tabs futures-oncomplete %} +{% tab 'Scala 2 and 3' for=futures-oncomplete %} val firstOccurrence: Future[Int] = Future { val source = scala.io.Source.fromFile("myText.txt") source.toSeq.indexOfSlice("myKeyword") } - firstOccurrence onComplete { + firstOccurrence.onComplete { case Success(idx) => println("The keyword first appears at position: " + idx) case Failure(t) => println("Could not process file: " + t.getMessage) } +{% endtab %} +{% endtabs %} The `onComplete` and `foreach` methods both have result type `Unit`, which @@ -393,19 +534,23 @@ 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. +{% tabs volatile %} +{% tab 'Scala 2 and 3' for=volatile %} @volatile var totalA = 0 val text = Future { "na" * 16 + "BATMAN!!!" } - text foreach { txt => + text.foreach { txt => totalA += txt.count(_ == 'a') } - text foreach { txt => + text.foreach { txt => totalA += txt.count(_ == 'A') } +{% endtab %} +{% endtabs %} Above, the two callbacks may execute one after the other, in which case the variable `totalA` holds the expected value `18`. @@ -434,10 +579,10 @@ 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 +5. In the event that some callbacks throw an exception, the other callbacks are executed regardless. -6. In the event that some of the callbacks never complete (e.g. the +6. In the event that some 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). @@ -456,25 +601,42 @@ 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: +{% tabs futures-07 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-07 %} val rateQuote = Future { connection.getCurrentValue(USD) } - rateQuote foreach { quote => + for (quote <- rateQuote) { val purchase = Future { if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } - purchase foreach { amount => + for (amount <- purchase) println("Purchased " + amount + " USD") - } } +{% endtab %} +{% tab 'Scala 3' for=futures-07 %} + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + for quote <- rateQuote do + val purchase = Future { + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") + } + + for amount <- purchase do + println("Purchased " + amount + " USD") +{% endtab %} +{% endtabs %} 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 `foreach` callback and we are +completed, the computation proceeds in the `foreach` 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. @@ -489,7 +651,7 @@ some other currency. We would have to repeat this pattern within the 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 `foreach` +the code -- it can only be acted upon from within the `foreach` callback. This means that other parts of the application do not see the `purchase` future and cannot register another `foreach` callback to it, for example, to sell some other currency. @@ -504,18 +666,36 @@ about mapping collections. Let's rewrite the previous example using the `map` combinator: +{% tabs futures-08 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-08 %} val rateQuote = Future { connection.getCurrentValue(USD) } - val purchase = rateQuote map { quote => + val purchase = rateQuote.map { quote => if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } - purchase foreach { amount => + purchase.foreach { amount => + println("Purchased " + amount + " USD") + } +{% endtab %} +{% tab 'Scala 3' for=futures-08 %} + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote.map { quote => + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") + } + + purchase.foreach { amount => println("Purchased " + amount + " USD") } +{% endtab %} +{% endtabs %} By using `map` on `rateQuote` we have eliminated one `foreach` callback and, more importantly, the nesting. @@ -544,11 +724,13 @@ 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 +Let's 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: +{% tabs futures-09 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-09 %} val usdQuote = Future { connection.getCurrentValue(USD) } val chfQuote = Future { connection.getCurrentValue(CHF) } @@ -561,20 +743,40 @@ Here is an example of `flatMap` and `withFilter` usage within for-comprehensions purchase foreach { amount => println("Purchased " + amount + " CHF") } +{% endtab %} +{% tab 'Scala 3' for=futures-09 %} + 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.foreach { amount => + println("Purchased " + amount + " CHF") + } +{% endtab %} +{% endtabs %} The `purchase` future is completed only once both `usdQuote` -and `chfQuote` are completed-- it depends on the values +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 { +{% tabs for-translation %} +{% tab 'Scala 2 and 3' for=for-translation %} + val purchase = usdQuote.flatMap { usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) } +{% endtab %} +{% endtabs %} which is a bit harder to grasp than the for-comprehension, but we analyze it to better understand the `flatMap` operation. @@ -611,11 +813,15 @@ amount. The `connection.buy` method takes an `amount` to buy and the expected future to contain `0` instead of the exception, we use the `recover` combinator: - val purchase: Future[Int] = rateQuote map { +{% tabs recover %} +{% tab 'Scala 2 and 3' for=recover %} + val purchase: Future[Int] = rateQuote.map { quote => connection.buy(amount, quote) - } recover { + }.recover { case QuoteChangedException() => 0 } +{% endtab %} +{% endtabs %} The `recover` combinator creates a new future which holds the same result as the original future if it completed successfully. If it did @@ -640,20 +846,24 @@ 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: +{% tabs fallback-to %} +{% tab 'Scala 2 and 3' for=fallback-to %} val usdQuote = Future { connection.getCurrentValue(USD) - } map { + }.map { usd => "Value: " + usd + "$" } val chfQuote = Future { connection.getCurrentValue(CHF) - } map { + }.map { chf => "Value: " + chf + "CHF" } - val anyQuote = usdQuote fallbackTo chfQuote + val anyQuote = usdQuote.fallbackTo(chfQuote) - anyQuote foreach { println(_) } + anyQuote.foreach { println(_) } +{% endtab %} +{% endtabs %} The `andThen` combinator is used purely for side-effecting purposes. It returns a new future with exactly the same result as the current @@ -665,17 +875,34 @@ 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]() +{% tabs futures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-10 %} + val allPosts = mutable.Set[String]() - Future { - session.getRecentPosts - } andThen { - case Success(posts) => allPosts ++= posts - } andThen { - case _ => - clearAll() - for (post <- allPosts) render(post) - } + Future { + session.getRecentPosts() + }.andThen { + case Success(posts) => allPosts ++= posts + }.andThen { + case _ => + clearAll() + for (post <- allPosts) render(post) + } +{% endtab %} +{% tab 'Scala 3' for=futures-10 %} + val allPosts = mutable.Set[String]() + + Future { + session.getRecentPosts() + }.andThen { + case Success(posts) => allPosts ++= posts + }.andThen { + case _ => + clearAll() + for post <- allPosts do render(post) + } +{% endtab %} +{% endtabs %} In summary, the combinators on futures are purely functional. Every combinator returns a new future which is related to the @@ -691,10 +918,20 @@ futures also have projections. If the original future fails, the fails with a `NoSuchElementException`. The following is an example which prints the exception to the screen: +{% tabs futures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-11 %} val f = Future { 2 / 0 } for (exc <- f.failed) println(exc) +{% endtab %} +{% tab 'Scala 3' for=futures-11 %} + val f = Future { + 2 / 0 + } + for exc <- f.failed do println(exc) +{% endtab %} +{% endtabs %} The for-comprehension in this example is translated to: @@ -704,10 +941,20 @@ Because `f` is unsuccessful here, the closure is registered to the `foreach` callback on a newly-successful `Future[Throwable]`. The following example does not print anything to the screen: +{% tabs futures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-12 %} val g = Future { 4 / 2 } for (exc <- g.failed) println(exc) +{% endtab %} +{% tab 'Scala 3' for=futures-12 %} + val g = Future { + 4 / 2 + } + for exc <- g.failed do println(exc) +{% endtab %} +{% endtabs %} <!-- There is another projection called `timedout` which is specific to the @@ -750,19 +997,40 @@ As seen with the global `ExecutionContext`, it is possible to notify an `Executi 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: +{% tabs fixed-thread-pool %} +{% tab 'Scala 2 and 3' for=fixed-thread-pool %} ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x)) +{% endtab %} +{% endtabs %} will do nothing, as shown in the following: - implicit val ec = ExecutionContext.fromExecutor( - Executors.newFixedThreadPool(4)) +{% tabs futures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-13 %} + implicit val ec = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) + + Future { + blocking { blockingStuff() } + } +{% endtab %} +{% tab 'Scala 3' for=futures-13 %} + given ExecutionContext = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) + Future { blocking { blockingStuff() } } +{% endtab %} +{% endtabs %} Has the same effect as +{% tabs alternative %} +{% tab 'Scala 2 and 3' for=alternative %} Future { blockingStuff() } +{% endtab %} +{% endtabs %} The blocking code may also throw an exception. In this case, the exception is forwarded to the caller. @@ -776,28 +1044,50 @@ 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. +end of the application to make sure that all the futures have been completed. Here is an example of how to block on the result of a future: +{% tabs futures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-14 %} import scala.concurrent._ import scala.concurrent.duration._ - def main(args: Array[String]) { + object awaitPurchase { + def main(args: Array[String]): Unit = { + 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) + } + } +{% endtab %} +{% tab 'Scala 3' for=futures-14 %} + import scala.concurrent.* + import scala.concurrent.duration.* + + @main def awaitPurchase = val rateQuote = Future { connection.getCurrentValue(USD) } - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") + val purchase = rateQuote.map { quote => + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") } - Await.result(purchase, 0 nanos) - } + Await.result(purchase, 0.nanos) +{% endtab %} +{% endtabs %} 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` +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 @@ -806,7 +1096,7 @@ that method will not throw an exception if the future is failed. The `Future` trait implements the `Awaitable` trait with methods `ready()` and `result()`. These methods cannot be called directly -by the clients-- they can only be called by the execution context. +by the clients -- they can only be called by the execution context. @@ -816,8 +1106,8 @@ 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 `failed` projection method, which allows this `Throwable` to be -treated as the success value of another `Future`. The following special -exceptions are treated differently: +treated as the success value of another `Future`. +The following exceptions receive special treatment: 1. `scala.runtime.NonLocalReturnControl[_]` -- this exception holds a value associated with the return. Typically, `return` constructs in method @@ -832,11 +1122,225 @@ 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 +Fatal exceptions (as determined by `NonFatal`) are rethrown from 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`](https://www.scala-lang.org/api/current/scala/util/control/NonFatal$.html) -for a more precise description of the semantics. +for a more precise description of which exceptions are considered fatal. + +`ExecutionContext.global` handles fatal exceptions by printing a stack trace, by default. + +A fatal exception means that the `Future` associated with the computation will never complete. +That is, "fatal" means that the error is not recoverable for the `ExecutionContext` +and is also not intended to be handled by user code. By contrast, application code may +attempt recovery from a "failed" `Future`, which has completed but with an exception. + +An execution context can be customized with a reporter that handles fatal exceptions. +See the factory methods [`fromExecutor`](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutor(e:java.util.concurrent.Executor,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutor) +and [`fromExecutorService`](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutorService(e:java.util.concurrent.ExecutorService,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutorService). + +Since it is necessary to set the [`UncaughtExceptionHandler`](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.UncaughtExceptionHandler.html) +for executing threads, as a convenience, when passed a `null` executor, +`fromExecutor` will create a context that is configured the same as `global`, +but with the supplied reporter for handling exceptions. + +The following example demonstrates how to obtain an `ExecutionContext` with custom error handling +and also shows the result of different exceptions, as described above: + +{% tabs exceptions class=tabs-scala-version %} +{% tab 'Scala 2' for=exceptions %} +~~~ scala +import java.util.concurrent.{ForkJoinPool, TimeoutException} +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.concurrent.duration.DurationInt +import scala.util.{Failure, Success} + +object Test extends App { + def crashing(): Int = throw new NoSuchMethodError("test") + def failing(): Int = throw new NumberFormatException("test") + def interrupt(): Int = throw new InterruptedException("test") + def erroring(): Int = throw new AssertionError("test") + + // computations can fail in the middle of a chain of combinators, after the initial Future job has completed + def testCrashes()(implicit ec: ExecutionContext): Future[Int] = + Future.unit.map(_ => crashing()) + def testFails()(implicit ec: ExecutionContext): Future[Int] = + Future.unit.map(_ => failing()) + def testInterrupted()(implicit ec: ExecutionContext): Future[Int] = + Future.unit.map(_ => interrupt()) + def testError()(implicit ec: ExecutionContext): Future[Int] = + Future.unit.map(_ => erroring()) + + // Wait for 1 second for the the completion of the passed `future` value and print it + def check(future: Future[Int]): Unit = + try { + Await.ready(future, 1.second) + for (completion <- future.value) { + println(s"completed $completion") + // In case of failure, also print the cause of the exception, when defined + completion match { + case Failure(exception) if exception.getCause != null => + println(s" caused by ${exception.getCause}") + _ => () + } + } + } catch { + // If the future value did not complete within 1 second, the call + // to `Await.ready` throws a TimeoutException + case _: TimeoutException => println(s"did not complete") + } + + def reporter(t: Throwable) = println(s"reported $t") + + locally { + // using the `global` implicit context + import ExecutionContext.Implicits._ + // a successful Future + check(Future(42)) // completed Success(42) + // a Future that completes with an application exception + check(Future(failing())) // completed Failure(java.lang.NumberFormatException: test) + // same, but the exception is thrown somewhere in the chain of combinators + check(testFails()) // completed Failure(java.lang.NumberFormatException: test) + // a Future that does not complete because of a linkage error; + // the trace is printed to stderr by default + check(testCrashes()) // did not complete + // a Future that completes with an operational exception that is wrapped + check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception) + // caused by java.lang.InterruptedException: test + // a Future that completes due to a failed assert, which is bad for the app, + // but is handled the same as interruption + check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception) + // caused by java.lang.AssertionError: test + } + locally { + // same as `global`, but adds a custom reporter that will handle uncaught + // exceptions and errors reported to the context + implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(null, reporter) + check(testCrashes()) // reported java.lang.NoSuchMethodError: test + // did not complete + } + locally { + // does not handle uncaught exceptions; the executor would have to be + // configured separately + val executor = ForkJoinPool.commonPool() + implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(executor, reporter) + // the reporter is not invoked and the Future does not complete + check(testCrashes()) // did not complete + } + locally { + // sample minimal configuration for a context and underlying pool that + // use the reporter + val handler: Thread.UncaughtExceptionHandler = + (_: Thread, t: Throwable) => reporter(t) + val executor = new ForkJoinPool( + Runtime.getRuntime.availableProcessors, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, // threads use the pool's handler + handler, + /*asyncMode=*/ false + ) + implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(executor, reporter) + check(testCrashes()) // reported java.lang.NoSuchMethodError: test + // did not complete + } +} +~~~ +{% endtab %} + +{% tab 'Scala 3' for=exceptions %} +~~~ scala +import java.util.concurrent.{ForkJoinPool, TimeoutException} +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.concurrent.duration.DurationInt +import scala.util.{Failure, Success} + +def crashing(): Int = throw new NoSuchMethodError("test") +def failing(): Int = throw new NumberFormatException("test") +def interrupt(): Int = throw new InterruptedException("test") +def erroring(): Int = throw new AssertionError("test") + +// computations can fail in the middle of a chain of combinators, +// after the initial Future job has completed +def testCrashes()(using ExecutionContext): Future[Int] = + Future.unit.map(_ => crashing()) +def testFails()(using ExecutionContext): Future[Int] = + Future.unit.map(_ => failing()) +def testInterrupted()(using ExecutionContext): Future[Int] = + Future.unit.map(_ => interrupt()) +def testError()(using ExecutionContext): Future[Int] = + Future.unit.map(_ => erroring()) + +// Wait for 1 second for the the completion of the passed `future` value and print it +def check(future: Future[Int]): Unit = + try + Await.ready(future, 1.second) + for completion <- future.value do + println(s"completed $completion") + // In case of failure, also print the cause of the exception, when defined + completion match + case Failure(exception) if exception.getCause != null => + println(s" caused by ${exception.getCause}") + case _ => () + catch + // If the future value did not complete within 1 second, the call + // to `Await.ready` throws a TimeoutException + case _: TimeoutException => println(s"did not complete") + +def reporter(t: Throwable) = println(s"reported $t") + +@main def test(): Unit = + locally: + // using the `global` implicit context + import ExecutionContext.Implicits.given + // a successful Future + check(Future(42)) // completed Success(42) + // a Future that completes with an application exception + check(Future(failing())) // completed Failure(java.lang.NumberFormatException: test) + // same, but the exception is thrown somewhere in the chain of combinators + check(testFails()) // completed Failure(java.lang.NumberFormatException: test) + // a Future that does not complete because of a linkage error; + // the trace is printed to stderr by default + check(testCrashes()) // did not complete + // a Future that completes with an operational exception that is wrapped + check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception) + // caused by java.lang.InterruptedException: test + // a Future that completes due to a failed assert, which is bad for the app, + // but is handled the same as interruption + check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception) + // caused by java.lang.AssertionError: test + + locally: + // same as `global`, but adds a custom reporter that will handle uncaught + // exceptions and errors reported to the context + given ExecutionContext = ExecutionContext.fromExecutor(null, reporter) + check(testCrashes()) // reported java.lang.NoSuchMethodError: test + // did not complete + + locally: + // does not handle uncaught exceptions; the executor would have to be + // configured separately + val executor = ForkJoinPool.commonPool() + given ExecutionContext = ExecutionContext.fromExecutor(executor, reporter) + // the reporter is not invoked and the Future does not complete + check(testCrashes()) // did not complete + + locally: + // sample minimal configuration for a context and underlying pool that + // use the reporter + val handler: Thread.UncaughtExceptionHandler = + (_: Thread, t: Throwable) => reporter(t) + val executor = new ForkJoinPool( + Runtime.getRuntime.availableProcessors, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, // threads use the pool's handler + handler, + /*asyncMode=*/ false + ) + given ExecutionContext = ExecutionContext.fromExecutor(executor, reporter) + check(testCrashes()) // reported java.lang.NoSuchMethodError: test + // did not complete +end test +~~~ +{% endtab %} +{% endtabs %} ## Promises @@ -860,6 +1364,8 @@ 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. +{% tabs promises %} +{% tab 'Scala 2 and 3' for=promises %} import scala.concurrent.{ Future, Promise } import scala.concurrent.ExecutionContext.Implicits.global @@ -868,16 +1374,18 @@ that value. This passing of the value is done using a promise. val producer = Future { val r = produceSomething() - p success r + p.success(r) continueDoingSomethingUnrelated() } val consumer = Future { startDoingSomething() - f foreach { r => + f.foreach { r => doSomethingWithResult() } } +{% endtab %} +{% endtabs %} Here, we create a promise and use its `future` method to obtain the `Future` that it completes. Then, we begin two asynchronous @@ -895,18 +1403,35 @@ promise that has already been completed (or failed) will throw an The following example shows how to fail a promise. +{% tabs futures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-15 %} val p = Promise[T]() val f = p.future val producer = Future { val r = someComputation if (isInvalid(r)) - p failure (new IllegalStateException) + p.failure(new IllegalStateException) else { val q = doSomeMoreComputation(r) - p success q + p.success(q) } } +{% endtab %} +{% tab 'Scala 3' for=futures-15 %} + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = someComputation + if isInvalid(r) then + p.failure(new IllegalStateException) + else + val q = doSomeMoreComputation(r) + p.success(q) + } +{% endtab %} +{% endtabs %} Here, the `producer` computes an intermediate result `r`, and checks whether it's valid. In the case that it's invalid, it fails the @@ -916,7 +1441,7 @@ 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 +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 @@ -943,14 +1468,18 @@ 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`: +{% tabs promises-2 %} +{% tab 'Scala 2 and 3' for=promises-2 %} val f = Future { 1 } val p = Promise[Int]() - p completeWith f + p.completeWith(f) - p.future foreach { x => + p.future.foreach { x => println(x) } +{% endtab %} +{% endtabs %} When failing a promise with an exception, three subtypes of `Throwable`s are handled specially. If the `Throwable` used to break the promise is @@ -969,19 +1498,37 @@ two futures `f` and `g` and produces a third future which is completed by either Here is an example of how to do it: +{% tabs futures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-16 %} def first[T](f: Future[T], g: Future[T]): Future[T] = { val p = Promise[T] - f foreach { x => + f.foreach { x => p.trySuccess(x) } - g foreach { x => + g.foreach { x => p.trySuccess(x) } p.future } +{% endtab %} +{% tab 'Scala 3' for=futures-16 %} + def first[T](f: Future[T], g: Future[T]): Future[T] = + val p = Promise[T] + + f.foreach { x => + p.trySuccess(x) + } + + g.foreach { x => + p.trySuccess(x) + } + + p.future +{% endtab %} +{% endtabs %} 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). @@ -1026,9 +1573,11 @@ Abstract `Duration` contains methods that allow: 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. +Duration also provides `unapply` methods, so it can be used in pattern matching constructs. Examples: +{% tabs futures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-17 %} import scala.concurrent.duration._ import java.util.concurrent.TimeUnit._ @@ -1040,3 +1589,18 @@ Examples: // pattern matching val Duration(length, unit) = 5 millis +{% endtab %} +{% tab 'Scala 3' for=futures-17 %} + 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 +{% endtab %} +{% endtabs %} diff --git a/_overviews/core/implicit-classes.md b/_overviews/core/implicit-classes.md index 39694e9caa..ed141370c6 100644 --- a/_overviews/core/implicit-classes.md +++ b/_overviews/core/implicit-classes.md @@ -7,6 +7,12 @@ partof: implicit-classes languages: [zh-cn] permalink: /overviews/core/:title.html +versionSpecific: true +scala2: true +--- + +In Scala 3, implicit classes are still supported for compatibility reasons but the recommended way to achieve the same result is to use [extension methods]({% link _overviews/scala3-book/ca-extension-methods.md %}). + --- **Josh Suereth** @@ -59,7 +65,7 @@ value or conversion. Implicit classes have the following restrictions: -**1. They must be defined inside of another `trait`/`class`/`object`.** +**1. They must be defined inside another `trait`/`class`/`object`.** object Helpers { @@ -71,7 +77,7 @@ Implicit classes have the following restrictions: **2. They may only take one non-implicit argument in their constructor.** - implicit class RichDate(date: java.util.Date) // OK! + implicit class RichDate(date: java.time.LocalDate) // OK! implicit class Indexer[T](collection: Seq[T], index: Int) // BAD! implicit class Indexer[T](collection: Seq[T])(implicit index: Index) // OK! diff --git a/_overviews/core/nightlies.md b/_overviews/core/nightlies.md new file mode 100644 index 0000000000..8155ea2bfe --- /dev/null +++ b/_overviews/core/nightlies.md @@ -0,0 +1,87 @@ +--- +layout: singlepage-overview +title: Nightly Versions of Scala +permalink: /overviews/core/:title.html +--- + +We regularly publish nightly versions of both Scala 3 and 2 so that users can preview and test the contents of upcoming releases. + +Here's how to find and use these versions. + +## Scala 3 + +Scala 3 nightly versions are published to Maven Central. If you know the full version number of the nightly you want to use, you can use it just like any other Scala 3 version. + +One quick way to get that version number is to visit [https://dotty.epfl.ch](https://dotty.epfl.ch) and look in the upper left corner. + +Another way is to scrape Maven Central, as shown in this script: [https://raw.githubusercontent.com/VirtusLab/community-build3/master/scripts/lastVersionNightly.sc](https://raw.githubusercontent.com/VirtusLab/community-build3/master/scripts/lastVersionNightly.sc) + +A third way is to use [scala-cli](https://scala-cli.virtuslab.org), as follows. (Since Scala 3.5.0, the `scala` command runs `scala-cli`.) + +### scala-cli + +You can run nightlies with commands such as: + + scala-cli -S 3.nightly + scala-cli -S 3.3.nightly + +The default command is `repl`, but all the other scala-cli subcommands such as `compile` and `run` work, too. It also works with `//>` directives in your script itself, for example: + + //> using scala 3.nightly + +See this [scala-cli doc page](https://scala-cli.virtuslab.org/docs/commands/compile#scala-nightlies) for details. + +## Scala 2.13 or 2.12 + +We informally refer to Scala 2 “nightly” versions, but technically it's a misnomer. A so-called “nightly” is built for every merged PR. + +Scala 2 nightly versions are published to a special resolver. Unless you are using scala-cli, you'll need to add that resolver to your build configuration in order to use these versions. + +### quick version (sbt) + + Global / resolvers += "scala-integration" at + "https://scala-ci.typesafe.com/artifactory/scala-integration/" + scalaVersion := "2.13.15-bin-abcd123" + +For a 2.12 nightly, substitute e.g. `2.12.20` for `2.13.15`; in either case, it's the version number of the _next_ release on that branch. + +For `abcd123`, substitute the first 7 characters of the SHA of the latest commit to the [2.13.x branch](https://github.com/scala/scala/commits/2.13.x) or [2.12.x branch](https://github.com/scala/scala/commits/2.12.x) that has a green checkmark. (Clicking the checkmark will show a CI job name with the whole version in its name.) + +A quick way to find out the full version number of a current nightly is to use [scala-cli](https://scala-cli.virtuslab.org), as follows. + +### quick version (scala-cli) + +You can run nightlies with: + + scala-cli -S 2.13.nightly + scala-cli -S 2.nightly # same as 2.13.nightly + scala-cli -S 2.12.nightly + +The default command is `repl`, but all the other scala-cli subcommands such as `compile` and `run` work, too. It also works with `//>` directives in your script itself, for example: + + //> using scala 2.nightly + +### Longer explanation + +We no longer publish `-SNAPSHOT` versions of Scala 2. + +But the team does publish nightly versions, each with its own fixed version number. The version number of a nightly looks like e.g. `2.13.1-bin-abcd123`. (`-bin-` signals binary compatibility to sbt; all 2.13.x releases since 2.13.0 are binary compatible with each other.) + +To tell sbt to use one of these nightlies, you need to do three things. + +First, add the resolver where the nightlies are kept: + + Global / resolvers += "scala-integration" at + "https://scala-ci.typesafe.com/artifactory/scala-integration/" + +Second, specify the Scala version: + + scalaVersion := "2.13.1-bin-abcd123" + +But that isn't a real version number. Manually substitute a version number containing the 7-character SHA of the last commit in the [scala/scala repository](https://github.com/scala/scala) for which a nightly version was published. Look at [https://travis-ci.org/scala/scala/branches](https://travis-ci.org/scala/scala/branches) and you'll see the SHA in the upper right corner of the 2.13.x (or 2.12.x) section. + +As soon as 2.13.1 is released, the version number in the nightly will bump to 2.13.2, and so on. + +If you have a multiproject build, be sure you set these settings across all projects when you modify your build definition. Or, you may set them temporarily in the sbt shell with `++2.13.1-bin-abcd123` (sbt 0.13.x) or `++2.13.1-bin-abcd123!` (sbt 1.x; the added exclamation point is necessary to force a version not included in `crossScalaVersions` to be used). + +Ideally, we would suggest an automated way to ask Travis-CI for the right SHA. This is presumably possible via Travis-CI's API, but as far as we know, nobody has looked into it yet. (Is there a volunteer?) diff --git a/_overviews/core/string-interpolation.md b/_overviews/core/string-interpolation.md deleted file mode 100644 index fab22642b2..0000000000 --- a/_overviews/core/string-interpolation.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -layout: singlepage-overview -title: String Interpolation -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](https://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 `${}`. - -For some special characters, it is necessary to escape them when embedded within a string. -To represent an actual dollar sign you can double it `$$`, like here: - - println(s"New offers starting at $$14.99") - -which will print the string `New offers starting at $14.99`. - -Double quotes also need to be escaped. This can be done by using triple quotes as shown: - - val person = """{"name":"James"}""" - -which will produce the string `{"name":"James"}` when printed. - -### 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" - <console>: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](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/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](https://www.scala-lang.org/api/current/scala/StringContext.html). -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 StringBuilder(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 index f96ad46fba..fc501fc4c7 100644 --- a/_overviews/core/value-classes.md +++ b/_overviews/core/value-classes.md @@ -7,9 +7,11 @@ partof: value-classes languages: [ja, zh-cn] permalink: /overviews/core/:title.html +scala2: true +versionSpecific: true --- -**Mark Harrah** +In Scala 3, value classes are still supported for compatibility reasons but the recommended way to achieve the same result is to use [opaque types][opaques]. ## Introduction @@ -53,7 +55,7 @@ The following fragment of `RichInt` shows how it extends `Int` to allow the expr } 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. +(`RichInt$.MODULE$.toHexString$extension(3)`), rather than a method call on a newly instantiated object. ## Correctness @@ -64,7 +66,7 @@ For example, a fragment of a data type that represents a distance might look lik def +(m: Meter): Meter = new Meter(value + m.value) } -Code that adds two distances, such as +Code that adds two distances, such as: val x = new Meter(3.4) val y = new Meter(4.3) @@ -93,7 +95,7 @@ Whenever a value class is treated as another type, including a universal trait, As an example, consider the `Meter` value class: trait Distance extends Any - case class Meter(val value: Double) extends AnyVal with Distance + case class Meter(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: @@ -107,7 +109,7 @@ If the signature of `add` were instead: 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: +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)) @@ -122,12 +124,12 @@ The array here contains actual `Meter` instances and not just the underlying dou 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 + case class P(i: Int) extends AnyVal - val p = new P(3) + val p = P(3) p match { // new P instantiated here case P(3) => println("Matched 3") - case P(x) => println("Not 3") + case P(_) => println("Not 3") } ## Limitations @@ -141,15 +143,15 @@ A value class ... 1. ... must have only a primary constructor with exactly one public, val parameter whose type is not a user-defined 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 +3. ... may not have nested or local classes, traits, or objects. 4. ... may not define concrete `equals` or `hashCode` methods. -5. ... must be a top-level class or a member of a statically accessible object +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. +This section provides many concrete examples of the limitations already described in the previous section. Multiple constructor parameters are not allowed: @@ -163,7 +165,7 @@ and the Scala compiler will generate the following error message: 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 + NoByName.scala:1: error: `val` parameters may not be call-by-name class NoByName(val x: => Int) extends AnyVal ^ @@ -178,25 +180,29 @@ Multiple constructors are not allowed: 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: +A value class cannot have lazy vals, vars, or vals as members and cannot have nested classes, traits, or objects: class NoLazyMember(val evaluate: () => Double) extends AnyVal { val member: Int = 3 + var y: Int = 4 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: error: this statement is not allowed in value class: 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: error: this statement is not allowed in value class: var y: Int = 4 + var y: Int = 4 + ^ + Invalid.scala:4: error: this statement is not allowed in value class: lazy val 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:5: error: value class may not have nested module definitions object NestedObject ^ - Invalid.scala:5: error: value class may not have nested class definitions + Invalid.scala:6: error: value class may not have nested class definitions class NestedClass ^ @@ -209,6 +215,10 @@ Note that local classes, traits, and objects are not allowed either, as in the f } } + Local.scala:3: error: implementation restriction: nested class is not allowed in value class + class Local + ^ + A current implementation restriction is that value classes cannot be nested: class Outer(val inner: Inner) extends AnyVal @@ -264,3 +274,5 @@ but this is allowed because the enclosing object is top-level: object Outer { class Inner(val x: Int) extends AnyVal } + +[opaques]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_overviews/getting-started/install-scala.md b/_overviews/getting-started/install-scala.md new file mode 100644 index 0000000000..0154e3b246 --- /dev/null +++ b/_overviews/getting-started/install-scala.md @@ -0,0 +1,345 @@ +--- +layout: singlepage-overview +title: Getting Started +partof: getting-started +languages: [fr, ja, ru, uk] +includeTOC: true +newcomer_resources: + - title: Are You Coming From Java? + description: What you should know to get to speed with Scala after your initial setup. + icon: "fa fa-coffee" + link: /tutorials/scala-for-java-programmers.html + - title: Scala in the Browser + description: > + To start experimenting with Scala right away, use "Scastie" in your browser. + icon: "fa fa-cloud" + link: https://scastie.scala-lang.org/pEBYc5VMT02wAGaDrfLnyw + +redirect_from: + - /getting-started.html + - /scala3/getting-started.html # we deleted the scala 3 version of this page +--- + +The instructions below cover both Scala 2 and Scala 3. + +<div class="inline-sticky-top"> +{% altDetails need-help-info-box 'Need Help?' class=help-info %} +*If you are having trouble with setting up Scala, feel free to ask for help in the `#scala-users` channel of +[our Discord](https://discord.com/invite/scala).* +{% endaltDetails %} +</div> + +## Resources For Newcomers + +{% include inner-documentation-sections.html links=page.newcomer_resources %} + +## Install Scala on your computer + +Installing Scala means installing various command-line tools such as the Scala compiler and build tools. +We recommend using the Scala installer tool "Coursier" that automatically installs all the requirements, but you can still manually install each tool. + +### Using the Scala Installer (recommended way) + +The Scala installer is a tool named [Coursier](https://get-coursier.io/docs/cli-overview), whose main command is named `cs`. +It ensures that a JVM and standard Scala tools are installed on your system. +Install it on your system with the following instructions. + +<!-- Display tabs for each OS --> +{% tabs install-cs-setup-tabs class=platform-os-options %} + +<!-- macOS --> +{% tab macOS for=install-cs-setup-tabs %} +Run the following command in your terminal, following the on-screen instructions: +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-brew %} +{% altDetails cs-setup-macos-nobrew "Alternatively, if you don't use Homebrew:" %} + On the Apple Silicon (M1, M2, …) architecture: + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-arm64 %} + Otherwise, on the x86-64 architecture: + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-x86-64 %} +{% endaltDetails %} +{% endtab %} +<!-- end macOS --> + +<!-- Linux --> +{% tab Linux for=install-cs-setup-tabs %} + Run the following command in your terminal, following the on-screen instructions. + + On the x86-64 architecture: + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-x86-64 %} + Otherwise, on the ARM64 architecture: + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-arm64 %} +{% endtab %} +<!-- end Linux --> + +<!-- Windows --> +{% tab Windows for=install-cs-setup-tabs %} + Download and execute [the Scala installer for Windows]({{site.data.setup-scala.windows-link}}) + based on Coursier, and follow the on-screen instructions. +{% endtab %} +<!-- end Windows --> + +<!-- Other --> +{% tab Other for=install-cs-setup-tabs defaultTab %} + <noscript> + <p><span style="font-style:italic;">JavaScript is disabled, click the tab relevant for your OS.</span></p> + </noscript> + Follow the documentation from Coursier on + [how to install and run `cs setup`](https://get-coursier.io/docs/cli-installation). +{% endtab %} +<!-- end Other --> + +{% endtabs %} +<!-- End tabs --> + +> <i class="fa fa-info"></i>   You may need to restart your terminal, log out, +> or reboot in order for the changes to take effect. +{: .help-info} + +<!-- Alternative Detail - test the `scala` command --> +{% altDetails testing-your-setup 'Testing your setup' %} +Check your setup with the command `scala -version`, which should output: +```bash +$ scala -version +Scala code runner version: 1.4.3 +Scala version (default): {{site.scala-3-version}} +``` +{% endaltDetails %} +<!-- end Alternative Detail --> + + +Along with managing JVMs, `cs setup` also installs useful command-line tools: + +| Commands | Description | +|----------|-------------| +| `scalac` | the Scala compiler | +| `scala`, `scala-cli` | [Scala CLI](https://scala-cli.virtuslab.org), interactive toolkit for Scala | +| `sbt`, `sbtn` | The [sbt](https://www.scala-sbt.org/) build tool | +| `amm` | [Ammonite](https://ammonite.io/) is an enhanced REPL | +| `scalafmt` | [Scalafmt](https://scalameta.org/scalafmt/) is the Scala code formatter | + +For more information about `cs`, read +[coursier-cli documentation](https://get-coursier.io/docs/cli-overview). + +> `cs setup` installs the Scala 3 compiler and runner by default (the `scalac` and +> `scala` commands, respectively). Whether you intend to use Scala 2 or 3, +> this is usually not an issue because most projects use a build tool that will +> use the correct version of Scala irrespective of the one installed "globally". +> Nevertheless, you can always launch a specific version of Scala using +> ``` +> $ cs launch scala:{{ site.scala-version }} +> $ cs launch scalac:{{ site.scala-version }} +> ``` +> If you prefer Scala 2 to be run by default, you can force that version to be installed with: +> ``` +> $ cs install scala:{{ site.scala-version }} scalac:{{ site.scala-version }} +> ``` + +### ...or manually + +You only need two tools to compile, run, test, and package a Scala project: Java 8 or 11, +and Scala CLI. +To install them manually: + +1. if you don't have Java 8 or 11 installed, download + Java from [Oracle Java 8](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html), [Oracle Java 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), + or [AdoptOpenJDK 8/11](https://adoptopenjdk.net/). Refer to [JDK Compatibility](/overviews/jdk-compatibility/overview.html) for Scala/Java compatibility detail. +1. Install [Scala CLI](https://scala-cli.virtuslab.org/install) + +## Using the Scala CLI + +In a directory of your choice, which we will call `<project-dir>`, create a file named `hello.scala` with the following code: +```scala +//> using scala {{site.scala-3-version}} + +@main +def hello(): Unit = + println("Hello, World!") +``` + +You can define a method with the `def` keyword and mark it as a "main" method with the `@main` annotation, designating it as +the entry point in program execution. The method's type is `Unit`, which means it does not return a value. `Unit` +can be thought of as an analogue to the `void` keyword found in other languages. The `println` method will print the `"Hello, World!"` +string to standard output. + +To run the program, execute `scala run hello.scala` command from a terminal, within the `<project-dir>` directory. The file will be compiled and executed, with console output +similar to following: +``` +$ scala run hello.scala +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Hello, World! +``` + +### Handling command-line arguments + +Rewrite the `hello.scala` file so that the program greets the person running it. +```scala +//> using scala {{site.scala-3-version}} + +@main +def hello(name: String): Unit = + println(s"Hello, $name!") +``` + +The `name` argument is expected to be provided when executing the program, and if it's not found, the execution will fail. +The `println` method receives an interpolated string, as indicated by the `s` letter preceding its content. `$name` will be substituted by +the content of the `name` argument. + +To pass the arguments when executing the program, put them after `--`: +``` +$ scala run hello.scala -- Gabriel +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Hello, Gabriel! +``` + +You can read more about [main methods](/scala3/book/methods-main-methods.html) and [string interpolation](/scala3/book/string-interpolation.html) in the Scala Book. + +### Adding dependencies + +We now write a program that will count the files and directories present in its working directory. +We use the [os-lib](https://github.com/com-lihaoyi/os-lib) library from the [Scala toolkit](/toolkit/introduction.html) +for that purpose. A dependency on the library can be added with the `//> using` directive. Put the following code in `counter.scala`. +```scala +//> using scala {{site.scala-3-version}} +//> using dep "com.lihaoyi::os-lib:0.11.4" + +@main +def countFiles(): Unit = + val paths = os.list(os.pwd) + println(paths.length) +``` + +In the code above, `os.pwd` returns the current working directory. We pass it to `os.list`, which returns a sequence +of paths directly within the directory passed as an argument. We use a `val` to declare an immutable value, in this example storing the +sequence of paths. + +Execute the program. The dependency will be automatically downloaded. The execution should result in a similar output: +``` +$ scala run counter.scala +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +4 +``` +The printed number should be 4: `hello.scala`, `counter.scala` and two hidden directories created automatically when a program is executed: +`.bsp` containing information about project used by IDEs, and `.scala-build` containing the results of compilation. + +As it turns out, the `os-lib` library is a part of Scala Toolkit, a collection of libraries recommended for tasks like testing, +operating system interaction or handling JSONs. You can read more about the libraries included in the toolkit [here](/toolkit/introduction.html). +To include the toolkit libraries, use the `//> using toolkit 0.5.0` directive: +```scala +//> using scala {{site.scala-3-version}} +//> using toolkit 0.5.0 + +@main +def countFiles(): Unit = + val paths = os.list(os.pwd) + println(paths.length) +``` + +This program is identical to the one above. However, other toolkit libraries will also be available to use, should you need them. + +### Using the REPL + +You can execute code interactively using the REPL provided by the `scala` command. Execute `scala` in the console without any arguments. +``` +$ scala +Welcome to Scala {{site.scala-3-version}} (20-ea, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> +``` + +Write a line of code to be executed and press enter. +``` +scala> println("Hello, World!") +Hello, World! + +scala> +``` + +The result will be printed immediately after executing the line. You can declare values: +``` +scala> val i = 1 +val i: Int = 1 + +scala> +``` + +A new value of type `Int` has been created. If you provide an expression that can be evaluated, its result will be stored in an automatically created value. +``` +scala> i + 3 +val res0: Int = 4 + +scala> +``` +You can exit the REPL with `:exit`. + +## Using an IDE + +> You can read a short summary of Scala IDEs on [a dedicated page](/getting-started/scala-ides.html). + +Let's use an IDE to open the code we wrote above. The most popular ones are [IntelliJ](https://www.jetbrains.com/idea/) and +[VSCode](https://scalameta.org/metals/docs/editors/vscode). +They both offer rich IDE features, but you can still use [many other editors](https://scalameta.org/metals/docs/editors/overview.html). + +### Prepare the project + +First, remove all the using directives, and put them in a single file `project.scala` in the `<project-dir>` directory. +This makes it easier to import as a project in an IDE: + +```scala +//> using scala {{site.scala-3-version}} +//> using toolkit 0.5.0 +``` + +> Optionally, you can re-initialise the necessary IDE files from within the `<project-dir>` directory with the command `scala setup-ide .`, but these files will already exist if you have previously run the project with the Scala CLI `run` command. + +### Using IntelliJ + +1. Download and install [IntelliJ Community Edition](https://www.jetbrains.com/help/idea/installation-guide.html) +1. Install the Scala plugin by following [the instructions on how to install IntelliJ plugins](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) +1. Open the `<project-dir>` directory, which should be imported automatically as a BSP project. + +### Using VSCode with Metals + +1. Download [VSCode](https://code.visualstudio.com/Download) +1. Install the Metals extension from [the Marketplace](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) +1. Next, open the `<project-dir>` directory in VSCode. Metals should activate and begin importing the project automatically. + +### Play with the source code + +View these three files in your IDE: + +- _project.scala_ +- _hello.scala_ +- _counter.scala_ + +You should notice the benefits of an IDE, such as syntax highlighting, and smart code interactions. +For example you can place the cursor over any part of the code, such as `os.pwd` in _counter.scala_ and documentation for the method will appear. + +When you run your project in the next step, the configuration in _project.scala_ will be used to run the code in the other source files. + +### Run the code + +If you’re comfortable using your IDE, you can run the code in _counter.scala_ from your IDE. +Attached to the `countFiles` method should be a prompt button. Click it to run the method. This should run without issue. +The `hello` method in _hello.scala_ needs arguments however, so will require extra configuration via the IDE to provide the argument. + +Otherwise, you can run either application from the IDE's built-in terminal as described in above sections. + +## Next steps + +Now that you have tasted a little bit of Scala, you can further explore the language itself, consider checking out: + +* [The Scala Book](/scala3/book/introduction.html) (see the Scala 2 version [here](/overviews/scala-book/introduction.html)), which provides a set of short lessons introducing Scala’s main features. +* [The Tour of Scala](/tour/tour-of-scala.html) for bite-sized introductions to Scala's features. +* [Learning Courses](/online-courses.html), which includes online interactive tutorials and courses. +* [Our list of some popular Scala books](/books.html). + +There are also other tutorials for other build-tools you can use with Scala: +* [Getting Started with Scala and sbt](/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.html) +* [Using Scala and Maven](/tutorials/scala-with-maven.html) + +## Getting Help +There are a multitude of mailing lists and real-time chat rooms in case you want to quickly connect with other Scala users. Check out our [community](https://scala-lang.org/community/) page for a list of these resources, and for where to reach out for help. diff --git a/_getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md b/_overviews/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md similarity index 96% rename from _getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md rename to _overviews/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md index 8d625bad57..6dc397f089 100644 --- a/_getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md +++ b/_overviews/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md @@ -2,7 +2,7 @@ title: Building a Scala Project with IntelliJ and sbt layout: singlepage-overview partof: building-a-scala-project-with-intellij-and-sbt -languages: [ja] +languages: [ja, ru, uk] disqus: true previous-page: getting-started/intellij-track/getting-started-with-scala-in-intellij next-page: testing-scala-in-intellij-with-scalatest @@ -60,10 +60,9 @@ but here's a glance at what everything is for: 1. Change the code in the class to the following: ``` -object Main extends App { +@main def run() = val ages = Seq(42, 75, 29, 64) println(s"The oldest person is ${ages.max}") -} ``` Note: IntelliJ has its own implementation of the Scala compiler, and sometimes your @@ -105,7 +104,7 @@ Continue to the next tutorial in the _getting started with IntelliJ_ series, and **or** -* [The Scala Book](/overviews/scala-book/introduction.html), which provides a set of short lessons introducing Scala’s main features. +* [The Scala Book](/scala3/book/introduction.html), which provides a set of short lessons introducing Scala’s main features. * [The Tour of Scala](/tour/tour-of-scala.html) for bite-sized introductions to Scala's features. - Continue learning Scala interactively online on [Scala Exercises](https://www.scala-exercises.org/scala_tutorial). diff --git a/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md b/_overviews/getting-started/intellij-track/getting-started-with-scala-in-intellij.md similarity index 65% rename from _getting-started/intellij-track/getting-started-with-scala-in-intellij.md rename to _overviews/getting-started/intellij-track/getting-started-with-scala-in-intellij.md index 6bab3d3d14..8bbd163a00 100644 --- a/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md +++ b/_overviews/getting-started/intellij-track/getting-started-with-scala-in-intellij.md @@ -2,7 +2,7 @@ title: Getting Started with Scala in IntelliJ layout: singlepage-overview partof: getting-started-with-scala-in-intellij -languages: [ja] +languages: [ja, ru, uk] disqus: true next-page: building-a-scala-project-with-intellij-and-sbt @@ -13,28 +13,28 @@ 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](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) -1. Next, download and install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Make sure you have the Java 8 JDK (also known as 1.8) or newer: + * run `javac -version` on the command line to check the Java version, + * if you don't have version 1.8 or higher, [install the JDK](https://www.oracle.com/java/technologies/downloads/). +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.) +[how to install IntelliJ plugins](https://www.jetbrains.com/help/idea/managing-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 IDEA. -1. Name the project **HelloWorld** +1. Open up IntelliJ and click **File** => **New** => **Project**. +1. Name the project **HelloWorld**. +1. Select **Scala** from the **Language** list. +1. Select **IntelliJ** from the **Build system** list. 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**. +1. Once the SDK is created, and you're back to the "New Project" window, click **Create**. ## Writing code @@ -42,7 +42,11 @@ take a few minutes but subsequent projects can use the same SDK. 1. On the **Project** pane on the left, right-click `src` and select **New** => **Scala class**. If you don't see **Scala class**, right-click on **HelloWorld** and click on **Add Framework Support...**, select **Scala** and proceed. If you see **Error: library is not specified**, you can either click download button, or select the library path manually. If you only see **Scala Worksheet** try expanding the `src` folder and its `main` subfolder, and right-click on the `scala` folder. 1. Name the class `Hello` and change the **Kind** to `object`. -1. Change the code in the class to the following: +1. Change the code in the file to the following: + +{% tabs hello-world-entry-point class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-entry-point %} ``` object Hello extends App { @@ -50,10 +54,42 @@ object Hello extends App { } ``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-entry-point %} + +``` +@main def hello(): Unit = + println("Hello, World!") +``` + +In Scala 3, you can remove the object `Hello` and define a top-level method +`hello` instead, which you annotate with `@main`. + +{% endtab %} + +{% endtabs %} + ## Running it + +{% tabs hello-world-run class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-run %} + * Right click on `Hello` in your code and select **Run 'Hello'**. * You're done! +{% endtab %} + +{% tab 'Scala 3' for=hello-world-run %} + +* Right click on `hello` in your code and select **Run 'hello'**. +* You're done! + +{% endtab %} + +{% endtabs %} + ## Experimenting with Scala A good way to try out code samples is with Scala Worksheets @@ -62,14 +98,18 @@ A good way to try out code samples is with Scala Worksheets 2. Name your new Scala worksheet "Mathematician". 3. Enter the following code into the worksheet: +{% tabs square %} +{% tab 'Scala 2 and 3' for=square %} ``` -def square(x: Int) = x * x +def square(x: Int): Int = x * x square(2) ``` +{% endtab %} +{% endtabs %} As you change your code, you'll notice that it gets evaluated -in the right pane. If you do not see a right pane, right click on your Scala worksheet in the Project pane, and click on Evaluate Worksheet. +in the right pane. If you do not see a right pane, right-click on your Scala worksheet in the Project pane, and click on Evaluate Worksheet. ## Next Steps diff --git a/_getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md b/_overviews/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md similarity index 83% rename from _getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md rename to _overviews/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md index 77d0b3341a..8a51eca2e0 100644 --- a/_getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md +++ b/_overviews/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md @@ -2,7 +2,7 @@ title: Testing Scala in IntelliJ with ScalaTest layout: singlepage-overview partof: testing-scala-in-intellij-with-scalatest -languages: [ja] +languages: [ja, ru, uk] disqus: true previous-page: building-a-scala-project-with-intellij-and-sbt @@ -20,37 +20,34 @@ This assumes you know [how to build a project in IntelliJ](building-a-scala-proj 1. Add the ScalaTest dependency: 1. Add the ScalaTest dependency to your `build.sbt` file: ``` - libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test ``` 1. If you get a notification "build.sbt was changed", select **auto-import**. 1. These two actions will cause `sbt` to download the ScalaTest library. - 1. Wait for the `sbt` sync to finish; otherwise, `FunSuite` and `test()` will be + 1. Wait for the `sbt` sync to finish; otherwise, `AnyFunSuite` and `test()` will be unrecognized. 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. Call it `CubeCalculator`, change the **Kind** to `object`, and hit enter or double-click on `object`. 1. Replace the code with the following: ``` - object CubeCalculator extends App { - def cube(x: Int) = { + object CubeCalculator: + 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. Name the class `CubeCalculatorTest` and hit enter or double-click on `class`. 1. Replace the code with the following: ``` - import org.scalatest.FunSuite + import org.scalatest.funsuite.AnyFunSuite - class CubeCalculatorTest extends FunSuite { + class CubeCalculatorTest extends AnyFunSuite: test("CubeCalculator.cube") { assert(CubeCalculator.cube(3) === 27) } - } ``` 1. In the source code, right-click `CubeCalculatorTest` and select **Run 'CubeCalculatorTest'**. @@ -60,9 +57,9 @@ This assumes you know [how to build a project in IntelliJ](building-a-scala-proj 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 +* `extends AnyFunSuite` lets us use functionality of ScalaTest's AnyFunSuite class such as the `test` function -* `test` is function that comes from the FunSuite library that collects +* `test` is a 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". diff --git a/_getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md b/_overviews/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md similarity index 78% rename from _getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md rename to _overviews/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md index 5c7bc37325..11c90825ea 100644 --- a/_getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md +++ b/_overviews/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md @@ -2,7 +2,7 @@ title: Getting Started with Scala and sbt on the Command Line layout: singlepage-overview partof: getting-started-with-scala-and-sbt-on-the-command-line -languages: [ja] +languages: [ja, ru, uk] disqus: true next-page: testing-scala-with-sbt-on-the-command-line @@ -26,6 +26,10 @@ We assume you know how to use a terminal. * [Linux](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Linux.html) ## Create the project + +{% tabs sbt-welcome-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=sbt-welcome-1 %} + 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. @@ -34,6 +38,21 @@ It will also create a `target` folder, which you can ignore. create a project called "hello-world". 1. Let's take a look at what just got generated: +{% endtab %} +{% tab 'Scala 3' for=sbt-welcome-1 %} + +1. `cd` to an empty folder. +1. Run the following command `sbt new scala/scala3.g8`. +This pulls the 'scala3' 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: + +{% endtab %} +{% endtabs %} + + ``` - hello-world - project (sbt uses this to install and manage plugins and dependencies) @@ -69,18 +88,22 @@ extra functionality to our apps. 1. Open up `build.sbt` and add the following line: ``` -libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1" ``` Here, `libraryDependencies` is a set of dependencies, and by using `+=`, we're adding the [scala-parser-combinators](https://github.com/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. +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. +> **Note for Java Libraries:** For a regular Java library, you should only use one percent (`%`) between the +> organization name and artifact name. Double percent (`%%`) is a specialisation for Scala libraries. +> You can learn more about the reason for this in the [sbt documentation][sbt-docs-lib-dependencies]. + ## 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). @@ -90,3 +113,5 @@ Continue to the next tutorial in the _getting started with sbt_ series, and lear - 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). + +[sbt-docs-lib-dependencies]: https://www.scala-sbt.org/1.x/docs/Library-Dependencies.html#Getting+the+right+Scala+version+with diff --git a/_getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md b/_overviews/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md similarity index 99% rename from _getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md rename to _overviews/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md index accc081592..9a446b1c76 100644 --- a/_getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md +++ b/_overviews/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md @@ -2,7 +2,7 @@ title: Testing Scala with sbt and ScalaTest on the Command Line layout: singlepage-overview partof: testing-scala-with-sbt-on-the-command-line -languages: [ja] +languages: [ja, ru, uk] disqus: true previous-page: getting-started-with-scala-and-sbt-on-the-command-line diff --git a/_overviews/getting-started/scala-ides.md b/_overviews/getting-started/scala-ides.md new file mode 100644 index 0000000000..9f210d4b1e --- /dev/null +++ b/_overviews/getting-started/scala-ides.md @@ -0,0 +1,55 @@ +--- +layout: singlepage-overview +title: Scala IDEs + +partof: scala-ides + +permalink: /getting-started/:title.html + +keywords: +- Scala +- IDE +- JetBrains +- IntelliJ +- VSCode +- Metals +--- + +It's of course possible to write Scala code in any editor and compile and run the code from the command line. But most developers prefer to use an IDE (Integrated Development Environment), especially for coding anything beyond simple exercises. + +The following IDEs are available for Scala: + +## IntelliJ IDEA + Scala plugin + +[https://jetbrains.com/scala](https://jetbrains.com/scala) + +![](../../resources/images/getting-started/IntelliJScala.png) + +IntelliJ IDEA is a cross-platform IDE developed by JetBrains that provides a consistent experience for a wide range of programming languages and technologies. It also supports Scala through the IntelliJ Scala Plugin, which is being developed at JetBrains. First, install IntelliJ IDEA Community Edition (unless you don't already use the Ultimate edition) and then add the IntelliJ Scala Plugin. + +IntelliJ IDEA and Scala Plugin will assist you in virtually every part of a Scala software developer's work. Use it if you like a solid integrated experience, sane default settings, and tested solutions. + +For more information, check out our tutorial [Getting Started with Scala in IntelliJ](/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.html) + +## Visual Studio Code + Metals + +[https://scalameta.org/metals](https://scalameta.org/metals) + +![](../../resources/images/getting-started/VSCodeMetals.png) + +Visual Studio Code, commonly called VS Code, is a source code editor from Microsoft. To add Scala support, you install an extension called Metals. + +(Why "Metals"? Because the underlying technologies are Scalameta and LSP ([Language Server Protocol](https://microsoft.github.io/language-server-protocol/)), and "Meta" + "LS" equals "Metals".) + +In contrast to IntelliJ IDEA + Scala Plugin, VS Code + Metals is aimed at people who like to get feedback and code intelligence straight from the compiler, which enables them to also try out experimental Scala features. + +## Your favorite editor + Metals + +Metals is most commonly used with VS Code, but it's also available for the following popular editors: + +* Emacs +* Vim +* Sublime Text +* Helix + +as documented [here](https://scalameta.org/metals/docs/#editor-support). diff --git a/_overviews/index.md b/_overviews/index.md index 32a2fcbb7f..53ad207975 100644 --- a/_overviews/index.md +++ b/_overviews/index.md @@ -2,8 +2,11 @@ layout: overviews partof: overviews title: Guides and Overviews -languages: [ja, zh-cn, ru] +languages: [ja, zh-cn, ru, uk] permalink: /overviews/:title.html +redirect_from: + - /scala3/guides.html + - /guides.html --- <!-- Auto generated from _data/overviews.yml --> diff --git a/_overviews/jdk-compatibility/overview.md b/_overviews/jdk-compatibility/overview.md index c00fd2f05f..c42ee96090 100644 --- a/_overviews/jdk-compatibility/overview.md +++ b/_overviews/jdk-compatibility/overview.md @@ -4,43 +4,67 @@ title: JDK Compatibility permalink: /overviews/jdk-compatibility/overview.html --- -Scala's primary platform is the Java Virtual Machine (JVM). (Other supported platforms: [Scala.js](https://www.scala-js.org/), [Scala Native](https://scala-native.readthedocs.io/).) +Scala's primary platform is the Java Virtual Machine (JVM). (Other supported platforms: [Scala.js](https://www.scala-js.org/), [Scala Native](https://scala-native.org/).) Sometimes new JVM and JDK (Java Development Kit) versions require us to update Scala to remain compatible. -## Version compatibility table +## Scala compatibility table -| JDK version | Minimum Scala versions | Recommended Scala versions | -|:-----------:|:---------------------------------|:-----------------------------------------------------------| -| 18 | 2.13.7, 2.12.15 | 2.13.8, 2.12.15 | -| 17 | 2.13.6, 2.12.15 | 2.13.8, 2.12.15 | -| 11 | 2.13.0, 2.12.4, 2.11.12 | 2.13.8, 2.12.15, 2.11.12 | -| 8 | 2.13.0, 2.12.0, 2.11.0, 2.10.2 | 2.13.8, 2.12.15, 2.11.12, 2.10.7 | -| 6, 7 | 2.11.0, 2.10.0 | 2.11.12, 2.10.7 | +Minimum Scala versions: -Even when a version combination isn't listed as supported, most features may still work. (But Scala 2.12+ definitely doesn't work at all on JDK 6 or 7.) +| JDK | 3 | 3 LTS | 2.13 | 2.12 | 2.11 | +|:-----------:|:--------:|:--------:|:---------:|:---------:|:----------:| +| 25 (ea) | 3.7.1 | 3.3.6 | 2.13.17* | 2.12.21* | | +| 24 | 3.6.4 | 3.3.6 | 2.13.16 | 2.12.21* | | +| 23 | 3.6.2 | 3.3.5 | 2.13.15 | 2.12.20 | | +| 22 | 3.4.0 | 3.3.4 | 2.13.13 | 2.12.19 | | +| 21 (LTS) | 3.4.0 | 3.3.1 | 2.13.11 | 2.12.18 | | +| 17 (LTS) | 3.0.0 | 3.3.0 | 2.13.6 | 2.12.15 | | +| 11 (LTS) | 3.0.0 | 3.3.0 | 2.13.0 | 2.12.4 | 2.11.12 | +| 8 (LTS) | 3.0.0 | 3.3.0 | 2.13.0 | 2.12.0 | 2.11.0 | -In general, Scala works on JDK 11+, including GraalVM, but it probably won't take special advantage of features that were added after JDK 8. See [below](#jdk-11-compatibility-notes). +\* = forthcoming; support available in [nightly builds](https://stackoverflow.com/q/40622878/86485) -Lightbend offers [commercial support](https://www.lightbend.com/lightbend-platform-subscription) for Scala 2. The linked page includes contact information for inquiring about supported and recommended versions. +Even when a version combination isn't listed as supported, most features might still work. + +Using the latest patch version of your chosen Scala version line is always recommended. + +Akka offers [commercial support](https://akka.io/pricing) for Scala 2. The linked page includes contact information for inquiring about supported and recommended versions. + +## Tooling compatibility table + +Minimum working versions: + +| JDK | scala-cli | sbt | mill | +|:-----------:|:----------:|:---------:|:-----------| +| 23 | 1.4.1 | 1.9.0 | 0.11.8 | +| 21 (LTS) | 1.0.0 | 1.9.0 | 0.11.5 | +| 17 (LTS) | 1.0.0 | 1.6.0 | 0.7.0 | +| 11 (LTS) | 1.0.0 | 1.1.0 | 0.1.5 | +| 8 (LTS) | 1.0.0 | 1.0.0 | 0.1.0 | + +Even when a version combination isn't listed as supported, most features might still work. + +Using a different build tool, such as Gradle or Maven? We invite pull +requests adding additional columns to this table. ## Running versus compiling -JDK 8, 11, and 17 are all reasonable choices both for *compiling* and *running* Scala code. +JDK 8, 11, 17, and 21 are all reasonable choices both for *compiling* and *running* Scala code. Since the JVM is normally backwards compatible, it is usually safe to use a newer JVM for *running* your code than the one it was compiled on, especially if you are not using JVM features designated "experimental" or "unsafe". -JDK 8 remains in use at many shops (as of early 2022), but usage is declining and some projects are dropping support. If you compile on JDK 11+ but want to allow your users to stay on 8, additional care is needed to avoid using APIs and features that don't exist in 8. (For this reason, some Scala developers use JDK 11 or 17 for their daily work but do release builds on JDK 8.) +JDK 8 remains in use at some shops (as of 2023), but usage is declining and some projects are dropping support. If you compile on JDK 11+ but want to allow your users to stay on 8, additional care is needed to avoid using APIs and features that don't exist in 8. (For this reason, some Scala developers use a newer JDK for their daily work but do release builds on JDK 8.) ## Long Term Support (LTS) versions After Java 8, Oracle introduced the concept of LTS versions of the JDK. These versions will remain supported (by Oracle, and likely by the rest of the ecosystem, including Scala) for longer than the versions in between. See <https://www.oracle.com/technetwork/java/eol-135779.html>. -JDK 8, 11, and 17 are LTS versions. +JDK 8, 11, 17, and 21 are LTS versions. (The next LTS version will be 25.) -Scala provides experimental support for running the Scala compiler on non-LTS versions of the JDK. The current LTS versions are normally tested in our CI matrix and by the Scala community build. We may also test non-LTS versions, but any issues found there are considered lower priority, and will not be considered release blockers. (Lightbend may be able to offer faster resolution of issues like this under commercial support.) +Scala provides experimental support for running the Scala compiler on non-LTS versions of the JDK. The current LTS versions are normally tested in our CI matrix and by the Scala community build. We may also test non-LTS versions, but any issues found there are considered lower priority, and will not be considered release blockers. (The Scala team at Akka may be able to offer faster resolution of issues like this under commercial support.) -As already mentioned, Scala code compiled on JDK 8 should run without problems in later JVMs. We will give higher priority to bugs that break this property. (For example, later in the 2.13.x series we hope to provide support for JPMS module access checks, to ensure your code won't incur `LinkageErrors` due to module access violations.) +As already mentioned, Scala code compiled on JDK 8 should run without problems in later JVMs. We will give higher priority to bugs that break this property. (For example, in 2.13.x we might eventually provide support for JPMS module access checks, to ensure your code won't incur `LinkageErrors` due to module access violations.) ## JDK vendors and distributions @@ -56,9 +80,9 @@ OpenJDK comes in various flavors, offered by different providers. We build and The Scala test suite and Scala community build are green on JDK 11. -The Scala compiler does not enforce the restrictions of the Java Platform Module System, which means that code that typechecks may incur linkage errors at runtime. Scala 2.13.x will eventually provide [rudimentary support](https://github.com/scala/scala/pull/7218) for this (perhaps only in nightlies built on JDK 11). +In general, Scala works on JDK 11+, including GraalVM, but may not take special advantage of features that were added after JDK 8. -For sbt users, JDK 11 support requires minimum sbt version 1.1.0. sbt 1.3.9 or newer is recommended. (If you are still on the 0.13.x series, use 0.13.18.) +For example, the Scala compiler does not enforce the restrictions of the Java Platform Module System, which means that code that typechecks may incur linkage errors at runtime. Scala 2.13.x will eventually provide [rudimentary support](https://github.com/scala/scala/pull/7218) for this (perhaps only in nightlies built on JDK 11). To track progress on JDK 11 related issues in Scala, watch: @@ -69,7 +93,7 @@ To track progress on JDK 11 related issues in Scala, watch: JDK 17 is an LTS release. -Scala 2.13.6 and 2.12.15 support JDK 17. +Scala 2.13.6+ and 2.12.15+ support JDK 17. The Scala test suite and Scala community build are green on JDK 17. @@ -77,16 +101,49 @@ For sbt users, sbt 1.6.0-RC1 is the first version to support JDK 17, but in prac For possible Scala issues, see the [jdk11](https://github.com/scala/bug/labels/jdk11) and [jdk17](https://github.com/scala/bug/labels/jdk17) labels in the Scala 2 bug tracker. -## JDK 18 compatibility notes +## JDK 21 compatibility notes + +JDK 21 is an LTS release. + +Scala 3.3.1+, 2.13.11+, and 2.12.18+ support JDK 21. + +The Scala test suite and Scala 2.13 community build are green on JDK 21. + +For sbt users, sbt 1.9.0 is the first version to support JDK 21. + +For possible Scala issues, see the [jdk11](https://github.com/scala/bug/labels/jdk11), [jdk17](https://github.com/scala/bug/labels/jdk17), and [jdk21](https://github.com/scala/bug/labels/jdk21) labels in the Scala 2 bug tracker. + +## JDK 22 compatibility notes + +JDK 22 is non-LTS. + +Scala 2.13.13+, 2.12.19+, 3.3.4+, and 3.6.2+ support JDK 22. + +For possible Scala 2 issues, see the [jdk11](https://github.com/scala/bug/labels/jdk11), [jdk17](https://github.com/scala/bug/labels/jdk17), and [jdk21](https://github.com/scala/bug/labels/jdk21) labels in the Scala 2 bug tracker. + +## JDK 23 compatibility notes + +JDK 23 is non-LTS. + +Scala 2.13.15+, Scala 2.12.20+, and Scala 3.6.2+ support JDK 23. + +We are working on adding JDK 23 support to Scala 3.3.x. +(Support may be available in nightly builds and/or release candidates.) + +For possible Scala 2 issues, see the [jdk11](https://github.com/scala/bug/labels/jdk11), [jdk17](https://github.com/scala/bug/labels/jdk17), and [jdk21](https://github.com/scala/bug/labels/jdk21) labels in the Scala 2 bug tracker. + +## JDK 24 compatibility notes + +JDK 24 will be non-LTS. -Early access builds of JDK 18, a non-LTS release, are already available. +Scala 2.13.16+ supports, and Scala 2.12.21 (forthcoming) will support, JDK 24. We are also working on adding JDK 24 support to Scala 3. (Support may be available in nightly builds and/or release candidates.) -Initial support for JDK 18 was included in Scala 2.13.7 and 2.12.15. +For possible Scala 2 issues, see the [jdk11](https://github.com/scala/bug/labels/jdk11), [jdk17](https://github.com/scala/bug/labels/jdk17), and [jdk21](https://github.com/scala/bug/labels/jdk21) labels in the Scala 2 bug tracker. ## GraalVM Native Image compatibility notes -There are several records of successfully using Scala with [GraalVM](https://www.graalvm.org) Native Image(i.e.: ahead of time compiler) to produce directly executable binaries. -Beware that, even using solely the Scala standard library, Native Image compilation have some heavy requirements in terms of [reflective access](https://www.graalvm.org/reference-manual/native-image/Reflection/), and it very likely require additional configuration steps to be performed. +There are several records of successfully using Scala with [GraalVM](https://www.graalvm.org) Native Image (i.e., ahead of time compiler) to produce directly executable binaries. +Beware that, even using solely the Scala standard library, Native Image compilation have some heavy requirements in terms of [reflective access](https://www.graalvm.org/reference-manual/native-image/metadata/), and it very likely require additional configuration steps to be performed. A few sbt plugins are offering support for GraalVM Native Image compilation: @@ -95,6 +152,8 @@ A few sbt plugins are offering support for GraalVM Native Image compilation: ## Scala 3 ->The Scala 3.x series supports JDK 8, as well as 11 and beyond. +At present, both Scala 3 LTS and Scala Next support JDK 8, as well as 11 and beyond. -As Scala and the JVM continue to evolve, some eventual Scala version may drop support for JDK 8, in order to better take advantage of new JVM features. It isn't clear yet what the new minimum supported version might become. +As per [this blog post](https://www.scala-lang.org/news/next-scala-lts.html), +a forthcoming Scala 3 LTS version will drop JDK 8 support and may drop +11 as well. Stay tuned. diff --git a/_overviews/macros/annotations.md b/_overviews/macros/annotations.md index 103f65dc90..7300704010 100644 --- a/_overviews/macros/annotations.md +++ b/_overviews/macros/annotations.md @@ -57,8 +57,8 @@ results have to be wrapped in a `Block` for the lack of better notion in the ref 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 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 @@ -109,8 +109,8 @@ at a later point in the future). 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). +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 lookup in the enclosing context). ## Blackbox vs whitebox diff --git a/_overviews/macros/blackbox-whitebox.md b/_overviews/macros/blackbox-whitebox.md index 07c13f2aa2..d29cd6b16d 100644 --- a/_overviews/macros/blackbox-whitebox.md +++ b/_overviews/macros/blackbox-whitebox.md @@ -19,7 +19,7 @@ Separation of macros into blackbox ones and whitebox ones is a feature of Scala 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. +In fact, macros became an important part of our ecosystem so quickly that just a couple of 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. <span class="label success">UPDATE</span> 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](https://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. @@ -30,13 +30,13 @@ comprehensibility. ## 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"](https://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) article at Stack Overflow. +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 macro. In Scala 2.10, such expansion will retain its precise type as highlighted in the ["Static return type of Scala macros"](https://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) article at Stack Overflow. -This curious feature provides additional flexibility, enabling [fake type providers](https://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [extended vanilla materialization](/sips/source-locations.html), [fundep materialization]({{ site.baseurl }}/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. +This curious feature provides additional flexibility, enabling [fake type providers](https://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [extended vanilla materialization](https://github.com/scala/improvement-proposals/pull/18), [fundep materialization]({{ site.baseurl }}/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. +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 @@ -48,7 +48,7 @@ Blackbox def macros are treated differently from def macros of Scala 2.10. The f 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](https://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]({{ site.baseurl }}/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]({{ site.baseurl }}/sips/source-locations.html). +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](https://github.com/scala/improvement-proposals/pull/18). 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 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 index 57f380b7f6..255b504391 100644 --- a/_overviews/macros/bundles.md +++ b/_overviews/macros/bundles.md @@ -18,7 +18,7 @@ Macro bundles are a feature of Scala 2.11.x and Scala 2.12.x. Macro bundles are ## 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 +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 diff --git a/_overviews/macros/implicits.md b/_overviews/macros/implicits.md index 1f660d6ec9..04852d0f2d 100644 --- a/_overviews/macros/implicits.md +++ b/_overviews/macros/implicits.md @@ -140,7 +140,7 @@ macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothin 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. +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. diff --git a/_overviews/macros/overview.md b/_overviews/macros/overview.md index 87cf64ee8b..c66b1c6d48 100644 --- a/_overviews/macros/overview.md +++ b/_overviews/macros/overview.md @@ -223,15 +223,15 @@ The walkthrough in this guide uses the simplest possible command-line compilatio * 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 +### Using macros with Intellij IDEA -Both in Scala IDE and in Intellij IDEA macros are known to work fine, given they are moved to a separate project. +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 <path to the classes of your macro> 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. +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. The Intellij IDEA team will probably add support for this in their debugger 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 diff --git a/_overviews/macros/paradise.md b/_overviews/macros/paradise.md index 14e61dd9a5..72637b0854 100644 --- a/_overviews/macros/paradise.md +++ b/_overviews/macros/paradise.md @@ -20,7 +20,7 @@ Macro paradise is a plugin for several versions of Scala compilers. It is designed to reliably work with production releases of <code>scalac</code>, 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](roadmap.html) -and visit [the paradise announcement](https://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html) +and visit [the paradise announcement](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/news/_posts/2013-08-07-roadmap-for-macro-paradise.html) to learn more about our support guarantees. ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases @@ -35,7 +35,7 @@ to learn more about our support guarantees. 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](roadmap.html) document for more information. +Proceed to [the feature list](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 diff --git a/_overviews/macros/typemacros.md b/_overviews/macros/typemacros.md index 691b2f5e83..773819fa6d 100644 --- a/_overviews/macros/typemacros.md +++ b/_overviews/macros/typemacros.md @@ -12,7 +12,7 @@ permalink: /overviews/macros/:title.html Type macros used to be available in previous versions of ["Macro Paradise"](paradise.html), but are not supported anymore in macro paradise 2.0. -Visit [the paradise 2.0 announcement](https://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) +Visit [the paradise 2.0 announcement](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/news/_posts/2013-08-05-macro-paradise-2.0.0-snapshot.html) for an explanation and suggested migration strategy. ## Intuition @@ -84,7 +84,7 @@ In Scala programs type macros can appear in one of five possible roles: type rol 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). +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 diff --git a/_overviews/macros/typeproviders.md b/_overviews/macros/typeproviders.md index 175126eab1..1e90c17003 100644 --- a/_overviews/macros/typeproviders.md +++ b/_overviews/macros/typeproviders.md @@ -85,7 +85,7 @@ captures the essence of the generated classes, providing a statically typed inte 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 +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](https://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/), [post 2](https://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [post 3](https://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/). ## Public type providers diff --git a/_overviews/macros/untypedmacros.md b/_overviews/macros/untypedmacros.md index cfceefb78c..cccb85729b 100644 --- a/_overviews/macros/untypedmacros.md +++ b/_overviews/macros/untypedmacros.md @@ -12,13 +12,13 @@ permalink: /overviews/macros/:title.html Untyped macros used to be available in previous versions of ["Macro Paradise"](paradise.html), but are not supported anymore in macro paradise 2.0. -Visit [the paradise 2.0 announcement](https://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) +Visit [the paradise 2.0 announcement](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/news/_posts/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 +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...) @@ -56,9 +56,9 @@ of the linked JIRA issue. Untyped macros make the full power of textual abstract 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 +and will pass them to the macro untyped. Even if some 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`). +also won't have 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 @@ -69,6 +69,6 @@ the first typecheck of a def macro expansion is performed against the return typ against the expected type of the expandee. More information can be found at Stack Overflow: [Static return type of Scala macros](https://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. +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. diff --git a/_overviews/macros/usecases.md b/_overviews/macros/usecases.md index 335d3f6bd5..eed399f3b1 100644 --- a/_overviews/macros/usecases.md +++ b/_overviews/macros/usecases.md @@ -19,12 +19,12 @@ to the realm of possible. Both commercial and research users of Scala use macros 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?"](https://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf) +The recent talk ["What Are Macros Good For?"](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/paperstalks/2014-02-04-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!"](https://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf), +["Scala Macros: Let Our Powers Combine!"](https://github.com/scalamacros/scalamacros.github.com/blob/5904f7ef88a439c668204b4bf262835e89fb13cb/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 index 2b64486f63..f98b628210 100644 --- a/_overviews/parallel-collections/architecture.md +++ b/_overviews/parallel-collections/architecture.md @@ -87,13 +87,13 @@ 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. -[<img src="{{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png" width="550">]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) +[<img src="{{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png" alt="Hierarchy of Scala Collections and Parallel Collections" width="550">]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) <center><b>Hierarchy of Scala's Collections and Parallel Collections Libraries</b></center> <br/> The goal is of course to integrate parallel collections as tightly as possible -with sequential collections, so as to allow for straightforward substitution +with sequential collections, 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 diff --git a/_overviews/parallel-collections/concrete-parallel-collections.md b/_overviews/parallel-collections/concrete-parallel-collections.md index 2885e72bc9..428f142918 100644 --- a/_overviews/parallel-collections/concrete-parallel-collections.md +++ b/_overviews/parallel-collections/concrete-parallel-collections.md @@ -84,10 +84,10 @@ is an ordered sequence of elements equally spaced apart. A parallel range is created in a similar way as the sequential [Range](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/immutable/Range.html): - scala> 1 to 3 par + scala> (1 to 3).par res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - scala> 15 to 5 by -2 par + 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 @@ -146,7 +146,7 @@ and 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 + scala> phs.map(x => x * x).sum res0: Int = 332833500 Similar to parallel hash tables, parallel hash trie diff --git a/_overviews/parallel-collections/custom-parallel-collections.md b/_overviews/parallel-collections/custom-parallel-collections.md index 7ea4330c62..88307d3910 100644 --- a/_overviews/parallel-collections/custom-parallel-collections.md +++ b/_overviews/parallel-collections/custom-parallel-collections.md @@ -72,10 +72,10 @@ 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 +of which is empty. If the current splitter has 1 or fewer 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 +specified by the `sizes` parameter. If the `sizes` parameter specifies fewer 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 @@ -112,9 +112,9 @@ may be suboptimal - producing a string again from the vector after filtering may ## Parallel collections with combiners -Lets say we want to `filter` the characters of the parallel string, to get rid +Let's 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 +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. @@ -134,7 +134,7 @@ 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 +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 @@ -195,7 +195,7 @@ live with this sequential bottleneck. 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: +part. However, there are a few approaches usually taken: 1. Concatenation and merge. Some data-structures have efficient implementations (usually logarithmic) of these operations. diff --git a/_overviews/parallel-collections/overview.md b/_overviews/parallel-collections/overview.md index b03bec798e..1ced205636 100644 --- a/_overviews/parallel-collections/overview.md +++ b/_overviews/parallel-collections/overview.md @@ -17,7 +17,7 @@ If you're using Scala 2.13+ and want to use Scala's parallel collections, you'll ## Motivation Amidst the shift in recent years by processor manufacturers from single to -multi-core architectures, academia and industry alike have conceded that +multicore 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 @@ -65,7 +65,7 @@ from Scala's (sequential) collection library, including: 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) +collection types and automatically inherit all the predefined (parallel) operations available on the other parallel collections in the standard library. @@ -155,13 +155,13 @@ sections of this guide. 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. +especially in regard 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 +in parallel, and re-"combining" all the results that were completed in parallel. These concurrent, and "out-of-order" semantics of parallel collections lead to @@ -176,7 +176,7 @@ 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`. +declared outside the closure which is passed to `foreach`. scala> var sum = 0 sum: Int = 0 diff --git a/_overviews/parallel-collections/performance.md b/_overviews/parallel-collections/performance.md index 625f08304e..2f7aa27f2f 100644 --- a/_overviews/parallel-collections/performance.md +++ b/_overviews/parallel-collections/performance.md @@ -45,7 +45,7 @@ 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 +represent them, so that they could be passed to a method with a type parameter. This induces extra allocations and is slower, also producing additional garbage on the heap. @@ -81,7 +81,7 @@ For proper benchmark examples, you can see the source code inside [Scala library This is a question commonly asked. The answer is somewhat involved. -The size of the collection at which the parallelization pays of really +The size of the collection at which the parallelization pays off really depends on many factors. Some of them, but not all, include: - Machine architecture. Different CPU types have different diff --git a/_overviews/plugins/index.md b/_overviews/plugins/index.md index c28e441f08..0b1ea54d55 100644 --- a/_overviews/plugins/index.md +++ b/_overviews/plugins/index.md @@ -35,7 +35,7 @@ You should not actually need to modify the Scala compiler very frequently, because Scala's light, flexible syntax will frequently allow you to provide a better solution using a clever library. -There are some times, though, where a compiler modification is the +There are some cases, though, where a compiler modification is the best choice even for Scala. Popular compiler plugins (as of 2018) include: @@ -268,12 +268,12 @@ object foo extends ScalaModule { ``` Please notice, that compiler plugins are typically bound to the full -version of the compiler, hence you have to use the `:::` (instead of -normal `::`) between the organization and the artifact name, +version of the compiler, hence you have to use the `:::` (instead of +normal `::`) between the organization and the artifact name, to declare your dependency. For more information about plugin usage in Mill, please refer to the -[Mill documentation](https://com-lihaoyi.github.io/mill/mill/Configuring_Mill.html#_scala_compiler_plugins). +[Mill documentation for Scala compiler plugins](https://mill-build.org/mill/Scala_Module_Config.html#_scala_compiler_plugins). ## Developing compiler plugins with an IDE diff --git a/_overviews/quasiquotes/expression-details.md b/_overviews/quasiquotes/expression-details.md index 62e810697d..6ef424fac1 100644 --- a/_overviews/quasiquotes/expression-details.md +++ b/_overviews/quasiquotes/expression-details.md @@ -16,7 +16,7 @@ permalink: /overviews/quasiquotes/:title.html 1. `Val`s, `Var`s and `Def`s without the right-hand side have it set to `q""`. 2. Abstract type definitions without bounds have them set to `q""`. -3. `Try` expressions without a finally clause have it set to `q""`. +3. `Try` expressions without a `finally` clause have it set to `q""`. 4. `Case` clauses without guards have them set to `q""`. The default `toString` formats `q""` as `<empty>`. @@ -58,13 +58,13 @@ During deconstruction you can use [unlifting]({{ site.baseurl }}/overviews/quasi scala> val q"${x: Int}" = q"1" x: Int = 1 -Similarly it would work with all the literal types except `Null`. (see [standard unliftables]({{ site.baseurl }}/overviews/quasiquotes/unlifting.html#standard-unliftables)) +Similarly, it would work with all the literal types except `Null`. (see [standard unliftables]({{ site.baseurl }}/overviews/quasiquotes/unlifting.html#standard-unliftables)) ## Identifier and Selection Identifiers and member selections are two fundamental primitives that let you refer to other definitions. A combination of two of them is also known as a `RefTree`. -Each term identifier is defined by its name and whether or not it is backquoted: +Each term identifier is defined by its name and whether it is backquoted: scala> val name = TermName("Foo") name: universe.TermName = Foo @@ -90,7 +90,7 @@ Apart from matching on identifiers with a given name, you can also extract their Name ascription is important here because without it you'll get a pattern that is equivalent to regular pattern variable binding. -Similarly you can create and extract member selections: +Similarly, you can create and extract member selections: scala> val member = TermName("bar") member: universe.TermName = bar @@ -112,7 +112,7 @@ This tree supports following variations: So an unqualified `q"this"` is equivalent to `q"${tpnme.EMPTY}.this"`. -Similarly for `super` we have: +Similarly, for `super` we have: scala> val q"$name.super[$qual].$field" = q"super.foo" name: universe.TypeName = @@ -145,7 +145,7 @@ This can be accomplished with the following: type arguments: List(Int), value arguments: List(1, 2) type arguments: List(), value arguments: List(scala.Symbol("a"), scala.Symbol("b")) -As you can see, we were able to match both calls regardless as to whether or not a specific type application exists. This happens because the type application matcher extracts the empty list of type arguments if the tree is not an actual type application, making it possible to handle both situations uniformly. +As you can see, we were able to match both calls regardless of whether a specific type application exists. This happens because the type application matcher extracts the empty list of type arguments if the tree is not an actual type application, making it possible to handle both situations uniformly. It is recommended to always include type applications when you match on a function with type arguments, as they will be inserted by the compiler during type checking, even if the user didn't write them explicitly: @@ -175,7 +175,7 @@ Here we might get one, or two subsequent value applications: scala> val q"g(...$argss)" = q"g" argss: List[List[universe.Tree]] = List() -Therefore it's recommended to use more specific patterns that check that ensure the extracted `argss` is not empty. +Therefore, it's recommended to use more specific patterns that check that ensure the extracted `argss` is not empty. Similarly to type arguments, implicit value arguments are automatically inferred during type checking: @@ -244,7 +244,7 @@ The *throw* expression is used to throw a throwable: ## Ascription -Ascriptions let users annotate the type of an intermediate expression: +Ascriptions let users annotate the type of intermediate expression: scala> val ascribed = q"(1 + 1): Int" ascribed: universe.Typed = (1.$plus(1): Int) @@ -469,7 +469,7 @@ There are three ways to create anonymous function: scala> val f3 = q"(a: Int) => a + 1" anon3: universe.Function = ((a: Int) => a.$plus(1)) -The first one uses the placeholder syntax. The second one names the function parameter but still relies on type inference to infer its type. An the last one explicitly defines the function parameter. Due to an implementation restriction, the second notation can only be used in parentheses or inside another expression. If you leave them out the you must specify the parameter types. +The first one uses the placeholder syntax. The second one names the function parameter but still relies on type inference to infer its type. An the last one explicitly defines the function parameter. Due to an implementation restriction, the second notation can only be used in parentheses or inside another expression. If you leave them out then you must specify the parameter types. Parameters are represented as [Vals]({{ site.baseurl }}/overviews/quasiquotes/definition-details.html#val-and-var-definitions). If you want to programmatically create a `val` that should have its type inferred you need to use the [empty type]({{ site.baseurl }}/overviews/quasiquotes/type-details.html#empty-type): @@ -576,7 +576,7 @@ Each enumerator in the comprehension can be expressed with the `fq"..."` interpo scala> val `for-yield` = q"for (..$enums) yield y" for-yield: universe.Tree -Similarly one can deconstruct the `for-yield` back into a list of enumerators and body: +Similarly, one can deconstruct the `for-yield` back into a list of enumerators and body: scala> val q"for (..$enums) yield $body" = `for-yield` enums: List[universe.Tree] = List(`<-`((x @ _), xs), `if`(x.$greater(0)), (y @ _) = x.$times(2)) @@ -609,10 +609,10 @@ Selectors are extracted as pattern trees that are syntactically similar to selec 1. Simple identifier selectors are represented as pattern bindings: `pq"bar"` 2. Renaming selectors are represented as thin arrow patterns: `pq"baz -> boo"` -3. Unimport selectors are represented as thin arrows with a wildcard right hand side: `pq"poison -> _"` +3. Unimport selectors are represented as thin arrows with a wildcard right-hand side: `pq"poison -> _"` 4. The wildcard selector is represented as a wildcard pattern: `pq"_"` -Similarly one construct imports back from a programmatically created list of selectors: +Similarly, one construct imports back from a programmatically created list of selectors: scala> val ref = q"a.b" scala> val sels = List(pq"foo -> _", pq"_") diff --git a/_overviews/quasiquotes/hygiene.md b/_overviews/quasiquotes/hygiene.md index 1523655696..f08a9145de 100644 --- a/_overviews/quasiquotes/hygiene.md +++ b/_overviews/quasiquotes/hygiene.md @@ -12,7 +12,7 @@ permalink: /overviews/quasiquotes/:title.html 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. -Sophisticated macro systems such as Racket's have mechanisms that make macros hygienic without any effort from macro writers. In Scala we don't have automatic hygiene - both of our codegen facilities (compile-time codegen with macros and runtime codegen with toolboxes) require programmers to handle hygiene manually. You must know how to work around the absence of hygiene, which is what this section is about. +Sophisticated macro systems such as Racket's have mechanisms that make macros hygienic without any effort from macro writers. In Scala, we don't have automatic hygiene - both of our codegen facilities (compile-time codegen with macros and runtime codegen with toolboxes) require programmers to handle hygiene manually. You must know how to work around the absence of hygiene, which is what this section is about. Preventing name clashes between regular and generated code means two things. First, we must ensure that, regardless of the context in which we put generated code, its meaning will not change (*referential transparency*). Second, we must make certain that regardless of the context in which we splice regular code, its meaning will not change (often called *hygiene in the narrow sense*). Let's see what can be done to this end on a series of examples. @@ -56,7 +56,7 @@ Here we can see that the unqualified reference to `Map` does not respect our cus MyMacro(2) } -If we compile both the macro and it's usage, we'll see that `println` will not be called when the application runs. This will happen because, after macro expansion, `Test.scala` will look like: +If we compile both the macro, and it's usage, we'll see that `println` will not be called when the application runs. This will happen because, after macro expansion, `Test.scala` will look like: // Expanded Test.scala package example diff --git a/_overviews/quasiquotes/intro.md b/_overviews/quasiquotes/intro.md index 4ffba9e912..de31e4f162 100644 --- a/_overviews/quasiquotes/intro.md +++ b/_overviews/quasiquotes/intro.md @@ -90,7 +90,7 @@ Similarly, patterns and expressions are also not equivalent: It's extremely important to use the right interpolator for the job in order to construct a valid syntax tree. -Additionally there are two auxiliary interpolators that let you work with minor areas of scala syntax: +Additionally, there are two auxiliary interpolators that let you work with minor areas of scala syntax:   | Used for ----|------------------------------------- diff --git a/_overviews/quasiquotes/setup.md b/_overviews/quasiquotes/setup.md index b121d666d6..155ee8a32b 100644 --- a/_overviews/quasiquotes/setup.md +++ b/_overviews/quasiquotes/setup.md @@ -18,9 +18,9 @@ All examples and code snippets in this guide are run under in 2.11 REPL with one scala> val universe: scala.reflect.runtime.universe.type = scala.reflect.runtime.universe scala> import universe._ -A wildcard import from a universe (be it a runtime reflection universe like here or a compile-time universe provided in macros) is all that's needed to use quasiquotes. All of the examples will assume that import. +A wildcard import from a universe (be it a runtime reflection universe like here or a compile-time universe provided in macros) is all that's needed to use quasiquotes. All the examples will assume that import. -Additionally some examples that use `ToolBox` API will need a few more lines to get things rolling: +Additionally, some examples that use `ToolBox` API will need a few more lines to get things rolling: scala> import scala.reflect.runtime.currentMirror scala> import scala.tools.reflect.ToolBox diff --git a/_overviews/quasiquotes/type-details.md b/_overviews/quasiquotes/type-details.md index f67cd4e563..a3cd254d24 100644 --- a/_overviews/quasiquotes/type-details.md +++ b/_overviews/quasiquotes/type-details.md @@ -37,7 +37,7 @@ It is recommended to always ascribe the name as `TypeName` when you work with ty ## Singleton Type -A singleton type is a way to express a type of a term definition that is being referenced: +A singleton type is a way to express a type of term definition that is being referenced: scala> val singleton = tq"foo.bar.type".sr singleton: String = SingletonTypeTree(Select(Ident(TermName("foo")), TermName("bar"))) @@ -124,7 +124,7 @@ A compound type lets users express a combination of a number of types with an op parents: List[universe.Tree] = List(A, B, C) defns: List[universe.Tree] = List() -Braces after parents are required to signal that this type is a compound type, even if there are no refinements and we just want to extract a sequence of types combined with the `with` keyword. +Braces after parents are required to signal that this type is a compound type, even if there are no refinements, and we just want to extract a sequence of types combined with the `with` keyword. On the other side of the spectrum are pure refinements without explicit parents (a.k.a. structural types): diff --git a/_overviews/quasiquotes/unlifting.md b/_overviews/quasiquotes/unlifting.md index e23f2d7152..adb8d4ed41 100644 --- a/_overviews/quasiquotes/unlifting.md +++ b/_overviews/quasiquotes/unlifting.md @@ -65,7 +65,7 @@ Here one must pay attention to a few nuances: 1. Similarly to `Liftable`, `Unliftable` defines a helper `apply` function in the companion object to simplify the creation of `Unliftable` instances. It - take a type parameter `T` as well as a partial function `PartialFunction[Tree, T]` + takes a type parameter `T` as well as a partial function `PartialFunction[Tree, T]` and returns an `Unliftable[T]`. At all inputs where a partial function is defined it is expected to return an instance of `T` unconditionally. diff --git a/_overviews/reflection/annotations-names-scopes.md b/_overviews/reflection/annotations-names-scopes.md index 7bf66cafcf..a4d1bbcce0 100644 --- a/_overviews/reflection/annotations-names-scopes.md +++ b/_overviews/reflection/annotations-names-scopes.md @@ -58,7 +58,7 @@ represent different kinds of Java annotation arguments: ## Names Names are simple wrappers for strings. -[Name](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Names$NameApi.html) +[Name](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Names$NameApi.html) 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, @@ -104,19 +104,19 @@ There are both Some names, such as "package", exist both as a type name and a term name. Standard names are made available through the `termNames` and `typeNames` members of class `Universe`. For a complete specification of all standard names, see the -[API documentation](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/StandardNames.html). +[API documentation](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/StandardNames.html). ## 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](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Symbols$Symbol.html)s. +iterable of [Symbol](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Symbols$Symbol.html)s. Additional functionality is exposed in *member scopes* that are returned by `members` and `decls` defined in -[scala.reflect.api.Types#TypeApi](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types$TypeApi.html). -[scala.reflect.api.Scopes#MemberScope](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Scopes$MemberScope.html) +[scala.reflect.api.Types#TypeApi](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types$TypeApi.html). +[scala.reflect.api.Scopes#MemberScope](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Scopes$MemberScope.html) supports the `sorted` method, which sorts members *in declaration order*. The following example returns a list of the symbols of all final members @@ -129,7 +129,7 @@ of the `List` class, in declaration order: 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`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Exprs$Expr.html). +[`scala.reflect.api.Exprs#Expr`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Exprs$Expr.html). 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 @@ -189,9 +189,9 @@ expressions are compile-time constants (see [section 6.24 of the Scala language 2. String literals - represented as instances of the string. -3. References to classes, typically constructed with [scala.Predef#classOf](https://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) - represented as [types](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types$Type.html). +3. References to classes, typically constructed with [scala.Predef#classOf](https://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) - represented as [types](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types$Type.html). -4. References to Java enumeration values - represented as [symbols](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Symbols$Symbol.html). +4. References to Java enumeration values - represented as [symbols](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Symbols$Symbol.html). Constant expressions are used to represent @@ -287,8 +287,8 @@ Example: ## Printers Utilities for nicely printing -[`Trees`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Trees.html) and -[`Types`](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Types.html). +[`Trees`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Trees.html) and +[`Types`](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Types.html). ### Printing Trees @@ -408,7 +408,7 @@ additionally shows the unique identifiers of symbols, as well as their kind ## Positions Positions (instances of the -[Position](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Position.html) trait) +[Position](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Position.html) 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 diff --git a/_overviews/reflection/overview.md b/_overviews/reflection/overview.md index 3de78d0525..d388e4016e 100644 --- a/_overviews/reflection/overview.md +++ b/_overviews/reflection/overview.md @@ -21,7 +21,7 @@ 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 +Reflection involves the ability to **reify** (i.e. 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. @@ -325,7 +325,7 @@ 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](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Universe.html) +[Universes API docs](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Universe.html) in package `scala.reflect.api`. To use most aspects of Scala reflection, including most code examples provided @@ -345,5 +345,5 @@ 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](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Mirrors.html) +[Mirrors API docs](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Mirrors.html) in package `scala.reflect.api`. diff --git a/_overviews/reflection/symbols-trees-types.md b/_overviews/reflection/symbols-trees-types.md index faad275ac0..4fba8ca28e 100644 --- a/_overviews/reflection/symbols-trees-types.md +++ b/_overviews/reflection/symbols-trees-types.md @@ -694,11 +694,11 @@ section: 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 +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 +_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: bash$ scala -Yrepl-class-based:false @@ -726,7 +726,7 @@ 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 +`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 diff --git a/_overviews/reflection/thread-safety.md b/_overviews/reflection/thread-safety.md index 862d465872..6c5aaa2e11 100644 --- a/_overviews/reflection/thread-safety.md +++ b/_overviews/reflection/thread-safety.md @@ -20,7 +20,7 @@ and to look up technical details, and here's a concise summary of the state of t <p><span class="label success">NEW</span> 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.</p> -Currently we know about two kinds of races associated with reflection. First of all, reflection initialization (the code that is called +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: diff --git a/_overviews/repl/overview.md b/_overviews/repl/overview.md index 38d5008dd6..c462643399 100644 --- a/_overviews/repl/overview.md +++ b/_overviews/repl/overview.md @@ -79,4 +79,4 @@ 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). +mechanism for the project and pull requests are accepted at [the GitHub repository](https://github.com/scala/scala). diff --git a/_overviews/scala-book/abstract-classes.md b/_overviews/scala-book/abstract-classes.md index a5ec3b96fa..88c496945c 100644 --- a/_overviews/scala-book/abstract-classes.md +++ b/_overviews/scala-book/abstract-classes.md @@ -5,11 +5,11 @@ title: Abstract Classes description: This page shows how to use abstract classes, including when and why you should use abstract classes. partof: scala_book overview-name: Scala Book -discourse: true num: 27 outof: 54 previous-page: traits-abstract-mixins next-page: collections-101 +new-version: /scala3/book/domain-modeling-tools.html#abstract-classes --- @@ -107,11 +107,3 @@ d.speak ``` We encourage you to copy and paste that code into the REPL to be sure that it works as expected, and then experiment with it as desired. - - - - - - - - diff --git a/_overviews/scala-book/anonymous-functions.md b/_overviews/scala-book/anonymous-functions.md index bbd7bc8d8d..619d8854a7 100644 --- a/_overviews/scala-book/anonymous-functions.md +++ b/_overviews/scala-book/anonymous-functions.md @@ -5,11 +5,11 @@ title: Anonymous Functions description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. partof: scala_book overview-name: Scala Book -discourse: true num: 34 outof: 54 previous-page: set-class next-page: collections-methods +new-version: /scala3/book/fun-anonymous-functions.html --- @@ -201,16 +201,3 @@ is the same as this example: ```scala val y = ints.filter(_ < 5) ``` - - - - - - - - - - - - - diff --git a/_overviews/scala-book/arraybuffer-examples.md b/_overviews/scala-book/arraybuffer-examples.md index 8d4dd8ef53..06bd6d1af2 100644 --- a/_overviews/scala-book/arraybuffer-examples.md +++ b/_overviews/scala-book/arraybuffer-examples.md @@ -5,11 +5,11 @@ title: The ArrayBuffer Class description: This page provides examples of how to use the Scala ArrayBuffer class, including adding and removing elements. partof: scala_book overview-name: Scala Book -discourse: true num: 29 outof: 54 previous-page: collections-101 next-page: list-class +new-version: /scala3/book/collections-classes.html#arraybuffer --- @@ -114,31 +114,20 @@ As a brief overview, here are several methods you can use with an `ArrayBuffer`: ```scala val a = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) a.append(4) // ArrayBuffer(1, 2, 3, 4) -a.append(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) -a.appendAll(Seq(7,8)) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8) +a.appendAll(Seq(5, 6)) // ArrayBuffer(1, 2, 3, 4, 5, 6) a.clear // ArrayBuffer() val a = ArrayBuffer(9, 10) // ArrayBuffer(9, 10) a.insert(0, 8) // ArrayBuffer(8, 9, 10) a.insertAll(0, Vector(4, 5, 6, 7)) // ArrayBuffer(4, 5, 6, 7, 8, 9, 10) a.prepend(3) // ArrayBuffer(3, 4, 5, 6, 7, 8, 9, 10) -a.prepend(1, 2) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) -a.prependAll(Array(0)) // ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +a.prependAll(Array(0, 1, 2)) // ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) a.remove(0) // ArrayBuffer(b, c, d, e, f, g) a.remove(2, 3) // ArrayBuffer(b, c, g) val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) -a.trimStart(2) // ArrayBuffer(c, d, e, f, g) -a.trimEnd(2) // ArrayBuffer(c, d, e) +a.dropInPlace(2) // ArrayBuffer(c, d, e, f, g) +a.dropRightInPlace(2) // ArrayBuffer(c, d, e) ``` - - - - - - - - - diff --git a/_overviews/scala-book/built-in-types.md b/_overviews/scala-book/built-in-types.md index 209b1b8f0b..c251b9a4f1 100644 --- a/_overviews/scala-book/built-in-types.md +++ b/_overviews/scala-book/built-in-types.md @@ -5,11 +5,11 @@ title: A Few Built-In Types description: A brief introduction to Scala's built-in types. partof: scala_book overview-name: Scala Book -discourse: true num: 10 outof: 54 previous-page: type-is-optional next-page: two-notes-about-strings +new-version: /scala3/book/first-look-at-types.html#scalas-value-types --- @@ -106,10 +106,3 @@ val c: Char = 'a' ``` As shown, enclose strings in double-quotes and a character in single-quotes. - - - - - - - diff --git a/_overviews/scala-book/case-classes.md b/_overviews/scala-book/case-classes.md index 8722d4c5ae..9ffae6db23 100644 --- a/_overviews/scala-book/case-classes.md +++ b/_overviews/scala-book/case-classes.md @@ -5,11 +5,11 @@ title: Case Classes description: This lesson provides an introduction to 'case classes' in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 49 outof: 54 previous-page: companion-objects next-page: case-objects +new-version: /scala3/book/domain-modeling-tools.html#case-classes --- @@ -185,5 +185,3 @@ res0: Person = Person(Christina,niece) ## The biggest advantage While all of these features are great benefits to functional programming, as they write in the book, [Programming in Scala](https://www.amazon.com/Programming-Scala-Updated-2-12/dp/0981531687/) (Odersky, Spoon, and Venners), “the biggest advantage of case classes is that they support pattern matching.” Pattern matching is a major feature of FP languages, and Scala’s case classes provide a simple way to implement pattern matching in match expressions and other areas. - - diff --git a/_overviews/scala-book/case-objects.md b/_overviews/scala-book/case-objects.md index 1b7426f12a..9bb17d2ec7 100644 --- a/_overviews/scala-book/case-objects.md +++ b/_overviews/scala-book/case-objects.md @@ -5,11 +5,11 @@ title: Case Objects description: This lesson introduces Scala 'case objects', which are used to create singletons with a few additional features. partof: scala_book overview-name: Scala Book -discourse: true num: 50 outof: 54 previous-page: case-classes next-page: functional-error-handling +new-version: /scala3/book/domain-modeling-tools.html#case-objects --- @@ -123,11 +123,3 @@ class Speak extends Actor { ``` This is a good, safe way to pass messages around in Scala applications. - - - - - - - - diff --git a/_overviews/scala-book/classes-aux-constructors.md b/_overviews/scala-book/classes-aux-constructors.md index a66a4b4d80..8bca7dc8cf 100644 --- a/_overviews/scala-book/classes-aux-constructors.md +++ b/_overviews/scala-book/classes-aux-constructors.md @@ -5,11 +5,11 @@ title: Auxiliary Class Constructors description: This page shows how to write auxiliary Scala class constructors, including several examples of the syntax. partof: scala_book overview-name: Scala Book -discourse: true num: 20 outof: 54 previous-page: classes next-page: constructors-default-values +new-version: /scala3/book/domain-modeling-tools.html#auxiliary-constructors --- @@ -72,11 +72,3 @@ class Pizza( var crustType: String = DefaultCrustType ) ``` - - - - - - - - diff --git a/_overviews/scala-book/classes.md b/_overviews/scala-book/classes.md index bc4fe65b66..bc7928eea0 100644 --- a/_overviews/scala-book/classes.md +++ b/_overviews/scala-book/classes.md @@ -5,11 +5,11 @@ title: Scala Classes description: This page shows examples of how to create Scala classes, including the basic Scala class constructor. partof: scala_book overview-name: Scala Book -discourse: true num: 19 outof: 54 previous-page: try-catch-finally next-page: classes-aux-constructors +new-version: /scala3/book/domain-modeling-tools.html#classes --- @@ -209,14 +209,3 @@ class Address ( var state: String ) ``` - - - - - - - - - - - diff --git a/_overviews/scala-book/collections-101.md b/_overviews/scala-book/collections-101.md index 6df136b528..995c20520b 100644 --- a/_overviews/scala-book/collections-101.md +++ b/_overviews/scala-book/collections-101.md @@ -5,11 +5,11 @@ title: Scala Collections description: This page provides an introduction to the Scala collections classes, including Vector, List, ArrayBuffer, Map, Set, and more. partof: scala_book overview-name: Scala Book -discourse: true num: 28 outof: 54 previous-page: abstract-classes next-page: arraybuffer-examples +new-version: /scala3/book/collections-intro.html --- @@ -34,6 +34,3 @@ The main Scala collections classes you’ll use on a regular basis are: We’ll demonstrate the basics of these classes in the following lessons. >In the following lessons on Scala collections classes, whenever we use the word *immutable*, it’s safe to assume that the class is intended for use in a *functional programming* (FP) style. With these classes you don’t modify the collection; you apply functional methods to the collection to create a new result. You’ll see what this means in the examples that follow. - - - diff --git a/_overviews/scala-book/collections-maps.md b/_overviews/scala-book/collections-maps.md index 95e890bbc3..0abc9da611 100644 --- a/_overviews/scala-book/collections-maps.md +++ b/_overviews/scala-book/collections-maps.md @@ -5,11 +5,11 @@ title: Common Map Methods description: This page shows examples of the most common methods that are available on Scala Maps. partof: scala_book overview-name: Scala Book -discourse: true num: 36 outof: 54 previous-page: collections-methods next-page: misc +new-version: /scala3/book/collections-methods.html --- @@ -102,13 +102,3 @@ states.filterInPlace((k,v) => k == "AK") ## See also There are many more things you can do with maps. See the [Map class documentation]({{site.baseurl}}/overviews/collections-2.13/maps.html) for more details and examples. - - - - - - - - - - diff --git a/_overviews/scala-book/collections-methods.md b/_overviews/scala-book/collections-methods.md index 7bd8d9a15a..e6620ec6cc 100644 --- a/_overviews/scala-book/collections-methods.md +++ b/_overviews/scala-book/collections-methods.md @@ -5,11 +5,11 @@ title: Common Sequence Methods description: This page shows examples of the most common methods that are available on the Scala sequences (collections classes). partof: scala_book overview-name: Scala Book -discourse: true num: 35 outof: 54 previous-page: anonymous-functions next-page: collections-maps +new-version: /scala3/book/collections-methods.html --- @@ -320,8 +320,3 @@ That might be a little mind-blowing if you’ve never seen it before, but after ## Even more! There are literally dozens of additional methods on the Scala sequence classes that will keep you from ever needing to write another `for` loop. However, because this is a simple introduction book they won’t all be covered here. For more information, see [the collections overview of sequence traits]({{site.baseurl}}/overviews/collections-2.13/seqs.html). - - - - - diff --git a/_overviews/scala-book/command-line-io.md b/_overviews/scala-book/command-line-io.md index ffb35f698e..b3ea6ca64c 100644 --- a/_overviews/scala-book/command-line-io.md +++ b/_overviews/scala-book/command-line-io.md @@ -5,11 +5,11 @@ title: Command-Line I/O description: An introduction to command-line I/O in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 12 outof: 54 previous-page: two-notes-about-strings next-page: control-structures +new-version: /scala3/book/taste-hello-world.html#ask-for-user-input --- @@ -98,12 +98,3 @@ import scala.io.StdIn.readLine ``` That import statement brings the `readLine` method into the current scope so you can use it in the application. - - - - - - - - - diff --git a/_overviews/scala-book/companion-objects.md b/_overviews/scala-book/companion-objects.md index 6babb21eb9..dc8cb8b1d3 100644 --- a/_overviews/scala-book/companion-objects.md +++ b/_overviews/scala-book/companion-objects.md @@ -5,11 +5,11 @@ title: Companion Objects description: This lesson provides an introduction to 'companion objects' in Scala, including writing 'apply' and 'unapply' methods. partof: scala_book overview-name: Scala Book -discourse: true num: 48 outof: 54 previous-page: no-null-values next-page: case-classes +new-version: /scala3/book/domain-modeling-tools.html#companion-objects --- @@ -271,13 +271,3 @@ The key points of this lesson are: - A companion object and its class can access each other’s private members - A companion object’s `apply` method lets you create new instances of a class without using the `new` keyword - A companion object’s `unapply` method lets you de-construct an instance of a class into its individual components - - - - - - - - - - diff --git a/_overviews/scala-book/concurrency-signpost.md b/_overviews/scala-book/concurrency-signpost.md index 1629700299..49ab2cd094 100644 --- a/_overviews/scala-book/concurrency-signpost.md +++ b/_overviews/scala-book/concurrency-signpost.md @@ -5,13 +5,12 @@ title: Concurrency description: An introduction to concurrency in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 52 outof: 54 previous-page: functional-error-handling next-page: futures +new-version: /scala3/book/concurrency.html --- In the next lesson you’ll see a primary tool for writing parallel and concurrent applications, the Scala `Future`. - diff --git a/_overviews/scala-book/constructors-default-values.md b/_overviews/scala-book/constructors-default-values.md index aa4429305e..952fe3fd46 100644 --- a/_overviews/scala-book/constructors-default-values.md +++ b/_overviews/scala-book/constructors-default-values.md @@ -5,11 +5,11 @@ title: Supplying Default Values for Constructor Parameters description: This page shows how to provide default values for Scala constructor parameters, with several examples. partof: scala_book overview-name: Scala Book -discourse: true num: 21 outof: 54 previous-page: classes-aux-constructors next-page: methods-first-look +new-version: /scala3/book/domain-modeling-tools.html#default-parameter-values --- Scala lets you supply default values for constructor parameters. For example, in previous lessons we showed that you can define a `Socket` class like this: @@ -88,12 +88,3 @@ is more readable than this code: ```scala val s = new Socket(2000, 3000) ``` - - - - - - - - - diff --git a/_overviews/scala-book/control-structures.md b/_overviews/scala-book/control-structures.md index 813a05face..8724ba7050 100644 --- a/_overviews/scala-book/control-structures.md +++ b/_overviews/scala-book/control-structures.md @@ -5,11 +5,11 @@ title: Control Structures description: This page provides an introduction to Scala's control structures, including if/then/else, for loops, try/catch/finally, etc. partof: scala_book overview-name: Scala Book -discourse: true num: 13 outof: 54 previous-page: command-line-io next-page: if-then-else-construct +new-version: /scala3/book/control-structures.html --- @@ -25,10 +25,3 @@ It also has a few unique constructs, including: - `for` expressions We’ll demonstrate these in the following lessons. - - - - - - - diff --git a/_overviews/scala-book/enumerations-pizza-class.md b/_overviews/scala-book/enumerations-pizza-class.md index 31625129f0..abe76d8b07 100644 --- a/_overviews/scala-book/enumerations-pizza-class.md +++ b/_overviews/scala-book/enumerations-pizza-class.md @@ -5,11 +5,11 @@ title: Enumerations (and a Complete Pizza Class) description: This page introduces Scala enumerations, and further shows how to create a complete OOP 'Pizza' class that uses those enumerations. partof: scala_book overview-name: Scala Book -discourse: true num: 23 outof: 54 previous-page: methods-first-look next-page: traits-intro +new-version: /scala3/book/domain-modeling-fp.html#modeling-the-data --- @@ -186,7 +186,3 @@ Toppings: ArrayBuffer(Cheese, Pepperoni) That code combines several different concepts — including two things we haven’t discussed yet in the `import` statement and the `ArrayBuffer` — but if you have experience with Java and other languages, hopefully it’s not too much to throw at you at one time. At this point we encourage you to work with that code as desired. Make changes to the code, and try using the `removeTopping` and `removeAllToppings` methods to make sure they work the way you expect them to work. - - - - diff --git a/_overviews/scala-book/for-expressions.md b/_overviews/scala-book/for-expressions.md index 7977777872..e7e5c0a90a 100644 --- a/_overviews/scala-book/for-expressions.md +++ b/_overviews/scala-book/for-expressions.md @@ -5,11 +5,11 @@ title: for Expressions description: This page shows how to use Scala 'for' expressions (also known as 'for-expressions'), including examples of how to use it with the 'yield' keyword. partof: scala_book overview-name: Scala Book -discourse: true num: 16 outof: 54 previous-page: for-loops next-page: match-expressions +new-version: /scala3/book/control-structures.html#for-expressions --- @@ -125,11 +125,3 @@ You can also put curly braces around the algorithm, if you prefer: ```scala val capNames = for (name <- names) yield { name.drop(1).capitalize } ``` - - - - - - - - diff --git a/_overviews/scala-book/for-loops.md b/_overviews/scala-book/for-loops.md index 5eef6cc279..b462c4d289 100644 --- a/_overviews/scala-book/for-loops.md +++ b/_overviews/scala-book/for-loops.md @@ -5,11 +5,11 @@ title: for Loops description: This page provides an introduction to the Scala 'for' loop, including how to iterate over Scala collections. partof: scala_book overview-name: Scala Book -discourse: true num: 15 outof: 54 previous-page: if-then-else-construct next-page: for-expressions +new-version: /scala3/book/control-structures.html#for-loops --- @@ -107,7 +107,3 @@ ratings.foreach { case(movie, rating) => println(s"key: $movie, value: $rating") } ``` - - - - diff --git a/_overviews/scala-book/functional-error-handling.md b/_overviews/scala-book/functional-error-handling.md index 00a448ccc0..bdbcc2f228 100644 --- a/_overviews/scala-book/functional-error-handling.md +++ b/_overviews/scala-book/functional-error-handling.md @@ -5,11 +5,11 @@ title: Functional Error Handling in Scala description: This lesson takes a look at error handling with functional programming in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 51 outof: 54 previous-page: case-objects next-page: concurrency-signpost +new-version: /scala3/book/fp-functional-error-handling.html --- @@ -129,15 +129,3 @@ scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: There are other classes that work in a similar manner, including Either/Left/Right in the Scala library, and other third-party libraries, but Option/Some/None and Try/Success/Failure are commonly used, and good to learn first. You can use whatever you like, but Try/Success/Failure is generally used when dealing with code that can throw exceptions — because you almost always want to understand the exception — and Option/Some/None is used in other places, such as to avoid using null values. - - - - - - - - - - - - diff --git a/_overviews/scala-book/functional-programming.md b/_overviews/scala-book/functional-programming.md index aa0581cf87..806697f189 100644 --- a/_overviews/scala-book/functional-programming.md +++ b/_overviews/scala-book/functional-programming.md @@ -5,11 +5,11 @@ title: Functional Programming description: This lesson begins a second on 'An introduction to functional programming in Scala'. partof: scala_book overview-name: Scala Book -discourse: true num: 44 outof: 54 previous-page: sbt-scalatest-bdd next-page: pure-functions +new-version: /scala3/book/fp-intro.html --- @@ -19,13 +19,3 @@ Scala lets you write code in an object-oriented programming (OOP) style, a funct *Functional programming* is a style of programming that emphasizes writing applications using only pure functions and immutable values. As Alvin Alexander wrote in [Functional Programming, Simplified](https://alvinalexander.com/scala/functional-programming-simplified-book), rather than using that description, it can be helpful to say that functional programmers have an extremely strong desire to see their code as math — to see the combination of their functions as a series of algebraic equations. In that regard, you could say that functional programmers like to think of themselves as mathematicians. That’s the driving desire that leads them to use *only* pure functions and immutable values, because that’s what you use in algebra and other forms of math. Functional programming is a large topic, and there’s no simple way to condense the entire topic into this little book, but in the following lessons we’ll give you a taste of FP, and show some of the tools Scala provides for developers to write functional code. - - - - - - - - - - diff --git a/_overviews/scala-book/futures.md b/_overviews/scala-book/futures.md index 9324b0ddf9..8493ed1931 100644 --- a/_overviews/scala-book/futures.md +++ b/_overviews/scala-book/futures.md @@ -5,11 +5,11 @@ title: Scala Futures description: This page provides an introduction to Futures in Scala, including Future callback methods. partof: scala_book overview-name: Scala Book -discourse: true num: 53 outof: 54 previous-page: concurrency-signpost next-page: where-next +new-version: /scala3/book/concurrency.html --- When you want to write parallel and concurrent applications in Scala, you *could* still use the native Java `Thread` — but the Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) makes parallel/concurrent programming much simpler, and it’s preferred. @@ -348,12 +348,3 @@ While this was a short introduction, hopefully those examples give you an idea o - A small demo GUI application named *Future Board* was written to accompany this lesson. It works a little like [Flipboard](https://flipboard.com), updating a group of news sources simultaneously. You can find the source code for Future Board in [this Github repository](https://github.com/alvinj/FPFutures). - While futures are intended for one-short, relatively short-lived concurrent processes, [Akka](https://akka.io) is an “actor model” library for Scala, and provides a terrific way to implement long-running parallel processes. (If this term is new to you, an *actor* is a long-running process that runs in parallel to the main application thread, and responds to messages that are sent to it.) - - - - - - - - - diff --git a/_overviews/scala-book/hello-world-1.md b/_overviews/scala-book/hello-world-1.md index c9793a2376..d9f9ddc0c6 100644 --- a/_overviews/scala-book/hello-world-1.md +++ b/_overviews/scala-book/hello-world-1.md @@ -5,11 +5,11 @@ title: Hello, World description: This page shares a Scala 'Hello, world' example. partof: scala_book overview-name: Scala Book -discourse: true num: 5 outof: 54 previous-page: scala-features next-page: hello-world-2 +new-version: /scala3/book/taste-hello-world.html --- Since the release of the book, *C Programming Language*, most programming books have begun with a simple “Hello, world” example, and in keeping with tradition, here’s the source code for a Scala “Hello, world” example: @@ -87,7 +87,3 @@ public final class Hello { ```` As that output shows, the `javap` command reads that *.class* file just as if it was created from Java source code. Scala code runs on the JVM and can use existing Java libraries — and both are terrific benefits for Scala programmers. - - - - diff --git a/_overviews/scala-book/hello-world-2.md b/_overviews/scala-book/hello-world-2.md index d07b7da00f..ac2f61cfe2 100644 --- a/_overviews/scala-book/hello-world-2.md +++ b/_overviews/scala-book/hello-world-2.md @@ -5,11 +5,11 @@ title: Hello, World - Version 2 description: This is a second Scala 'Hello, World' example. partof: scala_book overview-name: Scala Book -discourse: true num: 6 outof: 54 previous-page: hello-world-1 next-page: scala-repl +new-version: /scala3/book/taste-hello-world.html --- While that first “Hello, World” example works just fine, Scala provides a way to write applications more conveniently. Rather than including a `main` method, your `object` can just extend the `App` trait, like this: @@ -62,15 +62,3 @@ This shows: - Command-line arguments are automatically made available to you in a variable named `args`. - You determine the number of elements in `args` with `args.size` (or `args.length`, if you prefer). - `args` is an `Array`, and you access `Array` elements as `args(0)`, `args(1)`, etc. Because `args` is an object, you access the array elements with parentheses (not `[]` or any other special syntax). - - - - - - - - - - - - diff --git a/_overviews/scala-book/if-then-else-construct.md b/_overviews/scala-book/if-then-else-construct.md index 6fd09ef879..7087c3340c 100644 --- a/_overviews/scala-book/if-then-else-construct.md +++ b/_overviews/scala-book/if-then-else-construct.md @@ -5,11 +5,11 @@ title: The if/then/else Construct description: This page demonstrates Scala's if/then/else construct, including several examples you can try in the REPL. partof: scala_book overview-name: Scala Book -discourse: true num: 14 outof: 54 previous-page: control-structures next-page: for-loops +new-version: /scala3/book/control-structures.html#the-ifthenelse-construct --- @@ -79,10 +79,3 @@ println("Hello") ``` The first example runs the `doSomething` method as a side effect when `a` is equal to `b`. The second example is used for the side effect of writing a string to STDOUT. As you learn more about Scala you’ll find yourself writing more *expressions* and fewer *statements*. The differences between expressions and statements will also become more apparent. - - - - - - - diff --git a/_overviews/scala-book/introduction.md b/_overviews/scala-book/introduction.md index 7b417388fb..42bfe49502 100644 --- a/_overviews/scala-book/introduction.md +++ b/_overviews/scala-book/introduction.md @@ -8,6 +8,7 @@ overview-name: Scala Book num: 1 outof: 54 next-page: prelude-taste-of-scala +new-version: /scala3/book/introduction.html --- In these pages, *Scala Book* provides a quick introduction and overview of the Scala programming language. The book is written in an informal style, and consists of more than 50 small lessons. Each lesson is long enough to give you an idea of how the language features in that lesson work, but short enough that you can read it in fifteen minutes or less. @@ -17,10 +18,3 @@ One note before beginning: - In regards to programming style, most Scala programmers indent their code with two spaces, but we use four spaces because we think it makes the code easier to read, especially in a book format. To begin reading, click the “next” link, or select the *Prelude: A Taste of Scala* lesson in the table of contents. - - - - - - - diff --git a/_overviews/scala-book/list-class.md b/_overviews/scala-book/list-class.md index 1a606afc98..568463f1f7 100644 --- a/_overviews/scala-book/list-class.md +++ b/_overviews/scala-book/list-class.md @@ -5,11 +5,11 @@ title: The List Class description: This page provides examples of the Scala List class, including how to add and remove elements from a List. partof: scala_book overview-name: Scala Book -discourse: true num: 30 outof: 54 previous-page: arraybuffer-examples next-page: vector-class +new-version: /scala3/book/collections-classes.html#list --- [The List class](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) is a linear, immutable sequence. All this means is that it’s a linked-list that you can’t modify. Any time you want to add or remove `List` elements, you create a new `List` from an existing `List`. @@ -140,15 +140,3 @@ list: List[Int] = List(1, 2, 3) ``` This works because a `List` is a singly-linked list that ends with the `Nil` element. - - - - - - - - - - - - diff --git a/_overviews/scala-book/map-class.md b/_overviews/scala-book/map-class.md index 4a7d48db98..88efb3eec8 100644 --- a/_overviews/scala-book/map-class.md +++ b/_overviews/scala-book/map-class.md @@ -5,11 +5,11 @@ title: The Map Class description: This page provides examples of the Scala 'Map' class, including how to add and remove elements from a Map, and iterate over Map elements. partof: scala_book overview-name: Scala Book -discourse: true num: 32 outof: 54 previous-page: vector-class next-page: set-class +new-version: /scala3/book/collections-classes.html#maps --- @@ -158,12 +158,3 @@ ratings.foreach { ## See also There are other ways to work with Scala Maps, and a nice collection of Map classes for different needs. See the [Map class documentation]({{site.baseurl}}/overviews/collections-2.13/maps.html) for more information and examples. - - - - - - - - - diff --git a/_overviews/scala-book/match-expressions.md b/_overviews/scala-book/match-expressions.md index 4b54dfb35c..1c19d09c07 100644 --- a/_overviews/scala-book/match-expressions.md +++ b/_overviews/scala-book/match-expressions.md @@ -5,11 +5,11 @@ title: match Expressions description: This page shows examples of the Scala 'match' expression, including how to write match/case expressions. partof: scala_book overview-name: Scala Book -discourse: true num: 17 outof: 54 previous-page: for-expressions next-page: try-catch-finally +new-version: /scala3/book/control-structures.html#match-expressions --- @@ -247,11 +247,3 @@ stock match { ## Even more `match` expressions are very powerful, and there are even more things you can do with them, but hopefully these examples provide a good start towards using them. - - - - - - - - diff --git a/_overviews/scala-book/methods-first-look.md b/_overviews/scala-book/methods-first-look.md index 7e10bdfd77..7a8d8bb71e 100644 --- a/_overviews/scala-book/methods-first-look.md +++ b/_overviews/scala-book/methods-first-look.md @@ -5,11 +5,11 @@ title: A First Look at Scala Methods description: This page provides a first look at how to write Scala methods, including how to test them in the REPL. partof: scala_book overview-name: Scala Book -discourse: true num: 22 outof: 54 previous-page: constructors-default-values next-page: enumerations-pizza-class +new-version: /scala3/book/methods-intro.html --- @@ -104,9 +104,3 @@ If you paste that code into the REPL, you’ll see that it works just like the p scala> addThenDouble(1, 1) res0: Int = 4 ``` - - - - - - diff --git a/_overviews/scala-book/misc.md b/_overviews/scala-book/misc.md index 61c19bd1b2..d7c7b77c89 100644 --- a/_overviews/scala-book/misc.md +++ b/_overviews/scala-book/misc.md @@ -5,11 +5,11 @@ title: A Few Miscellaneous Items description: A few miscellaneous items about Scala partof: scala_book overview-name: Scala Book -discourse: true num: 37 outof: 54 previous-page: collections-maps next-page: tuples +new-version: /scala3/book/introduction.html --- @@ -17,5 +17,3 @@ In this section we’ll cover a few miscellaneous items about Scala: - Tuples - A Scala OOP example of a pizza restaurant order-entry system - - diff --git a/_overviews/scala-book/no-null-values.md b/_overviews/scala-book/no-null-values.md index 757fc62670..66771927f0 100644 --- a/_overviews/scala-book/no-null-values.md +++ b/_overviews/scala-book/no-null-values.md @@ -5,11 +5,11 @@ title: No Null Values description: This lesson demonstrates the Scala Option, Some, and None classes, including how to use them instead of null values. partof: scala_book overview-name: Scala Book -discourse: true num: 47 outof: 54 previous-page: passing-functions-around next-page: companion-objects +new-version: /scala3/book/fp-functional-error-handling.html --- @@ -301,10 +301,3 @@ This lesson was a little longer than the others, so here’s a quick review of t ## See also - Tony Hoare invented the null reference in 1965, and refers to it as his “[billion dollar mistake](https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions).” - - - - - - - diff --git a/_overviews/scala-book/oop-pizza-example.md b/_overviews/scala-book/oop-pizza-example.md index 7d50686e71..a7d11f9ff5 100644 --- a/_overviews/scala-book/oop-pizza-example.md +++ b/_overviews/scala-book/oop-pizza-example.md @@ -5,11 +5,11 @@ title: An OOP Example description: This lesson shares an example of some OOP-style classes for a pizza restaurant order entry system, including Pizza, Topping, and Order classes. partof: scala_book overview-name: Scala Book -discourse: true num: 39 outof: 54 previous-page: tuples next-page: sbt-scalatest-intro +new-version: /scala3/book/domain-modeling-oop.html --- @@ -216,9 +216,4 @@ To experiment with this on your own, please see the *PizzaOopExample* project in - [github.com/alvinj/HelloScalaExamples](https://github.com/alvinj/HelloScalaExamples) -To compile this project it will help to either (a) use IntelliJ IDEA or Eclipse, or (b) know how to use the [Scala Build Tool](http://www.scala-sbt.org). - - - - - +To compile this project it will help to either (a) use IntelliJ IDEA or Metals, or (b) know how to use the [Scala Build Tool](http://www.scala-sbt.org). diff --git a/_overviews/scala-book/passing-functions-around.md b/_overviews/scala-book/passing-functions-around.md index 2700ea06c7..91ca50d198 100644 --- a/_overviews/scala-book/passing-functions-around.md +++ b/_overviews/scala-book/passing-functions-around.md @@ -5,11 +5,11 @@ title: Passing Functions Around description: Like a good functional programming language, Scala lets you use functions just like other variables, including passing them into other functions. partof: scala_book overview-name: Scala Book -discourse: true num: 46 outof: 54 previous-page: pure-functions next-page: no-null-values +new-version: /scala3/book/fp-functions-are-values.html --- @@ -104,11 +104,3 @@ Those examples that use a “regular” function are equivalent to these anonymo List("foo", "bar").map(s => s.toUpperCase) List("foo", "bar").map(_.toUpperCase) ``` - - - - - - - - diff --git a/_overviews/scala-book/preliminaries.md b/_overviews/scala-book/preliminaries.md index e8057e37d9..8308f59818 100644 --- a/_overviews/scala-book/preliminaries.md +++ b/_overviews/scala-book/preliminaries.md @@ -5,11 +5,11 @@ title: Preliminaries description: A few things to know about getting started with Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 3 outof: 54 previous-page: prelude-taste-of-scala next-page: scala-features +new-version: /scala3/book/taste-intro.html#setting-up-scala --- @@ -21,7 +21,7 @@ That being said, there are a few good things to know before you read this book. ## Installing Scala -First, to run the examples in this book you’ll need to install Scala on your computer. See our general [Getting Started]({{site.baseurl}}/getting-started/index.html) page for details on how to use Scala (a) in an IDE and (b) from the command line. +First, to run the examples in this book you’ll need to install Scala on your computer. See our general [Getting Started]({{site.baseurl}}/getting-started/install-scala.html) page for details on how to use Scala (a) in an IDE and (b) from the command line. @@ -45,11 +45,10 @@ One good thing to know up front is that comments in Scala are just like comments ## IDEs -The three main IDEs (integrated development environments) for Scala are: +The two main IDEs (integrated development environments) for Scala are: - [IntelliJ IDEA](https://www.jetbrains.com/idea/download) - [Visual Studio Code](https://code.visualstudio.com) -- [Scala IDE for Eclipse](http://scala-ide.org) @@ -60,12 +59,3 @@ Another good thing to know is that Scala naming conventions follow the same “c - Class names: `Person`, `StoreEmployee` - Variable names: `name`, `firstName` - Method names: `convertToInt`, `toUpper` - - - - - - - - - diff --git a/_overviews/scala-book/prelude-taste-of-scala.md b/_overviews/scala-book/prelude-taste-of-scala.md index 1317561825..970631acf6 100644 --- a/_overviews/scala-book/prelude-taste-of-scala.md +++ b/_overviews/scala-book/prelude-taste-of-scala.md @@ -5,16 +5,16 @@ title: Prelude꞉ A Taste of Scala description: This page shares a Taste Of Scala example, quickly covering Scala's main features. partof: scala_book overview-name: Scala Book -discourse: true num: 2 outof: 54 previous-page: introduction next-page: preliminaries +new-version: /scala3/book/taste-intro.html --- Our hope in this book is to demonstrate that [Scala](http://scala-lang.org) is a beautiful, modern, expressive programming language. To help demonstrate that, in this first chapter we’ll jump right in and provide a whirlwind tour of Scala’s main features. After this tour, the book begins with a more traditional “Getting Started” chapter. ->In this book we assume that you’ve used a language like Java before, and are ready to see a series of Scala examples to get a feel for what the language looks like. Although it’s not 100% necessary, it will also help if you’ve already [downloaded and installed Scala](https://www.scala-lang.org/download) so you can test the examples as you go along. You can also test these examples online with [ScalaFiddle.io](https://scalafiddle.io). +>In this book we assume that you’ve used a language like Java before, and are ready to see a series of Scala examples to get a feel for what the language looks like. Although it’s not 100% necessary, it will also help if you’ve already [downloaded and installed Scala](https://www.scala-lang.org/download) so you can test the examples as you go along. You can also test these examples online with [Scastie](https://scastie.scala-lang.org/). @@ -553,11 +553,3 @@ If you like what you’ve seen so far, we hope you’ll like the rest of the boo ## A bit of background Scala was created by [Martin Odersky](https://en.wikipedia.org/wiki/Martin_Odersky), who studied under [Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth), who created Pascal and several other languages. Mr. Odersky is one of the co-designers of Generic Java, and is also known as the “father” of the `javac` compiler. - - - - - - - - diff --git a/_overviews/scala-book/pure-functions.md b/_overviews/scala-book/pure-functions.md index e753d67ce4..35597bd01a 100644 --- a/_overviews/scala-book/pure-functions.md +++ b/_overviews/scala-book/pure-functions.md @@ -5,11 +5,11 @@ title: Pure Functions description: This lesson provides an introduction to writing pure functions in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 45 outof: 54 previous-page: functional-programming next-page: passing-functions-around +new-version: /scala3/book/fp-pure-functions.html --- @@ -49,7 +49,7 @@ Conversely, the following functions are *impure* because they violate the defini The `foreach` method on collections classes is impure because it’s only used for its side effects, such as printing to STDOUT. ->A great hint that `foreach` is impure is that it’s method signature declares that it returns the type `Unit`. Because it returns nothing, logically the only reason you ever call it is to achieve some side effect. Similarly, *any* method that returns `Unit` is going to be an impure function. +>A great hint that `foreach` is impure is that its method signature declares that it returns the type `Unit`. Because it returns nothing, logically the only reason you ever call it is to achieve some side effect. Similarly, *any* method that returns `Unit` is going to be an impure function. Date and time related methods like `getDayOfWeek`, `getHour`, and `getMinute` are all impure because their output depends on something other than their input parameters. Their results rely on some form of hidden I/O, *hidden input* in these examples. @@ -100,19 +100,3 @@ The first key point of this lesson is the definition of a pure function: >A *pure function* is a function that depends only on its declared inputs and its internal algorithm to produce its output. It does not read any other values from “the outside world” — the world outside of the function’s scope — and it does not modify any values in the outside world. A second key point is that real-world applications consist of a combination of pure and impure functions. A common recommendation is to write the core of your application using pure functions, and then to use impure functions to communicate with the outside world. - - - - - - - - - - - - - - - - diff --git a/_overviews/scala-book/sbt-scalatest-bdd.md b/_overviews/scala-book/sbt-scalatest-bdd.md index ee20a8490e..29ba5e1eb6 100644 --- a/_overviews/scala-book/sbt-scalatest-bdd.md +++ b/_overviews/scala-book/sbt-scalatest-bdd.md @@ -5,11 +5,11 @@ title: Writing BDD Style Tests with ScalaTest and sbt description: This lesson shows how to write ScalaTest unit tests with sbt in a behavior-driven development (TDD) style. partof: scala_book overview-name: Scala Book -discourse: true num: 43 outof: 54 previous-page: sbt-scalatest-tdd next-page: functional-programming +new-version: /scala3/book/tools-sbt.html#using-sbt-with-scalatest --- @@ -45,9 +45,9 @@ Next, create a file named *MathUtilsTests.scala* in the *src/test/scala/simplete ```scala package simpletest -import org.scalatest.FunSpec +import org.scalatest.funspec.AnyFunSpec -class MathUtilsSpec extends FunSpec { +class MathUtilsSpec extends AnyFunSpec { describe("MathUtils::double") { @@ -70,7 +70,7 @@ class MathUtilsSpec extends FunSpec { As you can see, this is a very different-looking style than the TDD tests in the previous lesson. If you’ve never used a BDD style of testing before, a main idea is that the tests should be relatively easy to read for one of the “domain experts” who work with the programmers to create the application. A few notes about this code: -- It uses the `FunSpec` class where the TDD tests used `FunSuite` +- It uses the `AnyFunSpec` class where the TDD tests used `AnyFunSuite` - A set of tests begins with `describe` - Each test begins with `it`. The idea is that the test should read like, “It should do XYZ...,” where “it” is the `double` function - This example also shows how to mark a test as “pending” @@ -96,7 +96,7 @@ With those files in place you can again run `sbt test`. The important part of th [info] Suites: completed 2, aborted 0 [info] Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 1 [info] All tests passed. -[success] Total time: 4 s, completed Jan 6, 2018 4:58:23 PM +[success] Total time: 4 s ```` A few notes about that output: @@ -113,13 +113,5 @@ If you want to have a little fun with this, change one or more of the tests so t For more information about sbt and ScalaTest, see the following resources: -- [The main sbt documentation](http://www.scala-sbt.org/documentation.html) -- [The ScalaTest documentation](http://www.scalatest.org/user_guide) - - - - - - - - +- [The main sbt documentation](https://www.scala-sbt.org/1.x/docs/) +- [The ScalaTest documentation](https://www.scalatest.org/user_guide) diff --git a/_overviews/scala-book/sbt-scalatest-intro.md b/_overviews/scala-book/sbt-scalatest-intro.md index bf40ba8c6c..2c80d06799 100644 --- a/_overviews/scala-book/sbt-scalatest-intro.md +++ b/_overviews/scala-book/sbt-scalatest-intro.md @@ -5,11 +5,11 @@ title: sbt and ScalaTest description: In this lesson we'll start to introduce sbt and ScalaTest, two tools commonly used on Scala projects. partof: scala_book overview-name: Scala Book -discourse: true num: 40 outof: 54 previous-page: oop-pizza-example next-page: scala-build-tool-sbt +new-version: /scala3/book/tools-sbt.html --- @@ -19,5 +19,3 @@ In the next few lessons you’ll see a couple of tools that are commonly used in - [ScalaTest](http://www.scalatest.org), a code testing framework We’ll start by showing how to use sbt, and then you’ll see how to use ScalaTest and sbt together to build and test your Scala projects. - - diff --git a/_overviews/scala-book/sbt-scalatest-tdd.md b/_overviews/scala-book/sbt-scalatest-tdd.md index 7214566a09..dbdbeeb53c 100644 --- a/_overviews/scala-book/sbt-scalatest-tdd.md +++ b/_overviews/scala-book/sbt-scalatest-tdd.md @@ -5,11 +5,11 @@ title: Using ScalaTest with sbt description: This lesson shows how to write ScalaTest unit tests with sbt in a test-driven development (TDD) style. partof: scala_book overview-name: Scala Book -discourse: true num: 42 outof: 54 previous-page: scala-build-tool-sbt next-page: sbt-scalatest-bdd +new-version: /scala3/book/tools-sbt.html#using-sbt-with-scalatest --- @@ -39,7 +39,7 @@ version := "1.0" scalaVersion := "{{site.scala-version}}" libraryDependencies += - "org.scalatest" %% "scalatest" % "3.0.8" % Test + "org.scalatest" %% "scalatest" % "3.2.19" % Test ``` @@ -47,7 +47,7 @@ The first three lines of this file are essentially the same as the first example ```scala libraryDependencies += - "org.scalatest" %% "scalatest" % "3.0.8" % Test + "org.scalatest" %% "scalatest" % "3.2.19" % Test ``` >The ScalaTest documentation has always been good, and you can always find the up to date information on what those lines should look like on the [Installing ScalaTest](http://www.scalatest.org/install) page. @@ -85,8 +85,8 @@ There isn’t much that can go wrong with that source code, but it provides a si [warn] consider launching sbt without any commands, or explicitly passing 'shell' ... ... -[info] Compiling 1 Scala source to /Users/al/Projects/Scala/HelloScalaTest/target/scala-2.12/classes... -[info] Running simpletest.Hello +[info] compiling 1 Scala source to /Users/al/Projects/Scala/HelloScalaTest/target/scala-2.13/classes... +[info] running simpletest.Hello Hello Alvin Alexander [success] Total time: 4 s ```` @@ -108,9 +108,9 @@ Next, create a file named *HelloTests.scala* in that directory with the followin ```scala package simpletest -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite -class HelloTests extends FunSuite { +class HelloTests extends AnyFunSuite { // test 1 test("the name is set correctly in constructor") { @@ -130,7 +130,7 @@ class HelloTests extends FunSuite { This file demonstrates the ScalaTest `FunSuite` approach. A few important points: -- Your class should extend `FunSuite` +- Your class should extend `AnyFunSuite` - You create tests as shown, by giving each `test` a unique name - At the end of each test you should call `assert` to test that a condition has been satisfied @@ -140,7 +140,7 @@ Now you can run these tests with the `sbt test` command. Skipping the first few ```` > sbt test -[info] Set current project to HelloScalaTest (in build file:/Users/al/Projects/Scala/HelloScalaTest/) +[info] set current project to HelloScalaTest (in build file:/Users/al/Projects/Scala/HelloScalaTest/) [info] HelloTests: [info] - the name is set correctly in constructor [info] - a Person's name can be changed @@ -159,11 +159,3 @@ Now you can run these tests with the `sbt test` command. Skipping the first few This example demonstrates a *Test-Driven Development* (TDD) style of testing with ScalaTest. In the next lesson you’ll see how to write *Behavior-Driven Development* (BDD) tests with ScalaTest and sbt. >Keep the project you just created. You’ll use it again in the next lesson. - - - - - - - - diff --git a/_overviews/scala-book/scala-build-tool-sbt.md b/_overviews/scala-book/scala-build-tool-sbt.md index 8986433976..c329d06aa4 100644 --- a/_overviews/scala-book/scala-build-tool-sbt.md +++ b/_overviews/scala-book/scala-build-tool-sbt.md @@ -5,11 +5,11 @@ title: The most used scala build tool (sbt) description: This page provides an introduction to the Scala Build Tool, sbt, including a simple 'Hello, world' project. partof: scala_book overview-name: Scala Book -discourse: true num: 41 outof: 54 previous-page: sbt-scalatest-intro next-page: sbt-scalatest-tdd +new-version: /scala3/book/tools-sbt.html#building-scala-projects-with-sbt --- @@ -161,14 +161,5 @@ Here’s a list of other build tools you can use to build Scala projects: - [Ant](http://ant.apache.org/) - [Gradle](https://gradle.org/) - [Maven](https://maven.apache.org/) -- [Fury](https://propensive.com/opensource/fury) +- [Fury](https://github.com/propensive/fury) - [Mill](https://com-lihaoyi.github.io/mill/) - - - - - - - - - diff --git a/_overviews/scala-book/scala-features.md b/_overviews/scala-book/scala-features.md index eee55bd089..5973f1ea1a 100644 --- a/_overviews/scala-book/scala-features.md +++ b/_overviews/scala-book/scala-features.md @@ -5,19 +5,19 @@ title: Scala Features description: TODO partof: scala_book overview-name: Scala Book -discourse: true num: 4 outof: 54 previous-page: preliminaries next-page: hello-world-1 +new-version: /scala3/book/scala-features.html --- -The name *Scala* comes from the word *scalable*, and true to that name, it’s used to power the busiest websites in the world, including Twitter, Netflix, Tumblr, LinkedIn, Foursquare, and many more. +The name *Scala* comes from the word *scalable*, and true to that name, it’s used to power the busiest websites in the world, including X, Netflix, Tumblr, LinkedIn, Foursquare, and many more. Here are a few more nuggets about Scala: -- It’s a modern programming language created by [Martin Odersky](https://twitter.com/odersky?lang=en) (the father of `javac`), and influenced by Java, Ruby, Smalltalk, ML, Haskell, Erlang, and others. +- It’s a modern programming language created by [Martin Odersky](https://x.com/odersky?lang=en) (the father of `javac`), and influenced by Java, Ruby, Smalltalk, ML, Haskell, Erlang, and others. - It’s a high-level language. - It’s statically typed. - It has a sophisticated type inference system. @@ -28,6 +28,3 @@ Here are a few more nuggets about Scala: - Scala also works extremely well with the thousands of Java libraries that have been developed over the years. - A great thing about Scala is that you can be productive with it on Day 1, but it’s also a deep language, so as you go along you’ll keep learning, and finding newer, better ways to write code. Some people say that Scala will change the way you think about programming (and that’s a good thing). - A great Scala benefit is that it lets you write concise, readable code. The time a programmer spends reading code compared to the time spent writing code is said to be at least a 10:1 ratio, so writing code that’s *concise and readable* is a big deal. Because Scala has these attributes, programmers say that it’s *expressive*. - - - diff --git a/_overviews/scala-book/scala-repl.md b/_overviews/scala-book/scala-repl.md index c1ced1f219..d3227b15b1 100644 --- a/_overviews/scala-book/scala-repl.md +++ b/_overviews/scala-book/scala-repl.md @@ -5,11 +5,11 @@ title: The Scala REPL description: This page shares an introduction to the Scala REPL. partof: scala_book overview-name: Scala Book -discourse: true num: 7 outof: 54 previous-page: hello-world-2 next-page: two-types-variables +new-version: /scala3/book/taste-repl.html --- @@ -72,14 +72,5 @@ In addition to the REPL there are a couple of other, similar tools you can use: - [Scastie](https://scastie.scala-lang.org) is “an interactive playground for Scala” with several nice features, including being able to control build settings and share code snippets - IntelliJ IDEA has a Worksheet plugin that lets you do the same things inside your IDE -- The Scala IDE for Eclipse also has a Worksheet plugin -- [scalafiddle.io](https://scalafiddle.io) lets you run similar experiments in a web browser For more information on the Scala REPL, see the [Scala REPL overview]({{site.baseurl}}/overviews/repl/overview.html) - - - - - - - diff --git a/_overviews/scala-book/set-class.md b/_overviews/scala-book/set-class.md index 55fefb31d0..6123650f6f 100644 --- a/_overviews/scala-book/set-class.md +++ b/_overviews/scala-book/set-class.md @@ -5,11 +5,11 @@ title: The Set Class description: This page provides examples of the Scala 'Set' class, including how to add and remove elements from a Set, and iterate over Set elements. partof: scala_book overview-name: Scala Book -discourse: true num: 33 outof: 54 previous-page: map-class next-page: anonymous-functions +new-version: /scala3/book/collections-classes.html#working-with-sets --- @@ -122,11 +122,3 @@ res3: Boolean = false ## More Sets Scala has several more `Set` classes, including `SortedSet`, `LinkedHashSet`, and more. Please see the [Set class documentation]({{site.baseurl}}/overviews/collections-2.13/sets.html) for more details on those classes. - - - - - - - - diff --git a/_overviews/scala-book/traits-abstract-mixins.md b/_overviews/scala-book/traits-abstract-mixins.md index eeecb67aee..1bcbb87936 100644 --- a/_overviews/scala-book/traits-abstract-mixins.md +++ b/_overviews/scala-book/traits-abstract-mixins.md @@ -5,11 +5,11 @@ title: Using Scala Traits Like Abstract Classes description: This page shows how to use Scala traits just like abstract classes in Java, with examples of concrete and abstract methods. partof: scala_book overview-name: Scala Book -discourse: true num: 26 outof: 54 previous-page: traits-interfaces next-page: abstract-classes +new-version: /scala3/book/domain-modeling-tools.html#traits --- @@ -195,12 +195,3 @@ I'm running ``` This example works because all of the methods in the `TailWagger` and `Runner` traits are defined (they’re not abstract). - - - - - - - - - diff --git a/_overviews/scala-book/traits-interfaces.md b/_overviews/scala-book/traits-interfaces.md index a10ed18a61..1aab8ee4e8 100644 --- a/_overviews/scala-book/traits-interfaces.md +++ b/_overviews/scala-book/traits-interfaces.md @@ -5,11 +5,11 @@ title: Using Scala Traits as Interfaces description: This page shows how to use Scala traits just like Java interfaces, including several examples. partof: scala_book overview-name: Scala Book -discourse: true num: 25 outof: 54 previous-page: traits-intro next-page: traits-abstract-mixins +new-version: /scala3/book/domain-modeling-tools.html#traits --- ## Using Scala Traits as Interfaces @@ -146,10 +146,3 @@ Key points of this code: - Use `with` to extend subsequent traits From what you’ve seen so far, Scala traits work just like Java interfaces. But there’s more ... - - - - - - - diff --git a/_overviews/scala-book/traits-intro.md b/_overviews/scala-book/traits-intro.md index a6d1db0f26..66c7cf99d6 100644 --- a/_overviews/scala-book/traits-intro.md +++ b/_overviews/scala-book/traits-intro.md @@ -5,19 +5,14 @@ title: Scala Traits and Abstract Classes description: An introduction to Scala traits and abstract classes. partof: scala_book overview-name: Scala Book -discourse: true num: 24 outof: 54 previous-page: enumerations-pizza-class next-page: traits-interfaces +new-version: /scala3/book/domain-modeling-tools.html#traits --- Scala traits are a great feature of the language. As you’ll see in the following lessons, you can use them just like a Java interface, and you can also use them like abstract classes that have real methods. Scala classes can also extend and “mix in” multiple traits. Scala also has the concept of an abstract class, and we’ll show when you should use an abstract class instead of a trait. - - - - - diff --git a/_overviews/scala-book/try-catch-finally.md b/_overviews/scala-book/try-catch-finally.md index 5dee7890a4..a9e855cce1 100644 --- a/_overviews/scala-book/try-catch-finally.md +++ b/_overviews/scala-book/try-catch-finally.md @@ -5,11 +5,11 @@ title: try/catch/finally Expressions description: This page shows how to use Scala's try/catch/finally construct, including several complete examples. partof: scala_book overview-name: Scala Book -discourse: true num: 18 outof: 54 previous-page: match-expressions next-page: classes +new-version: /scala3/book/control-structures.html#trycatchfinally --- @@ -58,9 +58,3 @@ catch { ## More later We’ll cover more details about Scala’s try/catch/finally syntax in later lessons, such as in the “Functional Error Handling” lessons, but these examples demonstrate how the syntax works. A great thing about the syntax is that it’s consistent with the `match` expression syntax. This makes your code consistent and easier to read, and you don’t have to remember a special/different syntax. - - - - - - diff --git a/_overviews/scala-book/tuples.md b/_overviews/scala-book/tuples.md index c2eb8e4225..dab29195c8 100644 --- a/_overviews/scala-book/tuples.md +++ b/_overviews/scala-book/tuples.md @@ -5,11 +5,11 @@ title: Tuples description: This page is an introduction to the Scala 'tuple' data type, showing examples of how to use tuples in your Scala code. partof: scala_book overview-name: Scala Book -discourse: true num: 38 outof: 54 previous-page: misc next-page: oop-pizza-example +new-version: /scala3/book/taste-collections.html#tuples --- @@ -115,9 +115,3 @@ For cases like this where it feels like overkill to create a class for the metho ## Tuples aren’t collections Technically, Scala 2.x tuples aren’t collections classes, they’re just a convenient little container. Because they aren’t a collection, they don’t have methods like `map`, `filter`, etc. - - - - - - diff --git a/_overviews/scala-book/two-notes-about-strings.md b/_overviews/scala-book/two-notes-about-strings.md index fdcbd7a29a..31a097f758 100644 --- a/_overviews/scala-book/two-notes-about-strings.md +++ b/_overviews/scala-book/two-notes-about-strings.md @@ -5,11 +5,11 @@ title: Two Notes About Strings description: This page shares two important notes about strings in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 11 outof: 54 previous-page: built-in-types next-page: command-line-io +new-version: /scala3/book/first-look-at-types.html#strings --- @@ -110,6 +110,3 @@ our fathers ... ``` Because this is what you generally want, this is a common way to create multiline strings. - - - diff --git a/_overviews/scala-book/two-types-variables.md b/_overviews/scala-book/two-types-variables.md index 678c89dc3d..3ce00a0e54 100644 --- a/_overviews/scala-book/two-types-variables.md +++ b/_overviews/scala-book/two-types-variables.md @@ -5,11 +5,11 @@ title: Two Types of Variables description: Scala has two types of variables, val and var. partof: scala_book overview-name: Scala Book -discourse: true num: 8 outof: 54 previous-page: scala-repl next-page: type-is-optional +new-version: /scala3/book/taste-vars-data-types.html --- @@ -94,8 +94,7 @@ object Hello3 extends App { As before: - Save that code in a file named *Hello3.scala* -- Compile it with `scalac Hello3.scala` -- Run it with `scala Hello3` +- Compile and run it with `scala run Hello3.scala` @@ -112,12 +111,3 @@ age: Int = 19 ``` `val` fields can’t be redefined like that in the real world, but they can be redefined in the REPL playground. - - - - - - - - - diff --git a/_overviews/scala-book/type-is-optional.md b/_overviews/scala-book/type-is-optional.md index 3b21654433..6a49d6b751 100644 --- a/_overviews/scala-book/type-is-optional.md +++ b/_overviews/scala-book/type-is-optional.md @@ -5,11 +5,11 @@ title: The Type is Optional description: A note about explicit and implicit data type declarations in Scala. partof: scala_book overview-name: Scala Book -discourse: true num: 9 outof: 54 previous-page: two-types-variables next-page: built-in-types +new-version: /scala3/book/taste-vars-data-types.html#declaring-variable-types --- @@ -56,13 +56,3 @@ val p: Person = new Person("Candy") // unnecessarily verbose ## Use the explicit form when you need to be clear One place where you’ll want to show the data type is when you want to be clear about what you’re creating. That is, if you don’t explicitly declare the data type, the compiler may make a wrong assumption about what you want to create. Some examples of this are when you want to create numbers with specific data types. We show this in the next lesson. - - - - - - - - - - diff --git a/_overviews/scala-book/vector-class.md b/_overviews/scala-book/vector-class.md index 15981e7904..7da81e3125 100644 --- a/_overviews/scala-book/vector-class.md +++ b/_overviews/scala-book/vector-class.md @@ -5,11 +5,11 @@ title: The Vector Class description: This page provides examples of the Scala 'Vector' class, including how to add and remove elements from a Vector. partof: scala_book overview-name: Scala Book -discourse: true num: 31 outof: 54 previous-page: list-class next-page: map-class +new-version: /scala3/book/collections-classes.html#vector --- [The Vector class](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) is an indexed, immutable sequence. The “indexed” part of the description means that you can access `Vector` elements very rapidly by their index value, such as accessing `listOfPeople(999999)`. @@ -96,11 +96,3 @@ Joel Chris Ed ``` - - - - - - - - diff --git a/_overviews/scala-book/where-next.md b/_overviews/scala-book/where-next.md index 4b045d7182..9210e690e7 100644 --- a/_overviews/scala-book/where-next.md +++ b/_overviews/scala-book/where-next.md @@ -5,14 +5,12 @@ title: Where To Go Next description: Where to go next after reading the Scala Book partof: scala_book overview-name: Scala Book -discourse: true num: 54 outof: 54 previous-page: futures +new-version: /scala3/book/where-next.html --- We hope you enjoyed this introduction to the Scala programming language, and we also hope we were able to share some of the beauty of the language. As you continue working with Scala, you can find many more details at the [Guides and Overviews section]({{site.baseurl}}/overviews/index.html) of our website. - - diff --git a/_overviews/scala3-book/ca-context-bounds.md b/_overviews/scala3-book/ca-context-bounds.md index b42f71e56d..d4346ed94c 100644 --- a/_overviews/scala3-book/ca-context-bounds.md +++ b/_overviews/scala3-book/ca-context-bounds.md @@ -1,49 +1,123 @@ --- title: Context Bounds type: section -description: This page demonstrates Context Bounds in Scala 3. -num: 61 -previous-page: types-type-classes +description: This page demonstrates Context Bounds in Scala. +languages: [ru, zh-cn] +num: 63 +previous-page: ca-context-parameters next-page: ca-given-imports --- - -{% comment %} -- TODO: define "context parameter" -- TODO: define "synthesized" and "synthesized arguments" -{% endcomment %} - -In many situations the name of a _context parameter_ doesn’t have to be mentioned explicitly, since it’s only used by the compiler in synthesized arguments for other context parameters. +In many situations the name of a [context parameter]({% link _overviews/scala3-book/ca-context-parameters.md %}#context-parameters) does not have to be mentioned explicitly, since it is only used by the compiler in synthesized arguments for other context parameters. In that case you don’t have to define a parameter name, and can just provide the parameter type. ## Background -For example, this `maximum` method takes a _context parameter_ of type `Ord`, only to pass it on as an argument to `max`: +For example, consider a method `maxElement` that returns the maximum value in a collection: + +{% tabs context-bounds-max-named-param class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)(ord)) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def maxElement[A](as: List[A])(using ord: Ord[A]): A = + as.reduceLeft(max(_, _)(using ord)) +``` +{% endtab %} + +{% endtabs %} + +The method `maxElement` takes a _context parameter_ of type `Ord[A]` only to pass it on as an argument to the method +`max`. + +For the sake of completeness, here are the definitions of `max` and `Ord` (note that in practice we would use the +existing method `max` on `List`, but we made up this example for illustration purpose): + +{% tabs context-bounds-max-ord class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +/** Defines how to compare values of type `A` */ +trait Ord[A] { + def greaterThan(a1: A, a2: A): Boolean +} + +/** Returns the maximum of two values */ +def max[A](a1: A, a2: A)(implicit ord: Ord[A]): A = + if (ord.greaterThan(a1, a2)) a1 else a2 +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -def maximum[A](xs: List[A])(using ord: Ord[A]): A = - xs.reduceLeft(max(ord)) +/** Defines how to compare values of type `A` */ +trait Ord[A]: + def greaterThan(a1: A, a2: A): Boolean + +/** Returns the maximum of two values */ +def max[A](a1: A, a2: A)(using ord: Ord[A]): A = + if ord.greaterThan(a1, a2) then a1 else a2 ``` +{% endtab %} + +{% endtabs %} + +Note that the method `max` takes a context parameter of type `Ord[A]`, like the method `maxElement`. -In that code the parameter name `ord` isn’t actually required; it can be passed on as an inferred argument to `max`, so you just state that `maximum` uses the type `Ord[A]` without giving it a name: +## Omitting context arguments +Since `ord` is a context parameter in the method `max`, the compiler can supply it for us in the implementation of `maxElement`, +when we call the method `max`: + +{% tabs context-bounds-context class=tabs-scala-version %} + +{% tab 'Scala 2' %} ```scala -def maximum[A](xs: List[A])(using Ord[A]): A = - xs.reduceLeft(max) +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)) ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def maxElement[A](as: List[A])(using Ord[A]): A = + as.reduceLeft(max(_, _)) +``` + +Note that, because we don’t need to explicitly pass it to the method `max`, we can leave out its name in the definition +of the method `maxElement`. This is an _anonymous context parameter_. +{% endtab %} + +{% endtabs %} ## Context bounds -Given that background, a _context bound_ is a shorthand syntax for expressing the pattern of, “a context parameter that depends on a type parameter.” +Given that background, a _context bound_ is a shorthand syntax for expressing the pattern of, “a context parameter applied to a type parameter.” + +Using a context bound, the `maxElement` method can be written like this: + +{% tabs context-bounds-max-rewritten %} -Using a context bound, the `maximum` method can be written like this: +{% tab 'Scala 2 and 3' %} ```scala -def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) +def maxElement[A: Ord](as: List[A]): A = + as.reduceLeft(max(_, _)) ``` -A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with `Ord[A]`. +{% endtab %} + +{% endtabs %} + + +A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with type `Ord[A]`. +Under the hood, the compiler transforms this syntax into the one shown in the Background section. -For more information about context bounds, see the [“What are context bounds?”](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html) section of the Scala FAQ. +For more information about context bounds, see the [“What are context bounds?”]({% link _overviews/FAQ/index.md %}#what-are-context-bounds) section of the Scala FAQ. diff --git a/_overviews/scala3-book/ca-context-parameters.md b/_overviews/scala3-book/ca-context-parameters.md new file mode 100644 index 0000000000..3da62d4b3b --- /dev/null +++ b/_overviews/scala3-book/ca-context-parameters.md @@ -0,0 +1,157 @@ +--- +title: Context Parameters +type: section +description: This page demonstrates how to declare context parameters, and how the compiler infers them at call-site. +languages: [ru, zh-cn] +num: 62 +previous-page: ca-extension-methods +next-page: ca-context-bounds +redirect_from: /scala3/book/ca-given-using-clauses.html +--- + +Scala offers two important features for contextual abstraction: + +- **Context Parameters** allow you to specify parameters that, at the call-site, can be omitted by the programmer and should be automatically provided by the context. +- **Given Instances** (in Scala 3) or **Implicit Definitions** (in Scala 2) are terms that can be used by the Scala compiler to fill in the missing arguments. + +## Context Parameters + +When designing a system, often context information like _configuration_ or settings need to be provided to the different components of your system. +One common way to achieve this is by passing the configuration as an additional argument (or arguments) to your methods. + +In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods. + +{% tabs example %} +{% tab 'Scala 2 and 3' %} +```scala +case class Config(port: Int, baseUrl: String) + +def renderWebsite(path: String, config: Config): String = + "<html>" + renderWidget(List("cart"), config) + "</html>" + +def renderWidget(items: List[String], config: Config): String = ??? + +val config = Config(8080, "docs.scala-lang.org") +renderWebsite("/home", config) +``` +{% endtab %} +{% endtabs %} + +Let us assume that the configuration does not change throughout most of our code base. +Passing `config` to each and every method call (like `renderWidget`) becomes very tedious and makes our program more difficult to read, since we need to ignore the `config` argument. + +### Marking parameters as contextual + +We can mark some parameters of our methods as _contextual_. + +{% tabs 'contextual-parameters' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def renderWebsite(path: String)(implicit config: Config): String = + "<html>" + renderWidget(List("cart")) + "</html>" + // ^ + // no argument config required anymore + +def renderWidget(items: List[String])(implicit config: Config): String = ??? +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def renderWebsite(path: String)(using config: Config): String = + "<html>" + renderWidget(List("cart")) + "</html>" + // ^ + // no argument config required anymore + +def renderWidget(items: List[String])(using config: Config): String = ??? +``` +{% endtab %} +{% endtabs %} + +By starting a parameter section with the keyword `using` in Scala 3 or `implicit` in Scala 2, we tell the compiler that at the call-site it should automatically find an argument with the correct type. +The Scala compiler thus performs **term inference**. + +In our call to `renderWidget(List("cart"))` the Scala compiler will see that there is a term of type `Config` in scope (the `config`) and automatically provide it to `renderWidget`. +So the program is equivalent to the one above. + +In fact, since we do not need to refer to `config` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature in Scala 3: + +{% tabs 'anonymous' %} +{% tab 'Scala 3 Only' %} +```scala +// no need to come up with a parameter name +// vvvvvvvvvvvvv +def renderWebsite(path: String)(using Config): String = + "<html>" + renderWidget(List("cart")) + "</html>" +``` +{% endtab %} +{% endtabs %} + +In Scala 2, the name of implicit parameters is still mandatory. + +### Explicitly providing contextual arguments + +We have seen how to _abstract_ over contextual parameters and that the Scala compiler can provide arguments automatically for us. +But how can we specify which configuration to use for our call to `renderWebsite`? + +{% tabs 'explicit' class=tabs-scala-version %} +{% tab 'Scala 2' %} +We explicitly supply the argument value as if it was a regular argument: +```scala +renderWebsite("/home")(config) +``` +{% endtab %} +{% tab 'Scala 3' %} +Like we specified our parameter section with `using`, we can also explicitly provide contextual arguments with `using`: +```scala +renderWebsite("/home")(using config) +``` +{% endtab %} +{% endtabs %} + +Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense, and we want to make sure that the correct one is passed to the function. + +For all other cases, as we will see in the next section, there is also another way to bring contextual values into scope. + +## Given Instances (Implicit Definitions in Scala 2) + +We have seen that we can explicitly pass arguments as contextual parameters. +However, if there is _a single canonical value_ for a particular type, there is another preferred way to make it available to the Scala compiler: by marking it as `given` in Scala 3 or `implicit` in Scala 2. + +{% tabs 'instances' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +implicit val config: Config = Config(8080, "docs.scala-lang.org") +// ^^^^^^ +// this is the value the Scala compiler will infer +// as argument to contextual parameters of type Config +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +val config = Config(8080, "docs.scala-lang.org") + +// this is the type that we want to provide the +// canonical value for +// vvvvvv +given Config = config +// ^^^^^^ +// this is the value the Scala compiler will infer +// as argument to contextual parameters of type Config +``` +{% endtab %} +{% endtabs %} + +In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument. + +Having defined a canonical value for the type `Config`, we can call `renderWebsite` as follows: + +```scala +renderWebsite("/home") +// ^ +// again no argument +``` + +A detailed guide to where Scala looks for canonical values can be found in [the FAQ]({% link _overviews/FAQ/index.md %}#where-does-scala-look-for-implicits). + +[reference]: {{ site.scala3ref }}/overview.html +[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html diff --git a/_overviews/scala3-book/ca-contextual-abstractions-intro.md b/_overviews/scala3-book/ca-contextual-abstractions-intro.md index d95a9bfc54..8f7f5f79af 100644 --- a/_overviews/scala3-book/ca-contextual-abstractions-intro.md +++ b/_overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -2,16 +2,16 @@ title: Contextual Abstractions type: chapter description: This chapter provides an introduction to the Scala 3 concept of Contextual Abstractions. -num: 58 +languages: [ru, zh-cn] +num: 60 previous-page: types-others -next-page: ca-given-using-clauses +next-page: ca-extension-methods --- ## Background -Implicits in Scala 2 were a major distinguishing design feature. -They are *the* fundamental way to abstract over context. +Contextual abstractions are a way to abstract over context. They represent a unified paradigm with a great variety of use cases, among them: - Implementing type classes @@ -20,36 +20,36 @@ They represent a unified paradigm with a great variety of use cases, among them: - Expressing capabilities - Computing new types, and proving relationships between them -Since then, other languages have followed suit, e.g., Rust’s traits or Swift’s protocol extensions. +Other languages have been influenced by Scala in this regard. E.g., Rust’s traits or Swift’s protocol extensions. Design proposals are also on the table for Kotlin as compile time dependency resolution, for C# as Shapes and Extensions or for F# as Traits. -Implicits are also a common feature of theorem provers such as Coq or Agda. +Contextual abstractions are also a common feature of theorem provers such as Coq or Agda. -Even though these designs use different terminology, they’re all variants of the core idea of *term inference*: -Given a type, the compiler synthesizes a “canonical” term that has that type. +Even though these designs use different terminology, they’re all variants of the core idea of **term inference**: given a type, the compiler synthesizes a “canonical” term that has that type. +## Scala 3 Redesign -## Redesign +In Scala 2, contextual abstractions are supported by marking definitions (methods and values) or parameters as `implicit` (see [Context Parameters]({% link _overviews/scala3-book/ca-context-parameters.md %})). -Scala 3 includes a redesign of contextual abstractions in Scala. +Scala 3 includes a redesign of contextual abstractions. While these concepts were gradually “discovered” in Scala 2, they’re now well known and understood, and the redesign takes advantage of that knowledge. The design of Scala 3 focuses on **intent** rather than **mechanism**. Instead of offering one very powerful feature of implicits, Scala 3 offers several use-case oriented features: -- **Abtracting over contextual information**. +- **Retroactively extending classes**. + In Scala 2, extension methods are encoded by using [implicit conversions][implicit-conversions] or [implicit classes]({% link _overviews/core/implicit-classes.md %}). + In contrast, in Scala 3 [extension methods][extension-methods] are now directly built into the language, leading to better error messages and improved type inference. + +- **Abstracting over contextual information**. [Using clauses][givens] allow programmers to abstract over information that is available in the calling context and should be passed implicitly. As an improvement over Scala 2 implicits, using clauses can be specified by type, freeing function signatures from term variable names that are never explicitly referred to. - **Providing Type-class instances**. - [Given instances][type-classes] allow programmers to define the _canonical value_ of a certain type. - This makes programming with type-classes more straightforward without leaking implementation details. - -- **Retroactively extending classes**. - In Scala 2, extension methods had to be encoded using implicit conversions or implicit classes. - In contrast, in Scala 3 [extension methods][extension-methods] are now directly built into the language, leading to better error messages and improved type inference. + [Given instances][givens] allow programmers to define the _canonical value_ of a certain type. + This makes programming with [type-classes][type-classes] more straightforward without leaking implementation details. - **Viewing one type as another**. - Implicit conversion have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`. + Implicit conversions have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`. - **Higher-order contextual abstractions**. The _all-new_ feature of [context functions][contextual-functions] makes contextual abstractions a first-class citizen. @@ -77,11 +77,11 @@ Benefits of these changes include: This chapter introduces many of these new features in the following sections. -[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} [given-imports]: {% link _overviews/scala3-book/ca-given-imports.md %} [implicit-conversions]: {% link _overviews/scala3-book/ca-implicit-conversions.md %} [extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} [context-bounds]: {% link _overviews/scala3-book/ca-context-bounds.md %} [type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} [equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} -[contextual-functions]: {% link _overviews/scala3-book/types-dependent-function.md %} +[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html diff --git a/_overviews/scala3-book/ca-extension-methods.md b/_overviews/scala3-book/ca-extension-methods.md index b066aea99e..49f07b45be 100644 --- a/_overviews/scala3-book/ca-extension-methods.md +++ b/_overviews/scala3-book/ca-extension-methods.md @@ -2,42 +2,71 @@ title: Extension Methods type: section description: This page demonstrates how Extension Methods work in Scala 3. -num: 63 -previous-page: ca-given-imports -next-page: ca-type-classes +languages: [ru, zh-cn] +num: 61 +previous-page: ca-contextual-abstractions-intro +next-page: ca-context-parameters +scala3: true +versionSpecific: true --- +In Scala 2, a similar result could be achieved with [implicit classes]({% link _overviews/core/implicit-classes.md %}). + +--- Extension methods let you add methods to a type after the type is defined, i.e., they let you add new methods to closed classes. For example, imagine that someone else has created a `Circle` class: +{% tabs ext1 %} +{% tab 'Scala 2 and 3' %} ```scala case class Circle(x: Double, y: Double, radius: Double) ``` +{% endtab %} +{% endtabs %} Now imagine that you need a `circumference` method, but you can’t modify their source code. Before the concept of term inference was introduced into programming languages, the only thing you could do was write a method in a separate class or object like this: +{% tabs ext2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object CircleHelpers { + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala object CircleHelpers: def circumference(c: Circle): Double = c.radius * math.Pi * 2 ``` +{% endtab %} +{% endtabs %} Then you’d use that method like this: +{% tabs ext3 %} +{% tab 'Scala 2 and 3' %} ```scala val aCircle = Circle(2, 3, 5) // without extension methods CircleHelpers.circumference(aCircle) ``` +{% endtab %} +{% endtabs %} But with extension methods you can create a `circumference` method to work on `Circle` instances: +{% tabs ext4 %} +{% tab 'Scala 3 Only' %} ```scala extension (c: Circle) def circumference: Double = c.radius * math.Pi * 2 ``` +{% endtab %} +{% endtabs %} In this code: @@ -46,19 +75,27 @@ In this code: Then in your code you use `circumference` just as though it was originally defined in the `Circle` class: +{% tabs ext5 %} +{% tab 'Scala 3 Only' %} ```scala aCircle.circumference ``` +{% endtab %} +{% endtabs %} ### Import extension method Imagine, that `circumference` is defined in package `lib`, you can import it by +{% tabs ext6 %} +{% tab 'Scala 3 Only' %} ```scala import lib.circumference aCircle.circumference ``` +{% endtab %} +{% endtabs %} The compiler also supports you if the import is missing by showing a detailed compilation error message such as the following: @@ -75,11 +112,15 @@ The following import might fix the problem: The `extension` keyword declares that you’re about to define one or more extension methods on the type that’s put in parentheses. To define multiple extension methods on a type, use this syntax: +{% tabs ext7 %} +{% tab 'Scala 3 Only' %} ```scala extension (c: Circle) def circumference: Double = c.radius * math.Pi * 2 def diameter: Double = c.radius * 2 def area: Double = math.Pi * c.radius * c.radius ``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/scala3-book/ca-given-imports.md b/_overviews/scala3-book/ca-given-imports.md index 6cfb436a29..bc7c0754f4 100644 --- a/_overviews/scala3-book/ca-given-imports.md +++ b/_overviews/scala3-book/ca-given-imports.md @@ -2,9 +2,12 @@ title: Given Imports type: section description: This page demonstrates how 'given' import statements work in Scala 3. -num: 62 +languages: [ru, zh-cn] +num: 64 previous-page: ca-context-bounds -next-page: ca-extension-methods +next-page: ca-type-classes +scala3: true +versionSpecific: true --- @@ -31,7 +34,6 @@ object B: import A.{given, *} ``` - ## Discussion The wildcard selector `*` brings all definitions other than givens or extensions into scope, whereas a `given` selector brings all *givens*---including those resulting from extensions---into scope. diff --git a/_overviews/scala3-book/ca-given-using-clauses.md b/_overviews/scala3-book/ca-given-using-clauses.md deleted file mode 100644 index 704f83bd82..0000000000 --- a/_overviews/scala3-book/ca-given-using-clauses.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Given Instances and Using Clauses -type: section -description: This page demonstrates how to use 'given' instances and 'using' clauses in Scala 3. -num: 59 -previous-page: ca-contextual-abstractions-intro -next-page: types-type-classes ---- - -Scala 3 offers two important feature for contextual abstraction: - -- **Using Clauses** allow you to specify parameters that, at the call site, can be omitted by the programmer and should be automatically provided by the context. -- **Given Instances** let you define terms that can be used by the Scala compiler to fill in the missing arguments. - -## Using Clauses -When designing a system, often context information like _configuration_ or settings need to be provided to the different components of your system. -One common way to achieve this is by passing the configuration as additional argument to your methods. - -In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods. -```scala -case class Config(port: Int, baseUrl: String) - -def renderWebsite(path: String, c: Config): String = - "<html>" + renderWidget(List("cart"), c) + "</html>" - -def renderWidget(items: List[String], c: Config): String = ??? - -val config = Config(8080, "docs.scala-lang.org") -renderWebsite("/home", config) -``` -Let us assume that the configuration does not change throughout most of our code base. -Passing `c` to each and every method call (like `renderWidget`) becomes very tedious and makes our program more difficult to read, since we need to ignore the `c` argument. - -#### Using `using` to mark parameters as contextual -In Scala 3, we can mark some of the parameters of our methods as _contextual_. -```scala -def renderWebsite(path: String)(using c: Config): String = - "<html>" + renderWidget(List("cart")) + "</html>" - // ^^^ - // no argument c required anymore - -def renderWidget(items: List[String])(using c: Config): String = ??? -``` -By starting a parameter section with the keyword `using`, we tell the Scala compiler that at the callsite it should automatically find an argument with the correct type. -The Scala compiler thus performs **term inference**. - -In our call to `renderWidget(List("cart"))` the Scala compiler will see that there is a term of type `Config` in scope (the `c`) and automatically provide it to `renderWidget`. -So the program is equivalent to the one above. - -In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature: - -```scala -// no need to come up with a parameter name -// vvvvvvvvvvvvv -def renderWebsite(path: String)(using Config): String = - "<html>" + renderWidget(List("cart")) + "</html>" -``` - -#### Explicitly providing contextual arguments -We have seen how to _abstract_ over contextual parameters and that the Scala compiler can provide arguments automatically for us. -But how can we specify which configuration to use for our call to `renderWebsite`? - -Like we specified our parameter section with `using`, we can also explicitly provide contextual arguments with `using:` - -```scala -renderWebsite("/home")(using config) -``` -Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense and we want to make sure that the correct one is passed to the function. - -For all other cases, as we will see in the next Section, there is also another way to bring contextual values into scope. - -## Given Instances -We have seen that we can explicitly pass arguments as contextual parameters by marking the argument section of the _call_ with `using`. -However, if there is _a single canonical value_ for a particular type, there is another preferred way to make it available to the Scala compiler: by marking it as `given`. - -```scala -val config = Config(8080, "docs.scala-lang.org") -// this is the type that we want to provide the -// canonical value for -// vvvvvv -given Config = config -// ^^^^^^ -// this is the value the Scala compiler will infer -// as argument to contextual parameters of type Config -``` -In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument. - -Having defined a given for `Config`, we can simply call `renderWebsite`: - -```scala -renderWebsite("/home") -// ^^^^^ -// again no argument -``` - -[reference]: {{ site.scala3ref }}/overview.html -[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html diff --git a/_overviews/scala3-book/ca-implicit-conversions.md b/_overviews/scala3-book/ca-implicit-conversions.md index d04cefa104..2c2884aa56 100644 --- a/_overviews/scala3-book/ca-implicit-conversions.md +++ b/_overviews/scala3-book/ca-implicit-conversions.md @@ -1,48 +1,223 @@ --- title: Implicit Conversions type: section -description: This page demonstrates how to implement Implicit Conversions in Scala 3. -num: 66 +description: This page demonstrates how to implement Implicit Conversions in Scala. +languages: [ru, zh-cn] +num: 67 previous-page: ca-multiversal-equality next-page: ca-summary --- +Implicit conversions are a powerful Scala feature that allows users to supply an argument +of one type as if it were another type, to avoid boilerplate. -Implicit conversions are defined by `given` instances of the `scala.Conversion` class. -For example, not accounting for possible conversion errors, this code defines an implicit conversion from `String` to `Int`: +> Note that in Scala 2, implicit conversions were also used to provide additional members +> to closed classes (see [Implicit Classes]({% link _overviews/core/implicit-classes.md %})). +> In Scala 3, we recommend to address this use-case by defining [extension methods] instead +> of implicit conversions (although the standard library still relies on implicit conversions +> for historical reasons). + +## Example + +Consider for instance a method `findUserById` that takes a parameter of type `Long`: + +{% tabs implicit-conversions-1 %} +{% tab 'Scala 2 and 3' %} +~~~ scala +def findUserById(id: Long): Option[User] +~~~ +{% endtab %} +{% endtabs %} + +We omit the definition of the type `User` for the sake of brevity, it does not matter for +our example. + +In Scala, it is possible to call the method `findUserById` with an argument of type `Int` +instead of the expected type `Long`, because the argument will be implicitly converted +into the type `Long`: + +{% tabs implicit-conversions-2 %} +{% tab 'Scala 2 and 3' %} +~~~ scala +val id: Int = 42 +findUserById(id) // OK +~~~ +{% endtab %} +{% endtabs %} + +This code does not fail to compile with an error like “type mismatch: expected `Long`, +found `Int`” because there is an implicit conversion that converts the argument `id` +to a value of type `Long`. + +## Detailed Explanation + +This section describes how to define and use implicit conversions. + +### Defining an Implicit Conversion + +{% tabs implicit-conversions-3 class=tabs-scala-version %} + +{% tab 'Scala 2' %} +In Scala 2, an implicit conversion from type `S` to type `T` is defined by an +[implicit class]({% link _overviews/core/implicit-classes.md %}) `T` that takes +a single constructor parameter of type `S`, an +[implicit value]({% link _overviews/scala3-book/ca-context-parameters.md %}) of +function type `S => T`, or by an implicit method convertible to a value of that type. + +For example, the following code defines an implicit conversion from `Int` to `Long`: + +~~~ scala +import scala.language.implicitConversions + +implicit def int2long(x: Int): Long = x.toLong +~~~ + +This is an implicit method convertible to a value of type `Int => Long`. + +See the section “Beware the Power of Implicit Conversions” below for an +explanation of the clause `import scala.language.implicitConversions` +at the beginning. +{% endtab %} + +{% tab 'Scala 3' %} +In Scala 3, an implicit conversion from type `S` to type `T` is defined by a +[`given` instance]({% link _overviews/scala3-book/ca-context-parameters.md %}) +of type `scala.Conversion[S, T]`. For compatibility with Scala 2, it can also +be defined by an implicit method (read more in the Scala 2 tab). + +For example, this code defines an implicit conversion from `Int` to `Long`: ```scala -given Conversion[String, Int] with - def apply(s: String): Int = Integer.parseInt(s) +given int2long: Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong ``` -Using an alias this can be expressed more concisely as: +Like other given definitions, implicit conversions can be anonymous: + +~~~ scala +given Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong +~~~ + +Using an alias, this can be expressed more concisely as: ```scala -given Conversion[String, Int] = Integer.parseInt(_) +given Conversion[Int, Long] = (x: Int) => x.toLong ``` +{% endtab %} -Using either of those conversions, you can now use a `String` in places where an `Int` is expected: +{% endtabs %} -```scala +### Using an Implicit Conversion + +Implicit conversions are applied in two situations: + +1. If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`. +2. In a selection `e.m` with `e` of type `S`, if the selector `m` does not denote a member of `S` + (to support Scala-2-style [extension methods]). + +In the first case, a conversion `c` is searched for, which is applicable to `e` and whose result type conforms to `T`. + +In our example above, when we pass the argument `id` of type `Int` to the method `findUserById`, +the implicit conversion `int2long(id)` is inserted. + +In the second case, a conversion `c` is searched for, which is applicable to `e` and whose result contains a member named `m`. + +An example is to compare two strings `"foo" < "bar"`. In this case, `String` has no member `<`, so the implicit conversion `Predef.augmentString("foo") < "bar"` is inserted. (`scala.Predef` is automatically imported into all Scala programs.) + +### How Are Implicit Conversions Brought Into Scope? + +When the compiler searches for applicable conversions: + +- first, it looks into the current lexical scope + - implicit conversions defined in the current scope or the outer scopes + - imported implicit conversions + - implicit conversions imported by a wildcard import (Scala 2 only) +- then, it looks into the [companion objects] _associated_ with the argument + type `S` or the expected type `T`. The companion objects associated with + a type `X` are: + - the companion object `X` itself + - the companion objects associated with any of `X`’s inherited types + - the companion objects associated with any type argument in `X` + - if `X` is an inner class, the outer objects in which it is embedded + +For instance, consider an implicit conversion `fromStringToUser` defined in an +object `Conversions`: + +{% tabs implicit-conversions-4 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala import scala.language.implicitConversions -// a method that expects an Int -def plus1(i: Int) = i + 1 +object Conversions { + implicit def fromStringToUser(name: String): User = User(name) +} +~~~ +{% endtab %} +{% tab 'Scala 3' %} +~~~ scala +object Conversions: + given fromStringToUser: Conversion[String, User] = (name: String) => User(name) +~~~ +{% endtab %} +{% endtabs %} -// pass it a String that converts to an Int -plus1("1") -``` +The following imports would equivalently bring the conversion into scope: -> Note the clause `import scala.language.implicitConversions` at the beginning, -> to enable implicit conversions in the file. +{% tabs implicit-conversions-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala +import Conversions.fromStringToUser +// or +import Conversions._ +~~~ +{% endtab %} +{% tab 'Scala 3' %} +~~~ scala +import Conversions.fromStringToUser +// or +import Conversions.given +// or +import Conversions.{given Conversion[String, User]} +~~~ -## Discussion +Note that in Scala 3, a wildcard import (ie `import Conversions.*`) does not import given +definitions. +{% endtab %} +{% endtabs %} -The Predef package contains “auto-boxing” conversions that map primitive number types to subclasses of `java.lang.Number`. -For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: +In the introductory example, the conversion from `Int` to `Long` does not require an import +because it is defined in the object `Int`, which is the companion object of the type `Int`. -```scala -given int2Integer: Conversion[Int, java.lang.Integer] = - java.lang.Integer.valueOf(_) -``` +Further reading: +[Where does Scala look for implicits? (on Stackoverflow)](https://stackoverflow.com/a/5598107). + +### Beware the Power of Implicit Conversions + +{% tabs implicit-conversions-6 class=tabs-scala-version %} +{% tab 'Scala 2' %} +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. +{% endtab %} +{% tab 'Scala 3' %} +Because implicit conversions can have pitfalls if used indiscriminately the compiler warns in two situations: +- when compiling a Scala 2 style implicit conversion definition. +- at the call site where a given instance of `scala.Conversion` is inserted as a conversion. + +To turn off the warnings take either of these actions: + +- Import `scala.language.implicitConversions` into the scope of: + - a Scala 2 style implicit conversion definition + - call sites where a given instance of `scala.Conversion` is inserted as a conversion. +- Invoke the compiler with `-language:implicitConversions` +{% endtab %} +{% endtabs %} + +[extension methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[companion objects]: {% link _overviews/scala3-book/domain-modeling-tools.md %}#companion-objects diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md index a106ed0856..dfc6b4cdb0 100644 --- a/_overviews/scala3-book/ca-multiversal-equality.md +++ b/_overviews/scala3-book/ca-multiversal-equality.md @@ -2,12 +2,14 @@ title: Multiversal Equality type: section description: This page demonstrates how to implement Multiversal Equality in Scala 3. -num: 65 +languages: [ru, zh-cn] +num: 66 previous-page: ca-type-classes next-page: ca-implicit-conversions +scala3: true +versionSpecific: true --- - Previously, Scala had *universal equality*: Two values of any types could be compared with each other using `==` and `!=`. This came from the fact that `==` and `!=` are implemented in terms of Java’s `equals` method, which can also compare values of any two reference types. @@ -173,14 +175,12 @@ case class AudioBook( // override to allow AudioBook to be compared to PrintedBook override def equals(that: Any): Boolean = that match case a: AudioBook => - if this.author == a.author + this.author == a.author && this.title == a.title && this.year == a.year && this.lengthInMinutes == a.lengthInMinutes - then true else false case p: PrintedBook => - if this.author == p.author && this.title == p.title - then true else false + this.author == p.author && this.title == p.title case _ => false ``` @@ -192,7 +192,7 @@ println(aBook == pBook) // true (works because of `equals` in `AudioBook`) println(pBook == aBook) // false ``` -Currently the `PrintedBook` book doesn’t have an `equals` method, so the second comparison returns `false`. +Currently, the `PrintedBook` book doesn’t have an `equals` method, so the second comparison returns `false`. To enable that comparison, just override the `equals` method in `PrintedBook`. You can find additional information on [multiversal equality][ref-equal] in the reference documentation. diff --git a/_overviews/scala3-book/ca-summary.md b/_overviews/scala3-book/ca-summary.md index 8815df2009..bdd8c58537 100644 --- a/_overviews/scala3-book/ca-summary.md +++ b/_overviews/scala3-book/ca-summary.md @@ -2,23 +2,27 @@ title: Summary type: section description: This page provides a summary of the Contextual Abstractions lessons. -num: 67 +languages: [ru, zh-cn] +num: 68 previous-page: ca-implicit-conversions next-page: concurrency --- This chapter provides an introduction to most Contextual Abstractions topics, including: -- Given Instances and Using Clauses -- Context Bounds -- Given Imports -- Extension Methods -- Implementing Type Classes -- Multiversal Equality -- Implicit Conversions +- [Extension Methods]({% link _overviews/scala3-book/ca-extension-methods.md %}) +- [Given Instances and Using Clauses]({% link _overviews/scala3-book/ca-context-parameters.md %}) +- [Context Bounds]({% link _overviews/scala3-book/ca-context-bounds.md %}) +- [Given Imports]({% link _overviews/scala3-book/ca-given-imports.md %}) +- [Type Classes]({% link _overviews/scala3-book/ca-type-classes.md %}) +- [Multiversal Equality]({% link _overviews/scala3-book/ca-multiversal-equality.md %}) +- [Implicit Conversions]({% link _overviews/scala3-book/ca-implicit-conversions.md %}) + +These features are all variants of the core idea of **term inference**: given a type, the compiler synthesizes a “canonical” term that has that type. A few more advanced topics aren’t covered here, including: +- Conditional Given Instances - Type Class Derivation - Context Functions - By-Name Context Parameters @@ -27,4 +31,4 @@ A few more advanced topics aren’t covered here, including: Those topics are discussed in detail in the [Reference documentation][ref]. -[ref]: {{ site.scala3ref }}/contextual.html +[ref]: {{ site.scala3ref }}/contextual diff --git a/_overviews/scala3-book/ca-type-classes.md b/_overviews/scala3-book/ca-type-classes.md index ee4cc4d1d7..2a56a5de47 100644 --- a/_overviews/scala3-book/ca-type-classes.md +++ b/_overviews/scala3-book/ca-type-classes.md @@ -1,82 +1,134 @@ --- -title: Implementing Type Classes +title: Type Classes type: section -description: This page demonstrates how to create and use type classes in Scala 3. -num: 64 -previous-page: ca-extension-methods +description: This page demonstrates how to create and use type classes. +languages: [ru, zh-cn] +num: 65 +previous-page: ca-given-imports next-page: ca-multiversal-equality +redirect_from: /scala3/book/types-type-classes.html --- - A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. -This is useful in multiple use-cases, for example: +If you are coming from Java, you can think of type classes as something like [`java.util.Comparator[T]`][comparator]. -- Expressing how a type you don’t own---from the standard library or a third-party library---conforms to such behavior -- Expressing such a behavior for multiple types without involving sub-typing relationships between those types +> The paper [“Type Classes as Objects and Implicits”][typeclasses-paper] (2010) by Oliveira et al. discusses the basic ideas behind type classes in Scala. +> Even though the paper uses an older version of Scala, the ideas still hold to the current day. -In Scala 3, type classes are just traits with one or more parameters whose implementations are provided by `given` instances. +A type class is useful in multiple use-cases, for example: +- Expressing how a type you don’t own---from the standard library or a third-party library---conforms to such behavior +- Expressing such a behavior for multiple types without involving sub-typing relationships between those types +Type classes are traits with one or more parameters whose implementations are provided as `given` instances in Scala 3 or `implicit` values in Scala 2. ## Example -For example, `Show` is a well-known type class in Haskell, and the following code shows one way to implement it in Scala 3. -If you imagine that Scala classes don’t have a `toString` method, you can define a `Show` type class to add this behavior to any class that you want to be able to convert to a custom string. +For example, `Show` is a well-known type class in Haskell, and the following code shows one way to implement it in Scala. +If you imagine that Scala classes don’t have a `toString` method, you can define a `Show` type class to add this behavior to any type that you want to be able to convert to a custom string. ### The type class The first step in creating a type class is to declare a parameterized trait that has one or more abstract methods. Because `Showable` only has one method named `show`, it’s written like this: +{% tabs 'definition' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +// a type class +trait Showable[A] { + def show(a: A): String +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala // a type class trait Showable[A]: - extension(a: A) def show: String + extension (a: A) def show: String ``` +{% endtab %} +{% endtabs %} -This is the Scala 3 way of saying that any type that implements this trait must define how the `show` method works. -Notice that the syntax is very close to a normal trait: +Notice that this approach is close to the usual object-oriented approach, where you would typically define a trait `Show` as follows: +{% tabs 'trait' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +// a trait +trait Show { + def show: String +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala // a trait trait Show: def show: String ``` +{% endtab %} +{% endtabs %} There are a few important things to point out: -1. Type-classes like `Showable` take a type parameter `A` to say which type we provide the implementation of `show` for; in contrast, normal traits like `Show` do not. -2. To add the show functionality to a certain type `A`, the normal trait requires that `A extends Show`, while for type-classes we require to have an implementation of `Showable[A]`. -3. To allow the same method calling syntax in both `Showable` that mimics the one of `Show`, we define `Showable.show` as an extension method. +1. Type-classes like `Showable` take a type parameter `A` to say which type we provide the implementation of `show` for; in contrast, classic traits like `Show` do not. +2. To add the show functionality to a certain type `A`, the classic trait requires that `A extends Show`, while for type-classes we require to have an implementation of `Showable[A]`. +3. In Scala 3, to allow the same method calling syntax in both `Showable` that mimics the one of `Show`, we define `Showable.show` as an extension method. ### Implement concrete instances The next step is to determine what classes in your application `Showable` should work for, and then implement that behavior for them. For instance, to implement `Showable` for this `Person` class: +{% tabs 'person' %} +{% tab 'Scala 2 and 3' %} ```scala case class Person(firstName: String, lastName: String) ``` +{% endtab %} +{% endtabs %} -you’ll define a `given` value for `Showable[Person]`. -This code provides a concrete instance of `Showable` for the `Person` class: +you’ll define a single _canonical value_ of type `Showable[Person]`, ie an instance of `Showable` for the type `Person`, as the following code example demonstrates: +{% tabs 'instance' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +implicit val showablePerson: Showable[Person] = new Showable[Person] { + def show(p: Person): String = + s"${p.firstName} ${p.lastName}" +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala given Showable[Person] with - extension(p: Person) def show: String = + extension (p: Person) def show: String = s"${p.firstName} ${p.lastName}" ``` - -As shown, this is defined as an extension method on the `Person` class, and it uses the reference `p` inside the body of the `show` method. +{% endtab %} +{% endtabs %} ### Using the type class Now you can use this type class like this: +{% tabs 'usage' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val person = Person("John", "Doe") +println(showablePerson.show(person)) +``` + +Note that in practice, type classes are typically used with values whose type is unknown, unlike the type `Person`, as shown in the next section. +{% endtab %} +{% tab 'Scala 3' %} ```scala val person = Person("John", "Doe") println(person.show) ``` +{% endtab %} +{% endtabs %} Again, if Scala didn’t have a `toString` method available to every class, you could use this technique to add `Showable` behavior to any class that you want to be able to convert to a `String`. @@ -84,27 +136,53 @@ Again, if Scala didn’t have a `toString` method available to every class, you As with inheritance, you can define methods that use `Showable` as a type parameter: +{% tabs 'method' class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -def showAll[S: Showable](xs: List[S]): Unit = - xs.foreach(x => println(x.show)) +def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit = + as.foreach(a => println(showable.show(a))) showAll(List(Person("Jane"), Person("Mary"))) ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def showAll[A: Showable](as: List[A]): Unit = + as.foreach(a => println(a.show)) + +showAll(List(Person("Jane"), Person("Mary"))) +``` +{% endtab %} +{% endtabs %} ### A type class with multiple methods Note that if you want to create a type class that has multiple methods, the initial syntax looks like this: +{% tabs 'multiple-methods' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait HasLegs[A] { + def walk(a: A): Unit + def run(a: A): Unit +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala trait HasLegs[A]: extension (a: A) def walk(): Unit def run(): Unit ``` +{% endtab %} +{% endtabs %} ### A real-world example For a real-world example of how type classes are used in Scala 3, see the `CanEqual` discussion in the [Multiversal Equality section][multiversal]. - +[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf +[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} +[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html [multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} diff --git a/_overviews/scala3-book/collections-classes.md b/_overviews/scala3-book/collections-classes.md index e9b7adec45..acf3a7ff87 100644 --- a/_overviews/scala3-book/collections-classes.md +++ b/_overviews/scala3-book/collections-classes.md @@ -2,7 +2,8 @@ title: Collections Types type: section description: This page introduces the common Scala 3 collections types and some of their methods. -num: 37 +languages: [ru, zh-cn] +num: 39 previous-page: collections-intro next-page: collections-methods --- @@ -55,7 +56,7 @@ And this figure shows all collections in package _scala.collection.mutable_: ![Mutable collection hierarchy][collections3] -Having seen that detailed view of all of the collections types, the following sections introduce some of the common types you’ll use on a regular basis. +Having seen that detailed view of all the collections types, the following sections introduce some common types you’ll use on a regular basis. {% comment %} NOTE: those images come from this page: https://docs.scala-lang.org/overviews/collections-2.13/overview.html @@ -126,6 +127,9 @@ Any time you want to add or remove `List` elements, you create a new `List` from This is how you create an initial `List`: +{% tabs list-creation %} + +{% tab 'Scala 2 and 3' %} ```scala val ints = List(1, 2, 3) val names = List("Joel", "Chris", "Ed") @@ -133,36 +137,72 @@ val names = List("Joel", "Chris", "Ed") // another way to construct a List val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil ``` +{% endtab %} + +{% endtabs %} + You can also declare the `List`’s type, if you prefer, though it generally isn’t necessary: +{% tabs list-type %} + +{% tab 'Scala 2 and 3' %} ```scala val ints: List[Int] = List(1, 2, 3) val names: List[String] = List("Joel", "Chris", "Ed") ``` +{% endtab %} + +{% endtabs %} + One exception is when you have mixed types in a collection; in that case you may want to explicitly specify its type: +{% tabs list-mixed-types class=tabs-scala-version %} + +{% tab 'Scala 2' %} ```scala val things: List[Any] = List(1, "two", 3.0) ``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val things: List[String | Int | Double] = List(1, "two", 3.0) // with union types +val thingsAny: List[Any] = List(1, "two", 3.0) // with any +``` +{% endtab %} + +{% endtabs %} ### Adding elements to a List Because `List` is immutable, you can’t add new elements to it. -Instead you create a new list by prepending or appending elements to an existing `List`. +Instead, you create a new list by prepending or appending elements to an existing `List`. For instance, given this `List`: +{% tabs adding-elements-init %} + +{% tab 'Scala 2 and 3' %} ```scala val a = List(1, 2, 3) ``` +{% endtab %} + +{% endtabs %} When working with a `List`, _prepend_ one element with `::`, and prepend another `List` with `:::`, as shown here: +{% tabs adding-elements-example %} + +{% tab 'Scala 2 and 3' %} ```scala val b = 0 :: a // List(0, 1, 2, 3) val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) ``` +{% endtab %} + +{% endtabs %} You can also _append_ elements to a `List`, but because `List` is a singly-linked list, you should generally only prepend elements to it; appending elements to it is a relatively slow operation, especially when you work with large sequences. @@ -177,15 +217,27 @@ If you have a large collection and want to access elements by their index, use a These days IDEs help us out tremendously, but one way to remember those method names is to think that the `:` character represents the side that the sequence is on, so when you use `+:` you know that the list needs to be on the right, like this: +{% tabs list-prepending %} + +{% tab 'Scala 2 and 3' %} ```scala 0 +: a ``` +{% endtab %} + +{% endtabs %} Similarly, when you use `:+` you know the list needs to be on the left: +{% tabs list-appending %} + +{% tab 'Scala 2 and 3' %} ```scala a :+ 4 ``` +{% endtab %} + +{% endtabs %} There are more technical ways to think about this, but this can be a helpful way to remember the method names. @@ -201,24 +253,58 @@ You can also use non-symbolic method names to append and prepend elements, if yo Given a `List` of names: +{% tabs list-loop-init %} + +{% tab 'Scala 2 and 3' %} ```scala val names = List("Joel", "Chris", "Ed") ``` +{% endtab %} + +{% endtabs %} you can print each string like this: +{% tabs list-loop-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for (name <- names) println(name) +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala for name <- names do println(name) ``` +{% endtab %} + +{% endtabs %} This is what it looks like in the REPL: +{% tabs list-loop-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala scala> for name <- names do println(name) Joel Chris Ed ``` +{% endtab %} + +{% endtabs %} + A great thing about using `for` loops with collections is that Scala is consistent, and the same approach works with all sequences, including `Array`, `ArrayBuffer`, `List`, `Seq`, `Vector`, `Map`, `Set`, etc. @@ -227,22 +313,40 @@ A great thing about using `for` loops with collections is that Scala is consiste For those interested in a little bit of history, the Scala `List` is similar to the `List` from [the Lisp programming language](https://en.wikipedia.org/wiki/Lisp_(programming_language)), which was originally specified in 1958. Indeed, in addition to creating a `List` like this: +{% tabs list-history-init %} + +{% tab 'Scala 2 and 3' %} ```scala val ints = List(1, 2, 3) ``` +{% endtab %} + +{% endtabs %} you can also create the exact same list this way: +{% tabs list-history-init2 %} + +{% tab 'Scala 2 and 3' %} ```scala val list = 1 :: 2 :: 3 :: Nil ``` +{% endtab %} + +{% endtabs %} The REPL shows how this works: +{% tabs list-history-repl %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val list = 1 :: 2 :: 3 :: Nil list: List[Int] = List(1, 2, 3) ``` +{% endtab %} + +{% endtabs %} This works because a `List` is a singly-linked list that ends with the `Nil` element, and `::` is a `List` method that works like Lisp’s “cons” operator. @@ -254,20 +358,32 @@ It’s called “lazy”---or non-strict---because it computes its elements only You can see how lazy a `LazyList` is in the REPL: +{% tabs lazylist-example %} + +{% tab 'Scala 2 and 3' %} ```scala val x = LazyList.range(1, Int.MaxValue) x.take(1) // LazyList(<not computed>) x.take(5) // LazyList(<not computed>) x.map(_ + 1) // LazyList(<not computed>) ``` +{% endtab %} + +{% endtabs %} In all of those examples, nothing happens. Indeed, nothing will happen until you force it to happen, such as by calling its `foreach` method: -```` +{% tabs lazylist-evaluation-example %} + +{% tab 'Scala 2 and 3' %} +```scala scala> x.take(1).foreach(println) 1 -```` +``` +{% endtab %} + +{% endtabs %} For more information on the uses, benefits, and drawbacks of strict and non-strict (lazy) collections, see the “strict” and “non-strict” discussions on the [The Architecture of Scala 2.13’s Collections][strict] page. @@ -286,6 +402,9 @@ In general, except for the difference that (a) `Vector` is indexed and `List` is Here are a few ways you can create a `Vector`: +{% tabs vector-creation %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = Vector(1, 2, 3, 4, 5) @@ -298,24 +417,39 @@ val people = Vector( Person("Grover") ) ``` +{% endtab %} + +{% endtabs %} Because `Vector` is immutable, you can’t add new elements to it. -Instead you create a new sequence by appending or prepending elements to an existing `Vector`. +Instead, you create a new sequence by appending or prepending elements to an existing `Vector`. These examples show how to _append_ elements to a `Vector`: +{% tabs vector-appending %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Vector(1,2,3) // Vector(1, 2, 3) val b = a :+ 4 // Vector(1, 2, 3, 4) val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5) ``` +{% endtab %} + +{% endtabs %} This is how you _prepend_ elements: +{% tabs vector-prepending %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Vector(1,2,3) // Vector(1, 2, 3) val b = 0 +: a // Vector(0, 1, 2, 3) val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3) ``` +{% endtab %} + +{% endtabs %} In addition to fast random access and updates, `Vector` provides fast append and prepend times, so you can use these features as desired. @@ -323,6 +457,21 @@ In addition to fast random access and updates, `Vector` provides fast append and Finally, you use a `Vector` in a `for` loop just like a `List`, `ArrayBuffer`, or any other sequence: +{% tabs vector-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala scala> val names = Vector("Joel", "Chris", "Ed") val names: Vector[String] = Vector(Joel, Chris, Ed) @@ -332,40 +481,63 @@ Joel Chris Ed ``` +{% endtab %} +{% endtabs %} ## ArrayBuffer Use `ArrayBuffer` when you need a general-purpose, mutable indexed sequence in your Scala applications. -It’s mutable so you can change its elements, and also resize it. +It’s mutable, so you can change its elements, and also resize it. Because it’s indexed, random access of elements is fast. ### Creating an ArrayBuffer To use an `ArrayBuffer`, first import it: +{% tabs arraybuffer-import %} + +{% tab 'Scala 2 and 3' %} ```scala import scala.collection.mutable.ArrayBuffer ``` +{% endtab %} + +{% endtabs %} If you need to start with an empty `ArrayBuffer`, just specify its type: +{% tabs arraybuffer-creation %} + +{% tab 'Scala 2 and 3' %} ```scala var strings = ArrayBuffer[String]() var ints = ArrayBuffer[Int]() var people = ArrayBuffer[Person]() ``` +{% endtab %} + +{% endtabs %} If you know the approximate size your `ArrayBuffer` eventually needs to be, you can create it with an initial size: +{% tabs list-creation-with-size %} + +{% tab 'Scala 2 and 3' %} ```scala // ready to hold 100,000 ints val buf = new ArrayBuffer[Int](100_000) ``` +{% endtab %} + +{% endtabs %} To create a new `ArrayBuffer` with initial elements, just specify its initial elements, just like a `List` or `Vector`: +{% tabs arraybuffer-init %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = ArrayBuffer(1, 2, 3) val people = ArrayBuffer( @@ -374,6 +546,9 @@ val people = ArrayBuffer( Person("Grover") ) ``` +{% endtab %} + +{% endtabs %} ### Adding elements to an ArrayBuffer @@ -382,33 +557,51 @@ Or if you prefer methods with textual names you can also use `append`, `appendAl Here are some examples of `+=` and `++=`: +{% tabs arraybuffer-add %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) nums += 4 // ArrayBuffer(1, 2, 3, 4) nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) ``` +{% endtab %} + +{% endtabs %} ### Removing elements from an ArrayBuffer `ArrayBuffer` is mutable, so it has methods like `-=`, `--=`, `clear`, `remove`, and more. These examples demonstrate the `-=` and `--=` methods: +{% tabs arraybuffer-remove %} + +{% tab 'Scala 2 and 3' %} ```scala val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) a -= 'a' // ArrayBuffer(b, c, d, e, f, g) a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g) a --= Set('d', 'e') // ArrayBuffer(f, g) ``` +{% endtab %} + +{% endtabs %} ### Updating ArrayBuffer elements Update elements in an `ArrayBuffer` by either reassigning the desired element, or use the `update` method: +{% tabs arraybuffer-update %} + +{% tab 'Scala 2 and 3' %} ```scala val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4) a(2) = 50 // ArrayBuffer(1, 2, 50, 4) a.update(0, 10) // ArrayBuffer(10, 2, 50, 4) ``` +{% endtab %} + +{% endtabs %} @@ -421,6 +614,9 @@ Scala has both mutable and immutable `Map` types, and this section demonstrates Create an immutable `Map` like this: +{% tabs map-init %} + +{% tab 'Scala 2 and 3' %} ```scala val states = Map( "AK" -> "Alaska", @@ -428,37 +624,76 @@ val states = Map( "AZ" -> "Arizona" ) ``` +{% endtab %} + +{% endtabs %} Once you have a `Map` you can traverse its elements in a `for` loop like this: +{% tabs map-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala for (k, v) <- states do println(s"key: $k, value: $v") ``` +{% endtab %} + +{% endtabs %} The REPL shows how this works: -```` +{% tabs map-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for ((k, v) <- states) println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala scala> for (k, v) <- states do println(s"key: $k, value: $v") key: AK, value: Alaska key: AL, value: Alabama key: AZ, value: Arizona -```` +``` +{% endtab %} + +{% endtabs %} ### Accessing Map elements Access map elements by specifying the desired key value in parentheses: +{% tabs map-access-element %} + +{% tab 'Scala 2 and 3' %} ```scala val ak = states("AK") // ak: String = Alaska val al = states("AL") // al: String = Alabama ``` +{% endtab %} + +{% endtabs %} -In practice you’ll also use methods like `keys`, `keySet`, `keysIterator`, `for` loops, and higher-order functions like `map` to work with `Map` keys and values. +In practice, you’ll also use methods like `keys`, `keySet`, `keysIterator`, `for` loops, and higher-order functions like `map` to work with `Map` keys and values. ### Adding elements to a Map Add elements to an immutable map using `+` and `++`, remembering to assign the result to a new variable: +{% tabs map-add-element %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Map(1 -> "one") // a: Map(1 -> one) val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) @@ -468,11 +703,17 @@ val c = b ++ Seq( ) // c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) ``` +{% endtab %} + +{% endtabs %} ### Removing elements from a Map Remove elements from an immutable map using `-` or `--` and the key values to remove, remembering to assign the result to a new variable: +{% tabs map-remove-element %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Map( 1 -> "one", @@ -484,11 +725,17 @@ val a = Map( val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three) val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two) ``` +{% endtab %} + +{% endtabs %} ### Updating Map elements To update elements in an immutable map, use the `updated` method (or the `+` operator) while assigning the result to a new variable: +{% tabs map-update-element %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Map( 1 -> "one", @@ -499,11 +746,30 @@ val a = Map( val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!) val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three) ``` +{% endtab %} + +{% endtabs %} ### Traversing a Map As shown earlier, this is a common way to manually traverse elements in a map using a `for` loop: + +{% tabs map-traverse class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val states = Map( "AK" -> "Alaska", @@ -513,6 +779,9 @@ val states = Map( for (k, v) <- states do println(s"key: $k, value: $v") ``` +{% endtab %} + +{% endtabs %} That being said, there are _many_ ways to work with the keys and values in a map. Common `Map` methods include `foreach`, `map`, `keys`, and `values`. @@ -533,28 +802,46 @@ This section demonstrates the _immutable_ `Set`. Create new empty sets like this: +{% tabs set-creation %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = Set[Int]() val letters = Set[Char]() ``` +{% endtab %} + +{% endtabs %} Create sets with initial data like this: +{% tabs set-init %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') ``` +{% endtab %} + +{% endtabs %} ### Adding elements to a Set Add elements to an immutable `Set` using `+` and `++`, remembering to assign the result to a new variable: +{% tabs set-add-element %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Set(1, 2) // Set(1, 2) val b = a + 3 // Set(1, 2, 3) val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4) ``` +{% endtab %} + +{% endtabs %} Notice that when you attempt to add duplicate elements, they’re quietly dropped. @@ -565,11 +852,17 @@ Also notice that the order of iteration of the elements is arbitrary. Remove elements from an immutable set using `-` and `--`, again assigning the result to a new variable: +{% tabs set-remove-element %} + +{% tab 'Scala 2 and 3' %} ```scala val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4) val b = a - 5 // HashSet(1, 2, 3, 4) val c = b -- Seq(3, 4) // HashSet(1, 2) ``` +{% endtab %} + +{% endtabs %} @@ -582,39 +875,76 @@ These REPL examples demonstrate how to create ranges: LATER: the dotty repl currently shows results differently {% endcomment %} +{% tabs range-init %} + +{% tab 'Scala 2 and 3' %} ```scala 1 to 5 // Range(1, 2, 3, 4, 5) 1 until 5 // Range(1, 2, 3, 4) 1 to 10 by 2 // Range(1, 3, 5, 7, 9) 'a' to 'c' // NumericRange(a, b, c) ``` +{% endtab %} + +{% endtabs %} You can use ranges to populate collections: +{% tabs range-conversion %} + +{% tab 'Scala 2 and 3' %} ```scala val x = (1 to 5).toList // List(1, 2, 3, 4, 5) val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5) ``` +{% endtab %} + +{% endtabs %} They’re also used in `for` loops: -```` +{% tabs range-iteration class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (i <- 1 to 3) println(i) +1 +2 +3 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala scala> for i <- 1 to 3 do println(i) 1 2 3 -```` +``` +{% endtab %} + +{% endtabs %} + There are also `range` methods on : +{% tabs range-methods %} + +{% tab 'Scala 2 and 3' %} ```scala Vector.range(1, 5) // Vector(1, 2, 3, 4) List.range(1, 10, 2) // List(1, 3, 5, 7, 9) Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4) ``` +{% endtab %} + +{% endtabs %} When you’re running tests, ranges are also useful for generating test collections: +{% tabs range-tests %} + +{% tab 'Scala 2 and 3' %} ```scala val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10) val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9) @@ -624,6 +954,9 @@ val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0) val map = (1 to 3).map(e => (e,s"$e")).toMap // map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3") ``` +{% endtab %} + +{% endtabs %} ## More details diff --git a/_overviews/scala3-book/collections-intro.md b/_overviews/scala3-book/collections-intro.md index 7ecf0a835e..e953b95302 100644 --- a/_overviews/scala3-book/collections-intro.md +++ b/_overviews/scala3-book/collections-intro.md @@ -2,7 +2,8 @@ title: Scala Collections type: chapter description: This page provides and introduction to the common collections classes and their methods in Scala 3. -num: 36 +languages: [ru, zh-cn] +num: 38 previous-page: packaging-imports next-page: collections-classes --- diff --git a/_overviews/scala3-book/collections-methods.md b/_overviews/scala3-book/collections-methods.md index 4316dea761..6a56814b5c 100644 --- a/_overviews/scala3-book/collections-methods.md +++ b/_overviews/scala3-book/collections-methods.md @@ -2,7 +2,8 @@ title: Collections Methods type: section description: This page demonstrates the common methods on the Scala 3 collections classes. -num: 38 +languages: [ru, zh-cn] +num: 40 previous-page: collections-classes next-page: collections-summary --- @@ -36,6 +37,9 @@ The following methods work on all of the sequence types, including `List`, `Vect To give you an overview of what you’ll see in the following sections, these examples show some of the most commonly used collections methods. First, here are some methods that don’t use lambdas: +{% tabs common-method-examples %} + +{% tab 'Scala 2 and 3' %} ```scala val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) @@ -53,6 +57,9 @@ a.tail // List(20, 30, 40, 10) a.take(3) // List(10, 20, 30) a.takeRight(2) // List(40, 10) ``` +{% endtab %} + +{% endtabs %} ### Higher-order functions and lambdas @@ -60,6 +67,9 @@ a.takeRight(2) // List(40, 10) Next, we’ll show some commonly used higher-order functions (HOFs) that accept lambdas (anonymous functions). To get started, here are several variations of the lambda syntax, starting with the longest form, working in steps towards the most concise form: +{% tabs higher-order-functions-example %} + +{% tab 'Scala 2 and 3' %} ```scala // these functions are all equivalent and return // the same data: List(10, 20, 10) @@ -69,6 +79,9 @@ a.filter((i) => i < 25) // 2. `Int` is not required a.filter(i => i < 25) // 3. the parens are not required a.filter(_ < 25) // 4. `i` is not required ``` +{% endtab %} + +{% endtabs %} In those numbered examples: @@ -76,12 +89,15 @@ In those numbered examples: This much verbosity is _rarely_ required, and only needed in the most complex usages. 2. The compiler knows that `a` contains `Int`, so it’s not necessary to restate that here. 3. Parentheses aren’t needed when you have only one parameter, such as `i`. -4. When you have a single parameter and it appears only once in your anonymous function, you can replace the parameter with `_`. +4. When you have a single parameter, and it appears only once in your anonymous function, you can replace the parameter with `_`. The [Anonymous Function][lambdas] provides more details and examples of the rules related to shortening lambda expressions. Now that you’ve seen the concise form, here are examples of other HOFs that use the short-form lambda syntax: +{% tabs anonymous-functions-example %} + +{% tab 'Scala 2 and 3' %} ```scala a.dropWhile(_ < 25) // List(30, 40, 10) a.filter(_ > 100) // List() @@ -89,11 +105,17 @@ a.filterNot(_ < 25) // List(30, 40) a.find(_ > 20) // Some(30) a.takeWhile(_ < 30) // List(10, 20) ``` +{% endtab %} + +{% endtabs %} It’s important to note that HOFs also accept methods and functions as parameters---not just lambda expressions. Here are some examples of the `map` HOF that uses a method named `double`. Several variations of the lambda syntax are shown again: +{% tabs method-as-parameter-example %} + +{% tab 'Scala 2 and 3' %} ```scala def double(i: Int) = i * 2 @@ -102,17 +124,26 @@ a.map(i => double(i)) a.map(double(_)) a.map(double) ``` +{% endtab %} + +{% endtabs %} In the last example, when an anonymous function consists of one function call that takes a single argument, you don’t have to name the argument, so even `_` isn’t required. Finally, you can combine HOFs as desired to solve problems: +{% tabs higher-order-functions-combination-example %} + +{% tab 'Scala 2 and 3' %} ```scala // yields `List(100, 200)` a.filter(_ < 40) .takeWhile(_ < 30) .map(_ * 10) ``` +{% endtab %} + +{% endtabs %} @@ -120,10 +151,16 @@ a.filter(_ < 40) The examples in the following sections use these lists: +{% tabs sample-data %} + +{% tab 'Scala 2 and 3' %} ```scala val oneToTen = (1 to 10).toList val names = List("adam", "brandy", "chris", "david") ``` +{% endtab %} + +{% endtabs %} @@ -134,22 +171,37 @@ it then returns a new list with all of the modified elements. Here’s an example of the `map` method being applied to the `oneToTen` list: +{% tabs map-example %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val doubles = oneToTen.map(_ * 2) doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) ``` +{% endtab %} + +{% endtabs %} You can also write anonymous functions using a long form, like this: +{% tabs map-example-anonymous %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val doubles = oneToTen.map(i => i * 2) doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) ``` +{% endtab %} + +{% endtabs %} However, in this lesson we’ll always use the first, shorter form. Here are a few more examples of the `map` method being applied to the `oneToTen` and `names` lists: +{% tabs few-more-examples %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val capNames = names.map(_.capitalize) capNames: List[String] = List(Adam, Brandy, Chris, David) @@ -160,6 +212,9 @@ nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david scala> val isLessThanFive = oneToTen.map(_ < 5) isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) ``` +{% endtab %} + +{% endtabs %} As shown in the last two examples, it’s perfectly legal (and common) to use `map` to return a collection that has a different type than the original type. @@ -171,6 +226,9 @@ The `filter` method creates a new list containing the element that satisfy the p A predicate, or condition, is a function that returns a `Boolean` (`true` or `false`). Here are a few examples: +{% tabs filter-example %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val lessThanFive = oneToTen.filter(_ < 5) lessThanFive: List[Int] = List(1, 2, 3, 4) @@ -181,20 +239,35 @@ evens: List[Int] = List(2, 4, 6, 8, 10) scala> val shortNames = names.filter(_.length <= 4) shortNames: List[String] = List(adam) ``` +{% endtab %} + +{% endtabs %} A great thing about the functional methods on collections is that you can chain them together to solve problems. For instance, this example shows how to chain `filter` and `map`: +{% tabs filter-example-anonymous %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.filter(_ < 4).map(_ * 10) ``` +{% endtab %} + +{% endtabs %} The REPL shows the result: +{% tabs filter-example-anonymous-repl %} + +{% tab 'Scala 2 and 3' %} ```scala scala> oneToTen.filter(_ < 4).map(_ * 10) val res1: List[Int] = List(10, 20, 30) ``` +{% endtab %} + +{% endtabs %} @@ -204,6 +277,9 @@ The `foreach` method is used to loop over all elements in a collection. Note that `foreach` is used for side-effects, such as printing information. Here’s an example with the `names` list: +{% tabs foreach-example %} + +{% tab 'Scala 2 and 3' %} ```scala scala> names.foreach(println) adam @@ -211,6 +287,9 @@ brandy chris david ``` +{% endtab %} + +{% endtabs %} @@ -219,33 +298,57 @@ david The `head` method comes from Lisp and other earlier functional programming languages. It’s used to access the first element (the head element) of a list: +{% tabs head-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.head // 1 names.head // adam ``` +{% endtab %} + +{% endtabs %} Because a `String` can be seen as a sequence of characters, you can also treat it like a list. This is how `head` works on these strings: +{% tabs string-head-example %} + +{% tab 'Scala 2 and 3' %} ```scala "foo".head // 'f' "bar".head // 'b' ``` +{% endtab %} + +{% endtabs %} `head` is a great method to work with, but as a word of caution it can also throw an exception when called on an empty collection: +{% tabs head-error-example %} + +{% tab 'Scala 2 and 3' %} ```scala val emptyList = List[Int]() // emptyList: List[Int] = List() emptyList.head // java.util.NoSuchElementException: head of empty list ``` +{% endtab %} + +{% endtabs %} Because of this you may want to use `headOption` instead of `head`, especially when programming in a functional style: +{% tabs head-option-example %} + +{% tab 'Scala 2 and 3' %} ```scala emptyList.headOption // None ``` +{% endtab %} -As shown, it doesn’t throw an exception, it simply returns the type `Option` that has the value `None`. +{% endtabs %} + +As shown, it doesn't throw an exception, it simply returns the type `Option` that has the value `None`. You can learn more about this programming style in the [Functional Programming][fp-intro] chapter. @@ -255,6 +358,9 @@ You can learn more about this programming style in the [Functional Programming][ The `tail` method also comes from Lisp, and it’s used to print every element in a list after the head element. A few examples demonstrate this: +{% tabs tail-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.head // 1 oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -262,37 +368,73 @@ oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) names.head // adam names.tail // List(brandy, chris, david) ``` +{% endtab %} + +{% endtabs %} Just like `head`, `tail` also works on strings: +{% tabs string-tail-example %} + +{% tab 'Scala 2 and 3' %} ```scala "foo".tail // "oo" "bar".tail // "ar" ``` +{% endtab %} -`tail` throws an _java.lang.UnsupportedOperationException_ if the list is empty, so just like `head` and `headOption`, there’s also a `tailOption` method, which is preferred in functional programming. +{% endtabs %} + +`tail` throws a _java.lang.UnsupportedOperationException_ if the list is empty, so just like `head` and `headOption`, there’s also a `tailOption` method, which is preferred in functional programming. A list can also be matched, so you can write expressions like this: +{% tabs tail-match-example %} + +{% tab 'Scala 2 and 3' %} ```scala val x :: xs = names ``` +{% endtab %} + +{% endtabs %} Putting that code in the REPL shows that `x` is assigned to the head of the list, and `xs` is assigned to the tail: +{% tabs tail-match-example-repl %} + +{% tab 'Scala 2 and 3' %} ```scala scala> val x :: xs = names val x: String = adam val xs: List[String] = List(brandy, chris, david) ``` +{% endtab %} + +{% endtabs %} Pattern matching like this is useful in many situations, such as writing a `sum` method using recursion: +{% tabs tail-match-sum-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(list: List[Int]): Int = list match { + case Nil => 0 + case x :: xs => x + sum(xs) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala def sum(list: List[Int]): Int = list match case Nil => 0 case x :: xs => x + sum(xs) ``` +{% endtab %} + +{% endtabs %} @@ -301,6 +443,9 @@ def sum(list: List[Int]): Int = list match The `take`, `takeRight`, and `takeWhile` methods give you a nice way of “taking” the elements from a list that you want to use to create a new list. This is `take` and `takeRight`: +{% tabs take-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.take(1) // List(1) oneToTen.take(2) // List(1, 2) @@ -308,22 +453,37 @@ oneToTen.take(2) // List(1, 2) oneToTen.takeRight(1) // List(10) oneToTen.takeRight(2) // List(9, 10) ``` +{% endtab %} + +{% endtabs %} Notice how these methods work with “edge” cases, where we ask for more elements than are in the sequence, or ask for zero elements: +{% tabs take-edge-cases-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) oneToTen.take(0) // List() oneToTen.takeRight(0) // List() ``` +{% endtab %} + +{% endtabs %} And this is `takeWhile`, which works with a predicate function: +{% tabs take-while-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) names.takeWhile(_.length < 5) // List(adam) ``` +{% endtab %} + +{% endtabs %} ## `drop`, `dropRight`, `dropWhile` @@ -331,6 +491,9 @@ names.takeWhile(_.length < 5) // List(adam) `drop`, `dropRight`, and `dropWhile` are essentially the opposite of their “take” counterparts, dropping elements from a list. Here are some examples: +{% tabs drop-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10) oneToTen.drop(5) // List(6, 7, 8, 9, 10) @@ -338,22 +501,37 @@ oneToTen.drop(5) // List(6, 7, 8, 9, 10) oneToTen.dropRight(8) // List(1, 2) oneToTen.dropRight(7) // List(1, 2, 3) ``` +{% endtab %} + +{% endtabs %} Again notice how these methods work with edge cases: +{% tabs drop-edge-cases-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.drop(Int.MaxValue) // List() oneToTen.dropRight(Int.MaxValue) // List() oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) ``` +{% endtab %} + +{% endtabs %} And this is `dropWhile`, which works with a predicate function: +{% tabs drop-while-example %} + +{% tab 'Scala 2 and 3' %} ```scala oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) names.dropWhile(_ != "chris") // List(chris, david) ``` +{% endtab %} + +{% endtabs %} @@ -365,21 +543,46 @@ It takes a function (or anonymous function) and applies that function to success The best way to explain `reduce` is to create a little helper method you can pass into it. For example, this is an `add` method that adds two integers together, and also provides us some nice debug output: +{% tabs reduce-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def add(x: Int, y: Int): Int = { + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala def add(x: Int, y: Int): Int = val theSum = x + y println(s"received $x and $y, their sum is $theSum") theSum ``` +{% endtab %} + +{% endtabs %} Given that method and this list: +{% tabs reduce-example-init %} + +{% tab 'Scala 2 and 3' %} ```scala val a = List(1,2,3,4) ``` +{% endtab %} + +{% endtabs %} this is what happens when you pass the `add` method into `reduce`: +{% tabs reduce-example-evaluation %} + +{% tab 'Scala 2 and 3' %} ```scala scala> a.reduce(add) received 1 and 2, their sum is 3 @@ -387,22 +590,37 @@ received 3 and 3, their sum is 6 received 6 and 4, their sum is 10 res0: Int = 10 ``` +{% endtab %} + +{% endtabs %} As that result shows, `reduce` uses `add` to reduce the list `a` into a single value, in this case, the sum of the integers in the list. Once you get used to `reduce`, you’ll write a “sum” algorithm like this: +{% tabs reduce-example-sum %} + +{% tab 'Scala 2 and 3' %} ```scala scala> a.reduce(_ + _) res0: Int = 10 ``` +{% endtab %} + +{% endtabs %} Similarly, a “product” algorithm looks like this: +{% tabs reduce-example-multiply %} + +{% tab 'Scala 2 and 3' %} ```scala scala> a.reduce(_ * _) res1: Int = 24 ``` +{% endtab %} + +{% endtabs %} > An important concept to know about `reduce` is that---as its name implies---it’s used to _reduce_ a collection down to a single value. diff --git a/_overviews/scala3-book/collections-summary.md b/_overviews/scala3-book/collections-summary.md index 32aa74cd0c..4a7aa1c385 100644 --- a/_overviews/scala3-book/collections-summary.md +++ b/_overviews/scala3-book/collections-summary.md @@ -2,7 +2,8 @@ title: Summary type: section description: This page provides a summary of the Collections chapter. -num: 39 +languages: [ru, zh-cn] +num: 41 previous-page: collections-methods next-page: fp-intro --- diff --git a/_overviews/scala3-book/concurrency.md b/_overviews/scala3-book/concurrency.md index bda65f21a9..4364239bd8 100644 --- a/_overviews/scala3-book/concurrency.md +++ b/_overviews/scala3-book/concurrency.md @@ -2,13 +2,14 @@ title: Concurrency type: chapter description: This page discusses how Scala concurrency works, with an emphasis on Scala Futures. -num: 68 +languages: [ru, zh-cn] +num: 69 previous-page: ca-summary next-page: scala-tools --- -When you want to write parallel and concurrent applications in Scala, you _can_ use the native Java `Thread`---but the Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) offers a more high level and idiomatic approach so it’s preferred, and covered in this chapter. +When you want to write parallel and concurrent applications in Scala, you _can_ use the native Java `Thread`---but the Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) offers a more high level and idiomatic approach, so it’s preferred, and covered in this chapter. @@ -48,7 +49,7 @@ val x = aShortRunningTask() println("Here") ``` -Conversely, if `aShortRunningTask` is created as a `Future`, the `println` statement is printed almost immediately because `aShortRunningTask` is spawned off on some other thread---it doesn’t block. +Conversely, if `aShortRunningTask` is created as a `Future`, the `println` statement is printed almost immediately because `aShortRunningTask` is spawned off on some other thread---it doesn't block. In this chapter you’ll see how to use futures, including how to run multiple futures in parallel and combine their results in a `for` expression. You’ll also see examples of methods that are used to handle the value in a future once it returns. @@ -83,7 +84,7 @@ def longRunningAlgorithm() = 42 ``` -That fancy algorithm returns the integer value `42` after a ten second delay. +That fancy algorithm returns the integer value `42` after a ten-second delay. Now call that algorithm by wrapping it into the `Future` constructor, and assigning the result to a variable: ```scala @@ -92,7 +93,7 @@ eventualInt: scala.concurrent.Future[Int] = Future(<not completed>) ``` Right away, your computation---the call to `longRunningAlgorithm()`---begins running. -If you immediately check the value of the variable `eventualInt`, you see that the future hasn’t been completed yet: +If you immediately check the value of the variable `eventualInt`, you see that the future hasn't been completed yet: ```scala scala> eventualInt @@ -116,10 +117,10 @@ Therefore, when you work with the result of a future, you use the usual `Try`-ha ### Using `map` with futures `Future` has a `map` method, which you use just like the `map` method on collections. -This is what the result looks like when you call `map` right after creating the variable `f`: +This is what the result looks like when you call `map` right after creating the variable `a`: ```scala -scala> val a = eventualInt.map(_ * 2) +scala> val a = Future(longRunningAlgorithm()).map(_ * 2) a: scala.concurrent.Future[Int] = Future(<not completed>) ``` @@ -140,7 +141,7 @@ In addition to higher-order functions like `map`, you can also use callback meth One commonly used callback method is `onComplete`, which takes a *partial function* in which you handle the `Success` and `Failure` cases: ```scala -eventualInt.onComplete { +Future(longRunningAlgorithm()).onComplete { case Success(value) => println(s"Got the callback, value = $value") case Failure(e) => e.printStackTrace } @@ -157,7 +158,7 @@ Got the callback, value = 42 ## Other Future methods The `Future` class has other methods you can use. -It has some of the methods that you find on Scala collections classes, including: +It has some methods that you find on Scala collections classes, including: - `filter` - `flatMap` @@ -269,7 +270,7 @@ But because they’re run in parallel, the total time is just slightly longer th > r1 + r2 + r3 > ~~~ > So, if you want the computations to be possibly run in parallel, remember -> to run them outside of the `for` expression. +> to run them outside the `for` expression. ### A method that returns a future diff --git a/_overviews/scala3-book/control-structures.md b/_overviews/scala3-book/control-structures.md index 61141bc606..9d44db59cb 100644 --- a/_overviews/scala3-book/control-structures.md +++ b/_overviews/scala3-book/control-structures.md @@ -2,8 +2,9 @@ title: Control Structures type: chapter description: This page provides an introduction to Scala's control structures, including if/then/else, 'for' loops, 'for' expressions, 'match' expressions, try/catch/finally, and 'while' loops. -num: 18 -previous-page: first-look-at-types +languages: [ru, zh-cn] +num: 19 +previous-page: string-interpolation next-page: domain-modeling-intro --- @@ -22,26 +23,57 @@ It also has two other powerful constructs that you may not have seen before, dep These are all demonstrated in the following sections. - - ## The if/then/else construct A one-line Scala `if` statement looks like this: +{% tabs control-structures-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-1 %} +```scala +if (x == 1) println(x) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-1 %} ```scala if x == 1 then println(x) ``` +{% endtab %} +{% endtabs %} When you need to run multiple lines of code after an `if` equality comparison, use this syntax: +{% tabs control-structures-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-2 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-2 %} ```scala if x == 1 then println("x is 1, as you can see:") println(x) ``` +{% endtab %} +{% endtabs %} The `if`/`else` syntax looks like this: +{% tabs control-structures-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-3 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} else { + println("x was not 1") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-3 %} ```scala if x == 1 then println("x is 1, as you can see:") @@ -49,9 +81,23 @@ if x == 1 then else println("x was not 1") ``` +{% endtab %} +{% endtabs %} And this is the `if`/`else if`/`else` syntax: +{% tabs control-structures-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-4 %} +```scala +if (x < 0) + println("negative") +else if (x == 0) + println("zero") +else + println("positive") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-4 %} ```scala if x < 0 then println("negative") @@ -60,9 +106,20 @@ else if x == 0 then else println("positive") ``` +{% endtab %} +{% endtabs %} + +### `end if` statement + +<blockquote class="help-info"> +<i class="fa fa-info"></i>  This is new in Scala 3, and not supported in Scala 2. +</blockquote> You can optionally include an `end if` statement at the end of each expression, if you prefer: +{% tabs control-structures-5 %} +{% tab 'Scala 3 Only' %} + ```scala if x == 1 then println("x is 1, as you can see:") @@ -70,18 +127,42 @@ if x == 1 then end if ``` +{% endtab %} +{% endtabs %} ### `if`/`else` expressions always return a result Note that `if`/`else` comparisons form _expressions_, meaning that they return a value which you can assign to a variable. Because of this, there’s no need for a special ternary operator: +{% tabs control-structures-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-6 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-6 %} ```scala val minValue = if a < b then a else b ``` +{% endtab %} +{% endtabs %} Because they return a value, you can use `if`/`else` expressions as the body of a method: +{% tabs control-structures-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-7 %} +```scala +def compare(a: Int, b: Int): Int = + if (a < b) + -1 + else if (a == b) + 0 + else + 1 +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-7 %} ```scala def compare(a: Int, b: Int): Int = if a < b then @@ -91,49 +172,87 @@ def compare(a: Int, b: Int): Int = else 1 ``` +{% endtab %} +{% endtabs %} ### Aside: Expression-oriented programming As a brief note about programming in general, when every expression you write returns a value, that style is referred to as _expression-oriented programming_, or EOP. For example, this is an _expression_: +{% tabs control-structures-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-8 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-8 %} ```scala val minValue = if a < b then a else b ``` +{% endtab %} +{% endtabs %} Conversely, lines of code that don’t return values are called _statements_, and they’re used for their _side-effects_. For example, these lines of code don’t return values, so they’re used for their side effects: +{% tabs control-structures-9 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-9 %} +```scala +if (a == b) action() +println("Hello") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-9 %} ```scala if a == b then action() println("Hello") ``` +{% endtab %} +{% endtabs %} The first example runs the `action` method as a side effect when `a` is equal to `b`. The second example is used for the side effect of printing a string to STDOUT. As you learn more about Scala you’ll find yourself writing more _expressions_ and fewer _statements_. - - ## `for` loops In its most simple use, a Scala `for` loop can be used to iterate over the elements in a collection. For example, given a sequence of integers, you can loop over its elements and print their values like this: +{% tabs control-structures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-10 %} +```scala +val ints = Seq(1, 2, 3) +for (i <- ints) println(i) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-10 %} ```scala val ints = Seq(1, 2, 3) for i <- ints do println(i) ``` +{% endtab %} +{% endtabs %} -The code `i <- ints` is referred to as a _generator_, and if you leave the parentheses off of the generator, the `do` keyword is required before the code that follows it. -Otherwise you can write the code like this: -```scala -for (i <- ints) println(i) -``` +The code `i <- ints` is referred to as a _generator_. In any generator `p <- e`, the expression `e` can generate zero or many bindings to the pattern `p`. + +This is what the result looks like in the Scala REPL: -Regardless of which approach you use, this is what the result looks like in the Scala REPL: +{% tabs control-structures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) +scala> for (i <- ints) println(i) +1 +2 +3 +```` +{% endtab %} +{% tab 'Scala 3' for=control-structures-11 %} ```` scala> val ints = Seq(1,2,3) ints: Seq[Int] = List(1, 2, 3) @@ -143,21 +262,49 @@ scala> for i <- ints do println(i) 2 3 ```` +{% endtab %} +{% endtabs %} + When you need a multiline block of code following the `for` generator, use the following syntax: +{% tabs control-structures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-12 %} ```scala -for - i <- ints +for (i <- ints) { + val x = i * 2 + println(s"i = $i, x = $x") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-12 %} +```scala +for i <- ints do val x = i * 2 println(s"i = $i, x = $x") ``` +{% endtab %} +{% endtabs %} + ### Multiple generators `for` loops can have multiple generators, as shown in this example: +{% tabs control-structures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-13 %} +```scala +for { + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +} { + println(s"i = $i, j = $j, k = $k") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-13 %} ```scala for i <- 1 to 2 @@ -166,6 +313,9 @@ for do println(s"i = $i, j = $j, k = $k") ``` +{% endtab %} +{% endtabs %} + That expression prints this output: @@ -184,6 +334,18 @@ i = 2, j = b, k = 6 `for` loops can also contain `if` statements, which are known as _guards_: +{% tabs control-structures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-14 %} +```scala +for { + i <- 1 to 5 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-14 %} ```scala for i <- 1 to 5 @@ -191,6 +353,9 @@ for do println(i) ``` +{% endtab %} +{% endtabs %} + The output of that loop is: @@ -202,6 +367,20 @@ The output of that loop is: A `for` loop can have as many guards as needed. This example shows one way to print the number `4`: +{% tabs control-structures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-15 %} +```scala +for { + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-15 %} ```scala for i <- 1 to 10 @@ -211,12 +390,16 @@ for do println(i) ``` +{% endtab %} +{% endtabs %} ### Using `for` with Maps You can also use `for` loops with a `Map`. For example, given this `Map` of state abbreviations and their full names: +{% tabs map %} +{% tab 'Scala 2 and 3' for=map %} ```scala val states = Map( "AK" -> "Alaska", @@ -224,21 +407,44 @@ val states = Map( "AR" -> "Arizona" ) ``` +{% endtab %} +{% endtabs %} You can print the keys and values using `for`, like this: +{% tabs control-structures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-16 %} +```scala +for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-16 %} ```scala for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") ``` +{% endtab %} +{% endtabs %} Here’s what that looks like in the REPL: +{% tabs control-structures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-17 %} +```scala +scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-17 %} ```scala scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") AK: Alaska AL: Alabama AR: Arizona ``` +{% endtab %} +{% endtabs %} As the `for` loop iterates over the map, each key/value pair is bound to the variables `abbrev` and `fullName`, which are in a tuple: @@ -248,8 +454,6 @@ As the `for` loop iterates over the map, each key/value pair is bound to the var As the loop runs, the variable `abbrev` is assigned to the current _key_ in the map, and the variable `fullName` is assigned to the current map _value_. - - ## `for` expressions In the previous `for` loop examples, those loops were all used for _side effects_, specifically to print those values to STDOUT using `println`. @@ -257,15 +461,27 @@ In the previous `for` loop examples, those loops were all used for _side effects It’s important to know that you can also create `for` _expressions_ that return values. You create a `for` expression by adding the `yield` keyword and an expression to return, like this: +{% tabs control-structures-18 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-18 %} ```scala val list = - for - i <- 10 to 12 - yield - i * 2 + for (i <- 10 to 12) + yield i * 2 // list: IndexedSeq[Int] = Vector(20, 22, 24) ``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-18 %} +```scala +val list = + for i <- 10 to 12 + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` +{% endtab %} +{% endtabs %} + After that `for` expression runs, the variable `list` is a `Vector` that contains the values shown. This is how the expression works: @@ -273,9 +489,9 @@ This is how the expression works: 1. The `for` expression starts to iterate over the values in the range `(10, 11, 12)`. It first works on the value `10`, multiplies it by `2`, then _yields_ that result, the value `20`. 2. Next, it works on the `11`---the second value in the range. - It multiples it by `2`, then yields the value `22`. + It multiplies it by `2`, then yields the value `22`. You can think of these yielded values as accumulating in a temporary holding place. -3. Finally the loop gets the number `12` from the range, multiplies it by `2`, yielding the number `24`. +3. Finally, the loop gets the number `12` from the range, multiplies it by `2`, yielding the number `24`. The loop completes at this point and yields the final result, the `Vector(20, 22, 24)`. {% comment %} @@ -284,31 +500,65 @@ NOTE: This is a place where it would be great to have a TIP or NOTE block: While the intent of this section is to demonstrate `for` expressions, it can help to know that the `for` expression shown is equivalent to this `map` method call: +{% tabs map-call %} +{% tab 'Scala 2 and 3' for=map-call %} ```scala val list = (10 to 12).map(i => i * 2) ``` +{% endtab %} +{% endtabs %} -`for` expressions can be used any time you need to traverse all of the elements in a collection and apply an algorithm to those elements to create a new list. +`for` expressions can be used any time you need to traverse all the elements in a collection and apply an algorithm to those elements to create a new list. Here’s an example that shows how to use a block of code after the `yield`: +{% tabs control-structures-19 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-19 %} ```scala val names = List("_olivia", "_walter", "_peter") -val capNames = for name <- names yield +val capNames = for (name <- names) yield { val nameWithoutUnderscore = name.drop(1) val capName = nameWithoutUnderscore.capitalize capName +} // capNames: List[String] = List(Olivia, Walter, Peter) ``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-19 %} +```scala +val names = List("_olivia", "_walter", "_peter") +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` +{% endtab %} +{% endtabs %} ### Using a `for` expression as the body of a method Because a `for` expression yields a result, it can be used as the body of a method that returns a useful value. -This method returns all of the values in a given list of integers that are between `3` and `10`: +This method returns all the values in a given list of integers that are between `3` and `10`: +{% tabs control-structures-20 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-20 %} +```scala +def between3and10(xs: List[Int]): List[Int] = + for { + x <- xs + if x >= 3 + if x <= 10 + } yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-20 %} ```scala def between3and10(xs: List[Int]): List[Int] = for @@ -319,33 +569,34 @@ def between3and10(xs: List[Int]): List[Int] = between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) ``` - - +{% endtab %} +{% endtabs %} ## `while` loops Scala `while` loop syntax looks like this: +{% tabs control-structures-21 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-21 %} ```scala var i = 0 -while i < 3 do +while (i < 3) { println(i) i += 1 +} ``` - -If you use parentheses around the test condition, it can also be written like this: - +{% endtab %} +{% tab 'Scala 3' for=control-structures-21 %} ```scala var i = 0 -while (i < 3) { +while i < 3 do println(i) i += 1 -} ``` - - +{% endtab %} +{% endtabs %} ## `match` expressions @@ -354,9 +605,24 @@ Pattern matching is a major feature of functional programming languages, and Sca In the most simple case you can use a `match` expression like a Java `switch` statement, matching cases based on an integer value. Notice that this really is an expression, as it evaluates to a result: +{% tabs control-structures-22 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-22 %} +```scala +// `i` is an integer +val day = i match { + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // the default, catch-all +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-22 %} ```scala -import scala.annotation.switch - // `i` is an integer val day = i match case 0 => "Sunday" @@ -368,48 +634,113 @@ val day = i match case 6 => "Saturday" case _ => "invalid day" // the default, catch-all ``` +{% endtab %} +{% endtabs %} + +In this example, the variable `i` is tested against the cases shown. +If it’s between `0` and `6`, `day` is bound to the string that represents that day of the week. +Otherwise, it matches the catch-all case represented by the character, `_`, and `day` is bound to the string, `"invalid day"`. -In this example the variable `i` is tested against the cases shown. -If it’s between `0` and `6`, `day` is bound to a string that represents one of the days of the week. -Otherwise, the catch-all case is represented by the `_` character, and `day` is bound to the string, `"invalid day"`. +Since the cases are considered in the order they are written, and the first matching case is used, the default case, which matches any value, must come last. Any cases after the catch-all will be warned as unreachable cases. > When writing simple `match` expressions like this, it’s recommended to use the `@switch` annotation on the variable `i`. > This annotation provides a compile-time warning if the switch can’t be compiled to a `tableswitch` or `lookupswitch`, which are better for performance. - ### Using the default value -When you need to access the catch-all, default value in a `match` expression, just provide a variable name on the left side of the `case` statement, and then use that variable name on the right side of the statement as needed: +When you need to access the catch-all, default value in a `match` expression, just provide a variable name on the left side of the `case` statement instead of `_`, and then use that variable name on the right side of the statement as needed: +{% tabs control-structures-23 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-23 %} +```scala +i match { + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-23 %} ```scala i match case 0 => println("1") case 1 => println("2") - case what => println(s"You gave me: $what" ) + case what => println(s"You gave me: $what") ``` +{% endtab %} +{% endtabs %} -In this example the variable is named `what` to show that it can be given any legal name. -You can also use `_` as a name to ignore the value. +The name used in the pattern must begin with a lowercase letter. +A name beginning with an uppercase letter does not introduce a variable, but matches a value in scope: +{% tabs control-structures-24 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-24 %} +```scala +val N = 42 +i match { + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-24 %} +```scala +val N = 42 +i match + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +``` +{% endtab %} +{% endtabs %} + +If `i` is equal to `42`, then `case N` will match, and it will print the string `"42"`. It won't reach the default case. ### Handling multiple possible matches on one line As mentioned, `match` expressions have many capabilities. This example shows how to use multiple possible pattern matches in each `case` statement: +{% tabs control-structures-25 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-25 %} +```scala +val evenOrOdd = i match { + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-25 %} ```scala val evenOrOdd = i match case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") case _ => println("some other number") ``` - +{% endtab %} +{% endtabs %} ### Using `if` guards in `case` clauses You can also use guards in the `case`s of a match expression. In this example the second and third `case` both use guards to match multiple integer values: +{% tabs control-structures-26 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-26 %} +```scala +i match { + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-26 %} ```scala i match case 1 => println("one, a lonely number") @@ -417,9 +748,23 @@ i match case x if x > 3 => println("4+, that’s a party") case _ => println("i’m guessing your number is zero or less") ``` +{% endtab %} +{% endtabs %} Here’s another example, which shows how to match a given value against ranges of numbers: +{% tabs control-structures-27 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-27 %} +```scala +i match { + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-27 %} ```scala i match case a if 0 to 9 contains a => println(s"0-9 range: $a") @@ -427,13 +772,30 @@ i match case c if 20 to 29 contains c => println(s"20-29 range: $c") case _ => println("Hmmm...") ``` - +{% endtab %} +{% endtabs %} #### Case classes and match expressions You can also extract fields from `case` classes---and classes that have properly written `apply`/`unapply` methods---and use those in your guard conditions. Here’s an example using a simple `Person` case class: +{% tabs control-structures-28 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-28 %} +```scala +case class Person(name: String) + +def speak(p: Person) = p match { + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") +} + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-28 %} ```scala case class Person(name: String) @@ -445,18 +807,78 @@ def speak(p: Person) = p match speak(Person("Fred")) // "Fred says, Yubba dubba doo" speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" ``` +{% endtab %} +{% endtabs %} + +#### Binding matched patterns to variables + +You can bind the matched pattern to a variable to use type-specific behavior: +{% tabs pattern-binding class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-binding %} +```scala +trait Animal { + val name: String +} +case class Cat(name: String) extends Animal { + def meow: String = "Meow" +} +case class Dog(name: String) extends Animal { + def bark: String = "Bark" +} + +def speak(animal: Animal) = animal match { + case c @ Cat(name) if name == "Felix" => println(s"$name says, ${c.meow}!") + case d @ Dog(name) if name == "Rex" => println(s"$name says, ${d.bark}!") + case _ => println("I don't know you!") +} + +speak(Cat("Felix")) // "Felix says, Meow!" +speak(Dog("Rex")) // "Rex says, Bark!" +``` +{% endtab %} +{% tab 'Scala 3' for=pattern-binding %} +```scala +trait Animal: + val name: String +case class Cat(name: String) extends Animal: + def meow: String = "Meow" +case class Dog(name: String) extends Animal: + def bark: String = "Bark" + +def speak(animal: Animal) = animal match + case c @ Cat(name) if name == "Felix" => println(s"$name says, ${c.meow}!") + case d @ Dog(name) if name == "Rex" => println(s"$name says, ${d.bark}!") + case _ => println("I don't know you!") + +speak(Cat("Felix")) // "Felix says, Meow!" +speak(Dog("Rex")) // "Rex says, Bark!" +``` +{% endtab %} +{% endtabs %} ### Using a `match` expression as the body of a method Because `match` expressions return a value, they can be used as the body of a method. This method takes a `Matchable` value as an input parameter, and returns a `Boolean`, based on the result of the `match` expression: +{% tabs control-structures-29 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-29 %} +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" | false => false + case _ => true +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-29 %} ```scala def isTruthy(a: Matchable) = a match case 0 | "" | false => false case _ => true ``` +{% endtab %} +{% endtabs %} The input parameter `a` is defined to be the [`Matchable` type][matchable]---which is the root of all Scala types that pattern matching can be performed on. The method is implemented by matching on the input, providing two cases: @@ -464,6 +886,8 @@ The first one checks whether the given value is either the integer `0`, an empt In the default case, we return `true` for any other value. These examples show how this method works: +{% tabs is-truthy-call %} +{% tab 'Scala 2 and 3' for=is-truthy-call %} ```scala isTruthy(0) // false isTruthy(false) // false @@ -472,13 +896,15 @@ isTruthy(1) // true isTruthy(" ") // true isTruthy(2F) // true ``` +{% endtab %} +{% endtabs %} Using a `match` expression as the body of a method is a very common use. - #### Match expressions support many different types of patterns + There are many different forms of patterns that can be used to write `match` expressions. -Examples includes: +Examples include: - Constant patterns (such as `case 3 => `) - Sequence patterns (such as `case List(els : _*) =>`) @@ -488,6 +914,46 @@ Examples includes: All of these kinds of patterns are shown in the following `pattern` method, which takes an input parameter of type `Matchable` and returns a `String`: +{% tabs control-structures-30 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-30 %} +```scala +def pattern(x: Matchable): String = x match { + + // constant patterns + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // sequence patterns + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // tuple patterns + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // constructor patterns + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // type test patterns + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // the default wildcard pattern + case _ => "Unknown" +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-30 %} ```scala def pattern(x: Matchable): String = x match @@ -523,13 +989,71 @@ def pattern(x: Matchable): String = x match // the default wildcard pattern case _ => "Unknown" ``` +{% endtab %} +{% endtabs %} -{% comment %} -TODO: Add in the new Scala 3 syntax shown on this page: -http://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html -{% endcomment %} +You can also write the code on the right side of the `=>` on multiple lines if you think it is easier to read. Here is one example: + +{% tabs control-structures-31 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-31 %} +```scala +count match { + case 1 => + println("one, a lonely number") + case x if x == 2 || x == 3 => + println("two's company, three's a crowd") + case x if x > 3 => + println("4+, that's a party") + case _ => + println("i'm guessing your number is zero or less") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-31 %} +```scala +count match + case 1 => + println("one, a lonely number") + case x if x == 2 || x == 3 => + println("two's company, three's a crowd") + case x if x > 3 => + println("4+, that's a party") + case _ => + println("i'm guessing your number is zero or less") +``` +{% endtab %} +{% endtabs %} +In Scala 3, `match` expressions can be chained: +{% tabs 'control-structures-32' %} +{% tab 'Scala 3 Only' %} +```scala +i match + case odd: Int if odd % 2 == 1 => "odd" + case even: Int if even % 2 == 0 => "even" + case _ => "not an integer" +match + case "even" => true + case _ => false +``` +{% endtab %} +{% endtabs %} + +The `match` expression can also follow a period, which simplifies matching on results returned by chained method calls: + +{% tabs 'control-structures-33' %} +{% tab 'Scala 3 Only' %} +```scala +List(1, 2, 3) + .map(_ * 2) + .headOption + .match + case Some(value) => println(s"The head is: $value") + case None => println("The list is empty") +``` +{% endtab %} +{% endtabs %} ## try/catch/finally @@ -538,6 +1062,22 @@ For consistency, Scala uses the same syntax that `match` expressions use and sup In the following example, `openAndReadAFile` is a method that does what its name implies: it opens a file and reads the text in it, assigning the result to the mutable variable `text`: +{% tabs control-structures-34 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-34 %} +```scala +var text = "" +try { + text = openAndReadAFile(filename) +} catch { + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +} finally { + // close your resources here + println("Came to the 'finally' clause.") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-34 %} ```scala var text = "" try @@ -549,8 +1089,9 @@ finally // close your resources here println("Came to the 'finally' clause.") ``` +{% endtab %} +{% endtabs %} -Assuming that the `openAndReadAFile` method uses the Java `java.io.*` classes to read a file and doesn’t catch its exceptions, attempting to open and read a file can result in both a `FileNotFoundException` and an `IOException`, and those two exceptions are caught in the `catch` block of this example. - +Assuming that the `openAndReadAFile` method uses the Java `java.io.*` classes to read a file and doesn't catch its exceptions, attempting to open and read a file can result in both a `FileNotFoundException` and an `IOException`, and those two exceptions are caught in the `catch` block of this example. [matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_overviews/scala3-book/domain-modeling-fp.md b/_overviews/scala3-book/domain-modeling-fp.md index 50576789c7..bc08f034c2 100644 --- a/_overviews/scala3-book/domain-modeling-fp.md +++ b/_overviews/scala3-book/domain-modeling-fp.md @@ -2,7 +2,8 @@ title: FP Modeling type: section description: This chapter provides an introduction to FP domain modeling with Scala 3. -num: 22 +languages: [ru, zh-cn] +num: 23 previous-page: domain-modeling-oop next-page: methods-intro --- @@ -17,8 +18,6 @@ When modeling the world around us with FP, you typically use these Scala constru > If you’re not familiar with algebraic data types (ADTs) and their generalized version (GADTs), you may want to read the [Algebraic Data Types][adts] section before reading this section. - - ## Introduction In FP, the *data* and the *operations on that data* are two separate things; you aren’t forced to encapsulate them together like you do with OOP. @@ -36,7 +35,7 @@ Ignoring the division of whole numbers, the possible *operations* on those value +, -, * ```` -An FP design is implemented in a similar way: +In FP, business domains are modeled in a similar way: - You describe your set of values (your data) - You describe operations that work on those values (your functions) @@ -48,19 +47,52 @@ An FP design is implemented in a similar way: In this chapter we’ll model the data and operations for a “pizza” in a pizza store. You’ll see how to implement the “data” portion of the Scala/FP model, and then you’ll see several different ways you can organize the operations on that data. - - ## Modeling the Data In Scala, describing the data model of a programming problem is simple: -- If you want to model data with different alternatives, use the `enum` construct +- If you want to model data with different alternatives, use the `enum` construct, (or `case object` in Scala 2). - If you only want to group things (or need more fine-grained control) use `case` classes - ### Describing Alternatives -Data that simply consists of different alternatives, like crust size, crust type, and toppings, is concisely modeled with the Scala 3 `enum` construct: +Data that simply consists of different alternatives, like crust size, crust type, and toppings, is precisely modelled +in Scala by an enumeration. + +{% tabs data_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_1 %} + +In Scala 2 enumerations are expressed with a combination of a `sealed class` and several `case object` that extend the class: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=data_1 %} + +In Scala 3 enumerations are concisely expressed with the `enum` construct: ```scala enum CrustSize: @@ -72,12 +104,34 @@ enum CrustType: enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` + +{% endtab %} +{% endtabs %} + > Data types that describe different alternatives (like `CrustSize`) are also sometimes referred to as _sum types_. ### Describing Compound Data A pizza can be thought of as a _compound_ container of the different attributes above. -We can use a `case` class to describe that a `Pizza` consists of a `crustSize`, `crustType`, and potentially multiple `Topping`s: +We can use a `case` class to describe that a `Pizza` consists of a `crustSize`, `crustType`, and potentially multiple `toppings`: + +{% tabs data_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_2 %} + +```scala +import CrustSize._ +import CrustType._ +import Topping._ + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +{% endtab %} +{% tab 'Scala 3' for=data_2 %} ```scala import CrustSize.* @@ -90,6 +144,10 @@ case class Pizza( toppings: Seq[Topping] ) ``` + +{% endtab %} +{% endtabs %} + > Data Types that aggregate multiple components (like `Pizza`) are also sometimes referred to as _product types_. And that’s it. @@ -98,17 +156,25 @@ This solution is very concise because it doesn’t require the operations on a p The data model is easy to read, like declaring the design for a relational database. It is also very easy to create values of our data model and inspect them: +{% tabs data_3 %} +{% tab 'Scala 2 and 3' for=data_3 %} + ```scala val myFavPizza = Pizza(Small, Regular, Seq(Cheese, Pepperoni)) println(myFavPizza.crustType) // prints Regular ``` +{% endtab %} +{% endtabs %} #### More of the data model We might go on in the same way to model the entire pizza-ordering system. Here are a few other `case` classes that are used to model such a system: +{% tabs data_4 %} +{% tab 'Scala 2 and 3' for=data_4 %} + ```scala case class Address( street1: String, @@ -130,14 +196,14 @@ case class Order( ) ``` +{% endtab %} +{% endtabs %} + #### “Skinny domain objects” In his book, *Functional and Reactive Domain Modeling*, Debasish Ghosh states that where OOP practitioners describe their classes as “rich domain models” that encapsulate data and behaviors, FP data models can be thought of as “skinny domain objects.” This is because---as this lesson shows---the data models are defined as `case` classes with attributes, but no behaviors, resulting in short and concise data structures. - - - ## Modeling the Operations This leads to an interesting question: Because FP separates the data from the operations on that data, how do you implement those operations in Scala? @@ -145,6 +211,24 @@ This leads to an interesting question: Because FP separates the data from the op The answer is actually quite simple: you simply write functions (or methods) that operate on values of the data we just modeled. For instance, we can define a function that computes the price of a pizza. +{% tabs data_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_5 %} + +```scala +def pizzaPrice(p: Pizza): Double = p match { + case Pizza(crustSize, crustType, toppings) => { + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_5 %} + ```scala def pizzaPrice(p: Pizza): Double = p match case Pizza(crustSize, crustType, toppings) => @@ -153,15 +237,57 @@ def pizzaPrice(p: Pizza): Double = p match val tops = toppings.map(toppingPrice).sum base + crust + tops ``` + +{% endtab %} +{% endtabs %} + You can notice how the implementation of the function simply follows the shape of the data: since `Pizza` is a case class, we use pattern matching to extract the components and call helper functions to compute the individual prices. +{% tabs data_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_6 %} + +```scala +def toppingPrice(t: Topping): Double = t match { + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_6 %} + ```scala def toppingPrice(t: Topping): Double = t match case Cheese | Onions => 0.5 case Pepperoni | BlackOlives | GreenOlives => 0.75 ``` + +{% endtab %} +{% endtabs %} + Similarly, since `Topping` is an enumeration, we use pattern matching to distinguish between the different variants. Cheese and onions are priced at 50ct while the rest is priced at 75ct each. + +{% tabs data_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_7 %} + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match { + // if the crust size is small or medium, + // the type is not important + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 + } +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_7 %} + ```scala def crustPrice(s: CrustSize, t: CrustType): Double = (s, t) match @@ -172,18 +298,22 @@ def crustPrice(s: CrustSize, t: CrustType): Double = case (Large, Regular) => 0.75 case (Large, Thick) => 1.00 ``` + +{% endtab %} +{% endtabs %} + To compute the price of the crust we simultaneously pattern match on both the size and the type of the crust. > An important point about all functions shown above is that they are *pure functions*: they do not mutate any data or have other side-effects (like throwing exceptions or writing to a file). > All they do is simply receive values and compute the result. {% comment %} -I’ve added this comment per [this Github comment](https://github.com/scalacenter/docs.scala-lang/pull/3#discussion_r543372428). +I’ve added this comment per [this GitHub comment](https://github.com/scalacenter/docs.scala-lang/pull/3#discussion_r543372428). To that point, I’ve added these definitions here from our Slack conversation, in case anyone wants to update the “pure function” definition. If not, please delete this comment. Sébastien: ---------- -A function `f` is pure if, given the same input `x`, it will always return the same output `f(x)`, and it never modifies any state outside of it (therefore potentially causing other functions to behave differently in the future). +A function `f` is pure if, given the same input `x`, it will always return the same output `f(x)`, and it never modifies any state outside it (therefore potentially causing other functions to behave differently in the future). Jonathan: --------- @@ -203,12 +333,10 @@ Mine (Alvin, now modified, from fp-pure-functions.md): - It doesn’t have any “back doors”: It doesn’t read data from the outside world (including the console, web services, databases, files, etc.), or write data to the outside world {% endcomment %} - - ## How to Organize Functionality + When implementing the `pizzaPrice` function above, we did not say _where_ we would define it. -In Scala 3, it would be perfectly valid to define it on the toplevel of your file. -However, the language gives us many great tools to organize our logic in different namespaces and modules. +Scala gives you many great tools to organize your logic in different namespaces and modules. There are several different ways to implement and organize behaviors: @@ -227,6 +355,40 @@ A first approach is to define the behavior---the functions---in a companion obje With this approach, in addition to the enumeration or case class you also define an equally named companion object that contains the behavior. +{% tabs org_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=org_1 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// the companion object of case class Pizza +object Pizza { + // the implementation of `pizzaPrice` from above + def price(p: Pizza): Double = ... +} + +sealed abstract class Topping + +// the companion object of enumeration Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping + + // the implementation of `toppingPrice` above + def price(t: Topping): Double = ... +} +``` + +{% endtab %} +{% tab 'Scala 3' for=org_1 %} + ```scala case class Pizza( crustSize: CrustSize, @@ -245,17 +407,25 @@ enum Topping: // the companion object of enumeration Topping object Topping: // the implementation of `toppingPrice` above - def price(t: Topping): Double = t match - case Cheese | Onions => 0.5 - case Pepperoni | BlackOlives | GreenOlives => 0.75 + def price(t: Topping): Double = ... ``` + +{% endtab %} +{% endtabs %} + With this approach you can create a `Pizza` and compute its price like this: +{% tabs org_2 %} +{% tab 'Scala 2 and 3' for=org_2 %} + ```scala val pizza1 = Pizza(Small, Thin, Seq(Cheese, Onions)) Pizza.price(pizza1) ``` +{% endtab %} +{% endtabs %} + Grouping functionality this way has a few advantages: - It associates functionality with data and makes it easier to find for programmers (and the compiler). @@ -266,13 +436,12 @@ However, there are also a few tradeoffs that should be considered: - It tightly couples the functionality to your data model. In particular, the companion object needs to be defined in the same file as your `case` class. -- It might be unclear where to define functions like `crustPrice` that could equally well be placed in an companion object of `CrustSize` or `CrustType`. - +- It might be unclear where to define functions like `crustPrice` that could equally well be placed in a companion object of `CrustSize` or `CrustType`. ## Modules A second way to organize behavior is to use a “modular” approach. -The book, *Programming in Scala*, defines a *module* as, “a ‘smaller program piece’ with a well defined interface and a hidden implementation.” +The book, *Programming in Scala*, defines a *module* as, “a ‘smaller program piece’ with a well-defined interface and a hidden implementation.” Let’s look at what this means. ### Creating a `PizzaService` interface @@ -280,6 +449,26 @@ Let’s look at what this means. The first thing to think about are the `Pizza`s “behaviors”. When doing this, you sketch a `PizzaServiceInterface` trait like this: +{% tabs module_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_1 %} + +```scala +trait PizzaServiceInterface { + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_1 %} + ```scala trait PizzaServiceInterface: @@ -292,6 +481,9 @@ trait PizzaServiceInterface: def updateCrustType(p: Pizza, ct: CrustType): Pizza ``` +{% endtab %} +{% endtabs %} + As shown, each method takes a `Pizza` as an input parameter---along with other parameters---and then returns a `Pizza` instance as a result When you write a pure interface like this, you can think of it as a contract that states, “all non-abstract classes that extend this trait *must* provide an implementation of these services.” @@ -299,6 +491,9 @@ When you write a pure interface like this, you can think of it as a contract tha What you might also do at this point is imagine that you’re the consumer of this API. When you do that, it helps to sketch out some sample “consumer” code to make sure the API looks like what you want: +{% tabs module_2 %} +{% tab 'Scala 2 and 3' for=module_2 %} + ```scala val p = Pizza(Small, Thin, Seq(Cheese)) @@ -309,6 +504,9 @@ val p3 = updateCrustType(p2, Thick) val p4 = updateCrustSize(p3, Large) ``` +{% endtab %} +{% endtabs %} + If that code seems okay, you’ll typically start sketching another API---such as an API for orders---but since we’re only looking at pizzas right now, we’ll stop thinking about interfaces and create a concrete implementation of this interface. > Notice that this is usually a two-step process. @@ -316,11 +514,37 @@ If that code seems okay, you’ll typically start sketching another API---such a > In the second step you create a concrete *implementation* of that interface. > In some cases you’ll end up creating multiple concrete implementations of the base interface. - ### Creating a concrete implementation Now that you know what the `PizzaServiceInterface` looks like, you can create a concrete implementation of it by writing the body for all of the methods you defined in the interface: +{% tabs module_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_3 %} + +```scala +object PizzaService extends PizzaServiceInterface { + + def price(p: Pizza): Double = + ... // implementation from above + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_3 %} + ```scala object PizzaService extends PizzaServiceInterface: @@ -342,10 +566,34 @@ object PizzaService extends PizzaServiceInterface: end PizzaService ``` +{% endtab %} +{% endtabs %} + While this two-step process of creating an interface followed by an implementation isn’t always necessary, explicitly thinking about the API and its use is a good approach. With everything in place you can use your `Pizza` class and `PizzaService`: +{% tabs module_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_4 %} + +```scala +import PizzaService._ + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// use the PizzaService methods +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // prints 8.75 +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_4 %} + ```scala import PizzaService.* @@ -360,6 +608,9 @@ val p4 = updateCrustSize(p3, Large) println(price(p4)) // prints 8.75 ``` +{% endtab %} +{% endtabs %} + ### Functional Objects In the book, *Programming in Scala*, the authors define the term, “Functional Objects” as “objects that do not have any mutable state”. @@ -374,10 +625,41 @@ You can think of this approach as a “hybrid FP/OOP design” because you: > This really is a hybrid approach: like in an **OOP design**, the methods are encapsulated in the class with the data, but as typical for a **FP design**, methods are implemented as pure functions that don’t mutate the data - #### Example -Using this approach, you can directly implement the functionality on pizzas in the case case: +Using this approach, you can directly implement the functionality on pizzas in the case class: + +{% tabs module_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_5 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) { + + // the operations on the data model + def price: Double = + pizzaPrice(this) // implementation from above + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_5 %} ```scala case class Pizza( @@ -403,11 +685,17 @@ case class Pizza( this.copy(crustType = ct) ``` +{% endtab %} +{% endtabs %} + Notice that unlike the previous approaches, because these are methods on the `Pizza` class, they don’t take a `Pizza` reference as an input parameter. Instead, they have their own reference to the current pizza instance as `this`. Now you can use this new design like this: +{% tabs module_6 %} +{% tab 'Scala 2 and 3' for=module_6 %} + ```scala Pizza(Small, Thin, Seq(Cheese)) .addTopping(Pepperoni) @@ -415,7 +703,11 @@ Pizza(Small, Thin, Seq(Cheese)) .price ``` +{% endtab %} +{% endtabs %} + ### Extension Methods + Finally, we show an approach that lies between the first one (defining functions in the companion object) and the last one (defining functions as methods on the type itself). Extension methods let us create an API that is like the one of functional object, without having to define functions as methods on the type itself. @@ -427,6 +719,40 @@ This can have multiple advantages: Let us revisit our example once more. +{% tabs module_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_7 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +implicit class PizzaOps(p: Pizza) { + def price: Double = + pizzaPrice(p) // implementation from above + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` +In the above code, we define the different methods on pizzas as methods in an _implicit class_. +With `implicit class PizzaOps(p: Pizza)` then wherever `PizzaOps` is imported its methods will be available on +instances of `Pizza`. The receiver in this case is `p`. + +{% endtab %} +{% tab 'Scala 3' for=module_7 %} + ```scala case class Pizza( crustSize: CrustSize, @@ -451,9 +777,16 @@ extension (p: Pizza) p.copy(crustType = ct) ``` In the above code, we define the different methods on pizzas as _extension methods_. -With `extension (p: Pizza)` we say that we want to make the methods available on instances of `Pizza` and refer to the instance we extend as `p` in the following. +With `extension (p: Pizza)` we say that we want to make the methods available on instances of `Pizza`. The receiver +in this case is `p`. -This way, we can obtain the same API as before +{% endtab %} +{% endtabs %} + +Using our extension methods, we can obtain the same API as before: + +{% tabs module_8 %} +{% tab 'Scala 2 and 3' for=module_8 %} ```scala Pizza(Small, Thin, Seq(Cheese)) @@ -461,12 +794,15 @@ Pizza(Small, Thin, Seq(Cheese)) .updateCrustType(Thick) .price ``` + +{% endtab %} +{% endtabs %} + while being able to define extensions in any other module. Typically, if you are the designer of the data model, you will define your extension methods in the companion object. This way, they are already available to all users. Otherwise, extension methods need to be imported explicitly to be usable. - ## Summary of this Approach Defining a data model in Scala/FP tends to be simple: Just model variants of the data with enumerations and compound data with `case` classes. @@ -478,6 +814,5 @@ We have seen different ways to organize your functions: - You can use a “functional objects” approach and store the methods on the defined data type - You can use extension methods to equip your data model with functionality - [adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} [modeling-tools]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_overviews/scala3-book/domain-modeling-intro.md b/_overviews/scala3-book/domain-modeling-intro.md index 6707e9ddfe..fada05d5f3 100644 --- a/_overviews/scala3-book/domain-modeling-intro.md +++ b/_overviews/scala3-book/domain-modeling-intro.md @@ -2,7 +2,8 @@ title: Domain Modeling type: chapter description: This chapter provides an introduction to domain modeling in Scala 3. -num: 19 +languages: [ru, zh-cn] +num: 20 previous-page: control-structures next-page: domain-modeling-tools --- diff --git a/_overviews/scala3-book/domain-modeling-oop.md b/_overviews/scala3-book/domain-modeling-oop.md index 744a45023a..948504139e 100644 --- a/_overviews/scala3-book/domain-modeling-oop.md +++ b/_overviews/scala3-book/domain-modeling-oop.md @@ -2,14 +2,14 @@ title: OOP Modeling type: section description: This chapter provides an introduction to OOP domain modeling with Scala 3. -num: 21 +languages: [ru, zh-cn] +num: 22 previous-page: domain-modeling-tools next-page: domain-modeling-fp --- -This chapter provides an introduction to domain modeling using object-oriented programming (OOP) in Scala 3. - +This chapter provides an introduction to domain modeling using object-oriented programming (OOP) in Scala 3. ## Introduction @@ -23,20 +23,54 @@ Scala provides all the necessary tools for object-oriented design: - **Access modifiers** lets you control which members of a class can be accessed by which part of the code. ## Traits -Perhaps different than other languages with support for OOP, such as Java, the primary tool of decomposition in Scala is not classes, but traits. + +Perhaps different from other languages with support for OOP, such as Java, the primary tool of decomposition in Scala is not classes, but traits. They can serve to describe abstract interfaces like: +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait Showable: def show: String ``` +{% endtab %} +{% endtabs %} and can also contain concrete implementations: + +{% tabs traits_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String + def showHtml = "<p>" + show + "</p>" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait Showable: def show: String def showHtml = "<p>" + show + "</p>" ``` +{% endtab %} +{% endtabs %} + You can see that we define the method `showHtml` _in terms_ of the abstract method `show`. [Odersky and Zenger][scalable] present the _service-oriented component model_ and view: @@ -46,11 +80,29 @@ You can see that we define the method `showHtml` _in terms_ of the abstract meth We can already see this with our example of `Showable`: defining a class `Document` that extends `Showable`, we still have to define `show`, but are provided with `showHtml`: +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Document(text: String) extends Showable { + def show = text +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Document(text: String) extends Showable: def show = text ``` + +{% endtab %} +{% endtabs %} + #### Abstract Members + Abstract methods are not the only thing that can be left abstract in a trait. A trait can contain: @@ -58,13 +110,34 @@ A trait can contain: - abstract value definitions (`val x: T`) - abstract type members (`type T`), potentially with bounds (`type T <: S`) - abstract givens (`given t: T`) +<span class="tag tag-inline">Scala 3 only</span> Each of the above features can be used to specify some form of requirement on the implementor of the trait. ## Mixin Composition + Not only can traits contain abstract and concrete definitions, Scala also provides a powerful way to compose multiple traits: a feature which is often referred to as _mixin composition_. Let us assume the following two (potentially independently defined) traits: + +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait GreetingService { + def translate(text: String): String + def sayHello = translate("Hello") +} + +trait TranslationService { + def translate(text: String): String = "..." +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait GreetingService: def translate(text: String): String @@ -73,14 +146,35 @@ trait GreetingService: trait TranslationService: def translate(text: String): String = "..." ``` + +{% endtab %} +{% endtabs %} + To compose the two services, we can simply create a new trait extending them: + +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait ComposedService extends GreetingService with TranslationService +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait ComposedService extends GreetingService, TranslationService ``` + +{% endtab %} +{% endtabs %} + Abstract members in one trait (such as `translate` in `GreetingService`) are automatically matched with concrete members in another trait. -This not only works with methods as in this example, but also with all of the other abstract members mentioned above (that is, types, value definitions, etc.). +This not only works with methods as in this example, but also with all the other abstract members mentioned above (that is, types, value definitions, etc.). ## Classes + Traits are great to modularize components and describe interfaces (required and provided). But at some point we’ll want to create instances of them. When designing software in Scala, it’s often helpful to only consider using classes at the leafs of your inheritance model: @@ -89,45 +183,116 @@ When designing software in Scala, it’s often helpful to only consider using cl NOTE: I think “leaves” may technically be the correct word to use, but I prefer “leafs.” {% endcomment %} +{% tabs table-traits-cls-summary class=tabs-scala-version %} +{% tab 'Scala 2' %} +| Traits | `T1`, `T2`, `T3` +| Composed traits | `S1 extends T1 with T2`, `S2 extends T2 with T3` +| Classes | `C extends S1 with T3` +| Instances | `new C()` +{% endtab %} +{% tab 'Scala 3' %} | Traits | `T1`, `T2`, `T3` -| Composed traits | `S extends T1, T2`, `S extends T2, T3` -| Classes | `C extends S, T3` +| Composed traits | `S1 extends T1, T2`, `S2 extends T2, T3` +| Classes | `C extends S1, T3` | Instances | `C()` +{% endtab %} +{% endtabs %} This is even more the case in Scala 3, where traits now can also take parameters, further eliminating the need for classes. #### Defining Classes + Like traits, classes can extend multiple traits (but only one super class): + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class MyService(name: String) extends ComposedService with Showable { + def show = s"$name says $sayHello" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class MyService(name: String) extends ComposedService, Showable: def show = s"$name says $sayHello" ``` + +{% endtab %} +{% endtabs %} + #### Subtyping + We can create an instance of `MyService` as follows: + +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1: MyService = new MyService("Service 1") +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala val s1: MyService = MyService("Service 1") ``` + +{% endtab %} +{% endtabs %} + Through the means of subtyping, our instance `s1` can be used everywhere that any of the extended traits is expected: + +{% tabs class_3 %} +{% tab 'Scala 2 and 3' %} + ```scala val s2: GreetingService = s1 val s3: TranslationService = s1 val s4: Showable = s1 // ... and so on ... ``` +{% endtab %} +{% endtabs %} #### Planning for Extension + As mentioned before, it is possible to extend another class: + +{% tabs class_4 %} +{% tab 'Scala 2 and 3' %} + ```scala class Person(name: String) class SoftwareDeveloper(name: String, favoriteLang: String) extends Person(name) ``` + +{% endtab %} +{% endtabs %} + However, since _traits_ are designed as the primary means of decomposition, -a class that is defined in one file _cannot_ be extended in another file. -In order to allow this, the base class needs to be marked as `open`: +it is not recommended to extend a class that is defined in one file from another file. + +<h5>Open Classes <span class="tag tag-inline">Scala 3 only</span></h5> + +In Scala 3 extending non-abstract classes in other files is restricted. In order to allow this, the base class needs to +be marked as `open`: + +{% tabs class_5 %} +{% tab 'Scala 3 Only' %} + ```scala open class Person(name: String) ``` +{% endtab %} +{% endtabs %} + Marking classes with [`open`][open] is a new feature of Scala 3. Having to explicitly mark classes as open avoids many common pitfalls in OO design. In particular, it requires library designers to explicitly plan for extension and for instance document the classes that are marked as open with additional extension contracts. @@ -137,10 +302,27 @@ Unfortunately I can’t find any good links to this on the internet. I only mention this because I think that book and phrase is pretty well known in the Java world. {% endcomment %} - - ## Instances and Private Mutable State + Like in other languages with support for OOP, traits and classes in Scala can define mutable fields: + +{% tabs instance_6 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Counter { + // can only be observed by the method `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Counter: // can only be observed by the method `count` @@ -149,7 +331,26 @@ class Counter: def tick(): Unit = currentCount += 1 def count: Int = currentCount ``` + +{% endtab %} +{% endtabs %} + Every instance of the class `Counter` has its own private state that can only be observed through the method `count`, as the following interaction illustrates: + +{% tabs instance_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val c1 = new Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala val c1 = Counter() c1.count // 0 @@ -158,15 +359,19 @@ c1.tick() c1.count // 2 ``` +{% endtab %} +{% endtabs %} + #### Access Modifiers + By default, all member definitions in Scala are publicly visible. To hide implementation details, it’s possible to define members (methods, fields, types, etc.) to be `private` or `protected`. This way you can control how they are accessed or overridden. Private members are only visible to the class/trait itself and to its companion object. Protected members are also visible to subclasses of the class. - ## Advanced Example: Service Oriented Design + In the following, we illustrate some advanced features of Scala and show how they can be used to structure larger software components. The examples are adapted from the paper ["Scalable Component Abstractions"][scalable] by Martin Odersky and Matthias Zenger. Don’t worry if you don’t understand all the details of the example; it’s primarily intended to demonstrate how to use several type features to construct larger components. @@ -174,33 +379,67 @@ Don’t worry if you don’t understand all the details of the example; it’s p Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers): +{% tabs example_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + ```scala -trait SubjectObserver: +trait SubjectObserver { type S <: Subject type O <: Observer trait Subject { self: S => private var observers: List[O] = List() - def subscribe(obs: O): Unit = + def subscribe(obs: O): Unit = { observers = obs :: observers - def publish() = - for obs <- observers do obs.notify(this) + } + def publish() = { + for ( obs <- observers ) obs.notify(this) + } } trait Observer { def notify(sub: S): Unit } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait SubjectObserver: + + type S <: Subject + type O <: Observer + + trait Subject: + self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = + observers = obs :: observers + def publish() = + for obs <- observers do obs.notify(this) + + trait Observer: + def notify(sub: S): Unit ``` + +{% endtab %} +{% endtabs %} + There are a few things that need explanation. #### Abstract Type Members + The declaration `type S <: Subject` says that within the trait `SubjectObserver` we can refer to some _unknown_ (that is, abstract) type that we call `S`. However, the type is not completely unknown: we know at least that it is _some subtype_ of the trait `Subject`. All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`. The `<: Subject` part of the declaration is also referred to as an _upper bound on `S`_. #### Nested Traits + _Within_ trait `SubjectObserver`, we define two other traits. Let us begin with trait `Observer`, which only defines one abstract method `notify` that takes an argument of type `S`. As we will see momentarily, it is important that the argument has type `S` and not type `Subject`. @@ -210,6 +449,7 @@ Subscribing to a subject simply stores the object into this list. Again, the type of parameter `obs` is `O`, not `Observer`. #### Self-type Annotations + Finally, you might have wondered what the `self: S =>` on trait `Subject` is supposed to mean. This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. @@ -217,8 +457,37 @@ This is necessary to be able to call `obs.notify` with `this` as an argument, si If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`. ### Implementing the Component + We can now implement the above component and define the abstract type members to be concrete types: +{% tabs example_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object SensorReader extends SubjectObserver { + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject { + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = { + currentValue = v + publish() + } + } + + class Display extends Observer { + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala object SensorReader extends SubjectObserver: type S = Sensor @@ -235,11 +504,15 @@ object SensorReader extends SubjectObserver: def notify(sub: Sensor) = println(s"${sub.label} has value ${sub.value}") ``` + +{% endtab %} +{% endtabs %} + Specifically, we define a _singleton_ object `SensorReader` that extends `SubjectObserver`. In the implementation of `SensorReader`, we say that type `S` is now defined as type `Sensor`, and type `O` is defined to be equal to type `Display`. Both `Sensor` and `Display` are defined as nested classes within `SensorReader`, implementing the traits `Subject` and `Observer`, correspondingly. -Besides being an example of a service oriented design, this code also highlights many aspects of object-oriented programming: +Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming: - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`. - The implementation of `changeValue` uses the method `publish` defined in the extended trait. @@ -251,7 +524,39 @@ NOTE: You might say “the abstract method `notify`” in that last sentence, bu It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`. ### Using the Component + Finally, the following code illustrates how to use our `SensorReader` component: + +{% tabs example_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import SensorReader._ + +// setting up a network +val s1 = new Sensor("sensor1") +val s2 = new Sensor("sensor2") +val d1 = new Display() +val d2 = new Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// propagating updates through the network +s1.changeValue(2) +s2.changeValue(3) + +// prints: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 + +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala import SensorReader.* @@ -273,18 +578,16 @@ s2.changeValue(3) // sensor1 has value 2.0 // sensor2 has value 3.0 ``` + +{% endtab %} +{% endtabs %} + With all the object-oriented programming utilities under our belt, in the next section we will demonstrate how to design programs in a functional style. {% comment %} NOTE: One thing I occasionally do is flip things like this around, so I first show how to use a component, and then show how to implement that component. I don’t have a rule of thumb about when to do this, but sometimes it’s motivational to see the use first, and then see how to create the code to make that work. {% endcomment %} - - [scalable]: https://doi.org/10.1145/1094811.1094815 [open]: {{ site.scala3ref }}/other-new-features/open-classes.html [trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html - - - - diff --git a/_overviews/scala3-book/domain-modeling-tools.md b/_overviews/scala3-book/domain-modeling-tools.md index f69bad4871..c1475ce161 100644 --- a/_overviews/scala3-book/domain-modeling-tools.md +++ b/_overviews/scala3-book/domain-modeling-tools.md @@ -2,12 +2,14 @@ title: Tools type: section description: This chapter provides an introduction to the available domain modeling tools in Scala 3, including classes, traits, enums, and more. -num: 20 +languages: [ru, zh-cn] +num: 21 previous-page: domain-modeling-intro next-page: domain-modeling-oop --- -Scala 3 provides many different constructs so we can model the world around us: + +Scala provides many different constructs so we can model the world around us: - Classes - Objects @@ -15,61 +17,115 @@ Scala 3 provides many different constructs so we can model the world around us: - Traits - Abstract classes - Enums +<span class="tag tag-inline">Scala 3 only</span> - Case classes - Case objects This section briefly introduces each of these language features. - ## Classes As with other languages, a _class_ in Scala is a template for the creation of object instances. Here are some examples of classes: +{% tabs class_1 %} +{% tab 'Scala 2 and 3' %} + ```scala class Person(var name: String, var vocation: String) class Book(var title: String, var author: String, var year: Int) class Movie(var name: String, var director: String, var year: Int) ``` +{% endtab %} +{% endtabs %} + These examples show that Scala has a very lightweight way to declare classes. -All of the parameters of our example classes are defined as `var` fields, which means they are mutable: you can read them, and also modify them. +All the parameters of our example classes are defined as `var` fields, which means they are mutable: you can read them, and also modify them. If you want them to be immutable---read only---create them as `val` fields instead, or use a case class. Prior to Scala 3, you used the `new` keyword to create a new instance of a class: +{% tabs class_2 %} +{% tab 'Scala 2 Only' %} + ```scala val p = new Person("Robert Allen Zimmerman", "Harmonica Player") // --- ``` -However, with [creator applications][creator] this isn’t required in Scala 3: +{% endtab %} +{% endtabs %} + +However, with [universal apply methods][creator] this isn’t required in Scala 3: +<span class="tag tag-inline">Scala 3 only</span> + +{% tabs class_3 %} +{% tab 'Scala 3 Only' %} ```scala val p = Person("Robert Allen Zimmerman", "Harmonica Player") ``` +{% endtab %} +{% endtabs %} + Once you have an instance of a class such as `p`, you can access its fields, which in this example are all constructor parameters: +{% tabs class_4 %} +{% tab 'Scala 2 and 3' %} + ```scala p.name // "Robert Allen Zimmerman" p.vocation // "Harmonica Player" ``` +{% endtab %} +{% endtabs %} + As mentioned, all of these parameters were created as `var` fields, so you can also mutate them: +{% tabs class_5 %} +{% tab 'Scala 2 and 3' %} + ```scala p.name = "Bob Dylan" p.vocation = "Musician" ``` +{% endtab %} +{% endtabs %} + ### Fields and methods Classes can also have methods and additional fields that are not part of constructors. They are defined in the body of the class. The body is initialized as part of the default constructor: +{% tabs method class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person(var firstName: String, var lastName: String) { + + println("initialization begins") + val fullName = firstName + " " + lastName + + // a class method + def printFullName: Unit = + // access the `fullName` field, which is created above + println(fullName) + + printFullName + println("initialization ends") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Person(var firstName: String, var lastName: String): @@ -85,9 +141,26 @@ class Person(var firstName: String, var lastName: String): println("initialization ends") ``` +{% endtab %} +{% endtabs %} + The following REPL session shows how to create a new `Person` instance with this class: +{% tabs demo-person class=tabs-scala-version %} +{% tab 'Scala 2' %} +````scala +scala> val john = new Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe ```` +{% endtab %} +{% tab 'Scala 3' %} +````scala scala> val john = Person("John", "Doe") initialization begins John Doe @@ -97,6 +170,8 @@ val john: Person = Person@55d8f6bb scala> john.printFullName John Doe ```` +{% endtab %} +{% endtabs %} Classes can also extend traits and abstract classes, which we cover in dedicated sections below. @@ -104,13 +179,43 @@ Classes can also extend traits and abstract classes, which we cover in dedicated As a quick look at a few other features, class constructor parameters can also have default values: +{% tabs default-values_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000) { + override def toString = s"timeout: $timeout, linger: $linger" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Socket(val timeout: Int = 5_000, val linger: Int = 5_000): override def toString = s"timeout: $timeout, linger: $linger" ``` +{% endtab %} +{% endtabs %} + A great thing about this feature is that it lets consumers of your code create classes in a variety of different ways, as though the class had alternate constructors: +{% tabs default-values_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s = new Socket() // timeout: 5000, linger: 5000 +val s = new Socket(2_500) // timeout: 2500, linger: 5000 +val s = new Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = new Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = new Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala val s = Socket() // timeout: 5000, linger: 5000 val s = Socket(2_500) // timeout: 2500, linger: 5000 @@ -119,9 +224,29 @@ val s = Socket(timeout = 10_000) // timeout: 10000, linger: 5000 val s = Socket(linger = 10_000) // timeout: 5000, linger: 10000 ``` +{% endtab %} +{% endtabs %} + When creating a new instance of a class, you can also use named parameters. This is particularly helpful when many of the parameters have the same type, as shown in this comparison: +{% tabs default-values_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// option 1 +val s = new Socket(10_000, 10_000) + +// option 2 +val s = new Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala // option 1 val s = Socket(10_000, 10_000) @@ -133,6 +258,9 @@ val s = Socket( ) ``` +{% endtab %} +{% endtabs %} + ### Auxiliary constructors You can define a class to have multiple constructors so consumers of your class can build it in different ways. @@ -145,6 +273,48 @@ While analyzing the requirements you’ve seen that you need to be able to const One way to handle this situation in an OOP style is with this code: +{% tabs structor_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import java.time._ + +// [1] the primary constructor +class Student( + var name: String, + var govtId: String +) { + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] a constructor for when the student has completed + // their application + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = { + this(name, govtId) + _applicationDate = Some(applicationDate) + } + + // [3] a constructor for when the student is approved + // and now has a student id + def this( + name: String, + govtId: String, + studentId: Int + ) = { + this(name, govtId) + _studentId = studentId + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala import java.time.* @@ -177,6 +347,9 @@ class Student( _studentId = studentId ``` +{% endtab %} +{% endtabs %} + {% comment %} // for testing that code override def toString = s""" @@ -195,17 +368,31 @@ The class has three constructors, given by the numbered comments in the code: Those constructors can be called like this: +{% tabs structor_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1 = new Student("Mary", "123") +val s2 = new Student("Mary", "123", LocalDate.now()) +val s3 = new Student("Mary", "123", 456) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala val s1 = Student("Mary", "123") -val s2 = Student("Mary", "123", LocalDate.now) +val s2 = Student("Mary", "123", LocalDate.now()) val s3 = Student("Mary", "123", 456) ``` +{% endtab %} +{% endtabs %} + While this technique can be used, bear in mind that constructor parameters can also have default values, which make it seem that a class has multiple constructors. This is shown in the previous `Socket` example. - - ## Objects An object is a class that has exactly one instance. @@ -215,6 +402,21 @@ Objects in Scala allow grouping methods and fields under one namespace, similar Declaring an `object` is similar to declaring a `class`. Here’s an example of a “string utilities” object that contains a set of methods for working with strings: +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object StringUtils { + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala object StringUtils: def truncate(s: String, length: Int): String = s.take(length) @@ -222,13 +424,37 @@ object StringUtils: def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty ``` +{% endtab %} +{% endtabs %} + We can use the object as follows: + +{% tabs object_2 %} +{% tab 'Scala 2 and 3' %} + ```scala StringUtils.truncate("Chuck Bartowski", 5) // "Chuck" ``` +{% endtab %} +{% endtabs %} + Importing in Scala is very flexible, and allows us to import _all_ members of an object: +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import StringUtils._ +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala import StringUtils.* truncate("Chuck Bartowski", 5) // "Chuck" @@ -236,8 +462,14 @@ containsWhitespace("Sarah Walker") // true isNullOrEmpty("John Casey") // false ``` +{% endtab %} +{% endtabs %} + or just _some_ members: +{% tabs object_4 %} +{% tab 'Scala 2 and 3' %} + ```scala import StringUtils.{truncate, containsWhitespace} truncate("Charles Carmichael", 7) // "Charles" @@ -245,17 +477,37 @@ containsWhitespace("Captain Awesome") // true isNullOrEmpty("Morgan Grimes") // Not found: isNullOrEmpty (error) ``` +{% endtab %} +{% endtabs %} + Objects can also contain fields, which are also accessed like static members: +{% tabs object_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + ```scala -object MathConstants: +object MathConstants { val PI = 3.14159 val E = 2.71828 +} println(MathConstants.PI) // 3.14159 ``` +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +object MathConstants: + val PI = 3.14159 + val E = 2.71828 + +println(MathConstants.PI) // 3.14159 +``` +{% endtab %} +{% endtabs %} ## Companion objects @@ -266,10 +518,32 @@ A companion class or object can access the private members of its companion. Companion objects are used for methods and values that are not specific to instances of the companion class. For instance, in the following example the class `Circle` has a member named `area` which is specific to each instance, and its companion object has a method named `calculateArea` that’s (a) not specific to an instance, and (b) is available to every instance: +{% tabs companion class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.math._ + +class Circle(val radius: Double) { + def area: Double = Circle.calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala import scala.math.* -case class Circle(radius: Double): +class Circle(val radius: Double): def area: Double = Circle.calculateArea(radius) object Circle: @@ -279,6 +553,9 @@ val circle1 = Circle(5.0) circle1.area ``` +{% endtab %} +{% endtabs %} + In this example the `area` method that’s available to each instance uses the `calculateArea` method that’s defined in the companion object. Once again, `calculateArea` is similar to a static method in Java. Also, because `calculateArea` is private, it can’t be accessed by other code, but as shown, it can be seen by instances of the `Circle` class. @@ -295,6 +572,46 @@ Companion objects can be used for several purposes: Here’s a quick look at how `apply` methods can be used as factory methods to create new objects: +{% tabs companion-use class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person { + var name = "" + var age = 0 + override def toString = s"$name is $age years old" +} + +object Person { + // a one-arg factory method + def apply(name: String): Person = { + var p = new Person + p.name = name + p + } + + // a two-arg factory method + def apply(name: String, age: Int): Person = { + var p = new Person + p.name = name + p.age = age + p + } +} + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +The `unapply` method isn’t covered here, but it’s covered in the [Language Specification](https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns). + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Person: var name = "" @@ -325,9 +642,10 @@ val fred = Person("Fred", 29) //val fred: Person = Fred is 29 years old ``` -The `unapply` method isn’t covered here, but it’s covered in the [Reference documentation][unapply]. - +The `unapply` method isn’t covered here, but it’s covered in the [Reference documentation]({{ site.scala3ref }}/changed-features/pattern-matching.html). +{% endtab %} +{% endtabs %} ## Traits @@ -338,15 +656,49 @@ If you’re familiar with Java, a Scala trait is similar to an interface in Java In a basic use, a trait can be used as an interface, defining only abstract members that will be implemented by other classes: +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Employee { + def id: Int + def firstName: String + def lastName: String +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait Employee: def id: Int def firstName: String def lastName: String ``` + +{% endtab %} +{% endtabs %} + However, traits can also contain concrete members. For instance, the following trait defines two abstract members---`numLegs` and `walk()`---and also has a concrete implementation of a `stop()` method: +{% tabs traits_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasLegs { + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait HasLegs: def numLegs: Int @@ -354,8 +706,26 @@ trait HasLegs: def stop() = println("Stopped walking") ``` +{% endtab %} +{% endtabs %} + Here’s another trait with an abstract member and two concrete implementations: +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasTail { + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait HasTail: def tailColor: String @@ -363,11 +733,30 @@ trait HasTail: def stopTail() = println("Tail is stopped") ``` +{% endtab %} +{% endtabs %} + Notice how each trait only handles very specific attributes and behaviors: `HasLegs` deals only with legs, and `HasTail` deals only with tail-related functionality. Traits let you build small modules like this. Later in your code, classes can mix multiple traits to build larger components: +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class IrishSetter(name: String) extends HasLegs with HasTail { + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class IrishSetter(name: String) extends HasLegs, HasTail: val numLegs = 4 @@ -376,22 +765,37 @@ class IrishSetter(name: String) extends HasLegs, HasTail: override def toString = s"$name is a Dog" ``` +{% endtab %} +{% endtabs %} + Notice that the `IrishSetter` class implements the abstract members that are defined in `HasLegs` and `HasTail`. Now you can create new `IrishSetter` instances: +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val d = new IrishSetter("Big Red") // "Big Red is a Dog" +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala val d = IrishSetter("Big Red") // "Big Red is a Dog" ``` +{% endtab %} +{% endtabs %} + This is just a taste of what you can accomplish with traits. For more details, see the remainder of these modeling lessons. - - ## Abstract classes {% comment %} LATER: If anyone wants to update this section, our comments about abstract classes and traits are on Slack. The biggest points seem to be: + - The `super` of a trait is dynamic - At the use site, people can mix in traits but not classes - It remains easier to extend a class than a trait from Java, if the trait has at least a field @@ -409,20 +813,50 @@ In most situations you’ll use traits, but historically there have been two sit Prior to Scala 3, when a base class needed to take constructor arguments, you’d declare it as an `abstract class`: +{% tabs abstract_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +abstract class Pet(name: String) { + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" +} + +class Dog(name: String, var age: Int) extends Pet(name) { + val greeting = "Woof" +} + +val d = new Dog("Fido", 1) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala abstract class Pet(name: String): def greeting: String def age: Int override def toString = s"My name is $name, I say $greeting, and I’m $age" -class Dog(name: String, age: Int) extends Pet(name): +class Dog(name: String, var age: Int) extends Pet(name): val greeting = "Woof" val d = Dog("Fido", 1) ``` +{% endtab %} +{% endtabs %} + +<h4>Trait Parameters <span class="tag tag-inline">Scala 3 only</span></h4> + However, with Scala 3, traits can now have [parameters][trait-params], so you can now use traits in the same situation: +{% tabs abstract_2 %} + +{% tab 'Scala 3 Only' %} + ```scala trait Pet(name: String): def greeting: String @@ -434,18 +868,23 @@ class Dog(name: String, var age: Int) extends Pet(name): val d = Dog("Fido", 1) ``` + +{% endtab %} +{% endtabs %} + Traits are more flexible to compose---you can mix in multiple traits, but only extend one class---and should be preferred to classes and abstract classes most of the time. The rule of thumb is to use classes whenever you want to create instances of a particular type, and traits when you want to decompose and reuse behaviour. - -## Enums +<h2>Enums <span class="tag tag-inline">Scala 3 only</span></h2> An enumeration can be used to define a type that consists of a finite set of named values (in the section on [FP modeling][fp-modeling], we will see that enums are much more flexible than this). Basic enumerations are used to define sets of constants, like the months in a year, the days in a week, directions like north/south/east/west, and more. - As an example, these enumerations define sets of attributes related to pizzas: +{% tabs enum_1 %} +{% tab 'Scala 3 Only' %} + ```scala enum CrustSize: case Small, Medium, Large @@ -457,18 +896,30 @@ enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` +{% endtab %} +{% endtabs %} + To use them in other code, first import them, and then use them: +{% tabs enum_2 %} +{% tab 'Scala 3 Only' %} + ```scala import CrustSize.* val currentCrustSize = Small ``` +{% endtab %} +{% endtabs %} + Enum values can be compared using equals (`==`), and also matched on: +{% tabs enum_3 %} +{% tab 'Scala 3 Only' %} + ```scala // if/then -if (currentCrustSize == Large) +if currentCrustSize == Large then println("You get a prize!") // match @@ -478,10 +929,16 @@ currentCrustSize match case Large => println("large") ``` +{% endtab %} +{% endtabs %} + ### Additional Enum Features Enumerations can also be parameterized: +{% tabs enum_4 %} +{% tab 'Scala 3 Only' %} + ```scala enum Color(val rgb: Int): case Red extends Color(0xFF0000) @@ -489,8 +946,14 @@ enum Color(val rgb: Int): case Blue extends Color(0x0000FF) ``` +{% endtab %} +{% endtabs %} + And they can also have members (like fields and methods): +{% tabs enum_5 %} +{% tab 'Scala 3 Only' %} + ```scala enum Planet(mass: Double, radius: Double): private final val G = 6.67300E-11 @@ -503,14 +966,23 @@ enum Planet(mass: Double, radius: Double): // more planets here ... ``` +{% endtab %} +{% endtabs %} + ### Compatibility with Java Enums If you want to use Scala-defined enums as Java enums, you can do so by extending the class `java.lang.Enum` (which is imported by default) as follows: +{% tabs enum_6 %} +{% tab 'Scala 3 Only' %} + ```scala enum Color extends Enum[Color] { case Red, Green, Blue } ``` +{% endtab %} +{% endtabs %} + The type parameter comes from the Java `enum` definition, and should be the same as the type of the enum. There’s no need to provide constructor arguments (as defined in the Java API docs) to `java.lang.Enum` when extending it---the compiler generates them automatically. @@ -523,30 +995,84 @@ val res0: Int = -1 The section on [algebraic datatypes][adts] and the [reference documentation][ref-enums] cover enumerations in more detail. - ## Case classes Case classes are used to model immutable data structures. Take the following example: -```scala + +{% tabs case-classes_1 %} +{% tab 'Scala 2 and 3' %} + +```scala: case class Person(name: String, relation: String) ``` + +{% endtab %} +{% endtabs %} + Since we declare `Person` as a case class, the fields `name` and `relation` are public and immutable by default. We can create instances of case classes as follows: + +{% tabs case-classes_2 %} +{% tab 'Scala 2 and 3' %} + ```scala val christina = Person("Christina", "niece") ``` + +{% endtab %} +{% endtabs %} + Note that the fields can’t be mutated: + +{% tabs case-classes_3 %} +{% tab 'Scala 2 and 3' %} + ```scala christina.name = "Fred" // error: reassignment to val ``` + +{% endtab %} +{% endtabs %} + Since the fields of a case class are assumed to be immutable, the Scala compiler can generate many helpful methods for you: -* An `unapply` method is generated, which allows you to perform pattern matching on a case class (that is, `case Person(n, r) => ...`). -* A `copy` method is generated in the class, which is very useful to create modified copies of an instance. -* `equals` and `hashCode` methods using structural equality are generated, allowing you to use instances of case classes in `Map`s. -* A default `toString` method is generated, which is helpful for debugging. + +- An `unapply` method is generated, which allows you to perform pattern matching on a case class (that is, `case Person(n, r) => ...`). +- A `copy` method is generated in the class, which is very useful to create modified copies of an instance. +- `equals` and `hashCode` methods using structural equality are generated, allowing you to use instances of case classes in `Map`s. +- A default `toString` method is generated, which is helpful for debugging. These additional features are demonstrated in the below example: + +{% tabs case-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// Case classes can be used as patterns +christina match { + case Person(n, r) => println("name is " + n) +} + +// `equals` and `hashCode` methods generated for you +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// `toString` method +println(christina) // Person(Christina,niece) + +// built-in `copy` method +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// result: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) + +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala // Case classes can be used as patterns christina match @@ -567,19 +1093,20 @@ val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) // cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) ``` +{% endtab %} +{% endtabs %} ### Support for functional programming As mentioned, case classes support functional programming (FP): -- In FP you try to avoid mutating data structures. +- In FP, you try to avoid mutating data structures. It thus makes sense that constructor fields default to `val`. Since instances of case classes can’t be changed, they can easily be shared without fearing mutation or race conditions. - Instead of mutating an instance, you can use the `copy` method as a template to create a new (potentially changed) instance. This process can be referred to as “update as you copy.” - Having an `unapply` method auto-generated for you also lets case classes be used in advanced ways with pattern matching. - {% comment %} NOTE: We can use this following text, if desired. If it’s used, it needs to be updated a little bit. @@ -589,20 +1116,58 @@ A great thing about a case class is that it automatically generates an `unapply` To demonstrate this, imagine that you have this trait: +{% tabs case-classes_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Person { + def name: String +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala trait Person: def name: String ``` +{% endtab %} +{% endtabs %} + Then, create these case classes to extend that trait: +{% tabs case-classes_6 %} +{% tab 'Scala 2 and 3' %} + ```scala case class Student(name: String, year: Int) extends Person case class Teacher(name: String, specialty: String) extends Person ``` +{% endtab %} +{% endtabs %} + Because those are defined as case classes---and they have built-in `unapply` methods---you can write a match expression like this: +{% tabs case-classes_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def getPrintableString(p: Person): String = p match { + case Student(name, year) => + s"$name is a student in Year $year." + case Teacher(name, whatTheyTeach) => + s"$name teaches $whatTheyTeach." +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala def getPrintableString(p: Person): String = p match case Student(name, year) => @@ -611,13 +1176,22 @@ def getPrintableString(p: Person): String = p match s"$name teaches $whatTheyTeach." ``` +{% endtab %} +{% endtabs %} + Notice these two patterns in the `case` statements: +{% tabs case-classes_8 %} +{% tab 'Scala 2 and 3' %} + ```scala case Student(name, year) => case Teacher(name, whatTheyTeach) => ``` +{% endtab %} +{% endtabs %} + Those patterns work because `Student` and `Teacher` are defined as case classes that have `unapply` methods whose type signature conforms to a certain standard. Technically, the specific type of pattern matching shown in these examples is known as a _constructor pattern_. @@ -626,13 +1200,30 @@ Technically, the specific type of pattern matching shown in these examples is kn To show how that code works, create an instance of `Student` and `Teacher`: +{% tabs case-classes_9 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s = new Student("Al", 1) +val t = new Teacher("Bob Donnan", "Mathematics") +``` + +{% endtab %} +{% tab 'Scala 3' %} + ```scala val s = Student("Al", 1) val t = Teacher("Bob Donnan", "Mathematics") ``` +{% endtab %} +{% endtabs %} + Next, this is what the output looks like in the REPL when you call `getPrintableString` with those two instances: +{% tabs case-classes_10 %} +{% tab 'Scala 2 and 3' %} + ```scala scala> getPrintableString(s) res0: String = Al is a student in Year 1. @@ -641,6 +1232,9 @@ scala> getPrintableString(t) res1: String = Bob Donnan teaches Mathematics. ``` +{% endtab %} +{% endtabs %} + > All of this content on `unapply` methods and extractors is a little advanced for an introductory book like this, but because case classes are an important FP topic, it seems better to cover them, rather than skipping over them. #### Add pattern matching to any type with unapply @@ -648,14 +1242,49 @@ res1: String = Bob Donnan teaches Mathematics. A great Scala feature is that you can add pattern matching to any type by writing your own `unapply` method. As an example, this class defines an `unapply` method in its companion object: +{% tabs case-classes_11 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person(var name: String, var age: Int) +object Person { + def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala class Person(var name: String, var age: Int) object Person: def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) ``` +{% endtab %} +{% endtabs %} + Because it defines an `unapply` method, and because that method returns a tuple, you can now use `Person` with a `match` expression: +{% tabs case-classes_12 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val p = new Person("Astrid", 33) + +p match { + case Person(n,a) => println(s"name: $n, age: $a") + case null => println("No match") +} + +// that code prints: "name: Astrid, age: 33" +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala val p = Person("Astrid", 33) @@ -665,9 +1294,11 @@ p match // that code prints: "name: Astrid, age: 33" ``` -{% endcomment %} +{% endtab %} +{% endtabs %} +{% endcomment %} ## Case objects @@ -677,6 +1308,9 @@ They’re particularly useful whenever you need a singleton object that needs a Case objects are useful when you need to pass immutable messages around. For instance, if you’re working on a music player project, you’ll create a set of commands or messages like this: +{% tabs case-objects_1 %} +{% tab 'Scala 2 and 3' %} + ```scala sealed trait Message case class PlaySong(name: String) extends Message @@ -685,8 +1319,27 @@ case class DecreaseVolume(amount: Int) extends Message case object StopPlaying extends Message ``` +{% endtab %} +{% endtabs %} + Then in other parts of your code, you can write methods like this, which use pattern matching to handle the incoming message (assuming the methods `playSong`, `changeVolume`, and `stopPlayingSong` are defined somewhere else): +{% tabs case-objects_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def handleMessages(message: Message): Unit = message match { + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + ```scala def handleMessages(message: Message): Unit = message match case PlaySong(name) => playSong(name) @@ -694,6 +1347,10 @@ def handleMessages(message: Message): Unit = message match case DecreaseVolume(amount) => changeVolume(-amount) case StopPlaying => stopPlayingSong() ``` + +{% endtab %} +{% endtabs %} + [ref-enums]: {{ site.scala3ref }}/enums/enums.html [adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} [fp-modeling]: {% link _overviews/scala3-book/domain-modeling-fp.md %} diff --git a/_overviews/scala3-book/first-look-at-types.md b/_overviews/scala3-book/first-look-at-types.md index c62e2cc9dd..5cdb32e57f 100644 --- a/_overviews/scala3-book/first-look-at-types.md +++ b/_overviews/scala3-book/first-look-at-types.md @@ -2,13 +2,13 @@ title: A First Look at Types type: chapter description: This page provides a brief introduction to Scala's built-in data types, including Int, Double, String, Long, Any, AnyRef, Nothing, and Null. +languages: [ru, zh-cn] num: 17 previous-page: taste-summary -next-page: control-structures +next-page: string-interpolation --- - ## All values have a type In Scala, all values have a type, including numerical values and functions. @@ -16,21 +16,19 @@ The diagram below illustrates a subset of the type hierarchy. <a href="{{ site.baseurl }}/resources/images/scala3-book/hierarchy.svg"><img style="width:100%" src="{{ site.baseurl }}/resources/images/scala3-book/hierarchy.svg" alt="Scala 3 Type Hierarchy"></a> - ## Scala type hierarchy `Any` is the supertype of all types, also called the **top type**. It defines certain universal methods such as `equals`, `hashCode`, and `toString`. -The top-type `Any` has a subtype [`Matchable`][matchable], which is used to mark all types that we can perform pattern matching on. -It is important to guarantee a property call _"parametricity"_. +The top-type `Any` has a subtype [`Matchable`][matchable], which is used to mark all types that we can perform pattern matching on. It is important to guarantee a property call _"parametricity"_. We will not go into details here, but in summary, it means that we cannot pattern match on values of type `Any`, but only on values that are a subtype of `Matchable`. The [reference documentation][matchable] contains more information about `Matchable`. `Matchable` has two important subtypes: `AnyVal` and `AnyRef`. *`AnyVal`* represents value types. -There are a couple of predefined value types and they are non-nullable: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, and `Boolean`. +There are a couple of 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 we can refer to as: `()`. @@ -42,23 +40,32 @@ If Scala is used in the context of a Java runtime environment, `AnyRef` correspo In statement-based languages, `void` is used for methods that don’t return anything. If you write methods in Scala that have no return value, such as the following method, `Unit` is used for the same purpose: +{% tabs unit %} +{% tab 'Scala 2 and 3' for=unit %} ```scala def printIt(a: Any): Unit = println(a) ``` +{% endtab %} +{% endtabs %} Here’s an example that demonstrates that strings, integers, characters, boolean values, and functions are all instances of `Any` and can be treated just like every other object: +{% tabs any %} +{% tab 'Scala 2 and 3' for=any %} ```scala val list: List[Any] = List( "a string", 732, // an integer 'c', // a character + '\'', // a character with a backslash escape true, // a boolean value () => "an anonymous function returning a string" ) list.foreach(element => println(element)) ``` +{% endtab %} +{% endtabs %} The code defines a value `list` of type `List[Any]`. The list is initialized with elements of various types, but each is an instance of `scala.Any`, so we can add them to the list. @@ -69,6 +76,7 @@ Here’s the output of the program: a string 732 c +' true <function> ``` @@ -78,6 +86,8 @@ true As shown above, Scala’s numeric types extend `AnyVal`, and they’re all full-blown objects. These examples show how to declare variables of these numeric types: +{% tabs anyval %} +{% tab 'Scala 2 and 3' for=anyval %} ```scala val b: Byte = 1 val i: Int = 1 @@ -86,149 +96,157 @@ val s: Short = 1 val d: Double = 2.0 val f: Float = 3.0 ``` +{% endtab %} +{% endtabs %} In the first four examples, if you don’t explicitly specify a type, the number `1` will default to an `Int`, so if you want one of the other data types---`Byte`, `Long`, or `Short`---you need to explicitly declare those types, as shown. Numbers with a decimal (like 2.0) will default to a `Double`, so if you want a `Float` you need to declare a `Float`, as shown in the last example. Because `Int` and `Double` are the default numeric types, you typically create them without explicitly declaring the data type: +{% tabs anynum %} +{% tab 'Scala 2 and 3' for=anynum %} ```scala val i = 123 // defaults to Int val x = 1.0 // defaults to Double ``` +{% endtab %} +{% endtabs %} In your code you can also append the characters `L`, `D`, and `F` (and their lowercase equivalents) to numbers to specify that they are `Long`, `Double`, or `Float` values: +{% tabs type-post %} +{% tab 'Scala 2 and 3' for=type-post %} ```scala val x = 1_000L // val x: Long = 1000 val y = 2.2D // val y: Double = 2.2 -val z = 3.3F // val z: Float = 3.3 -``` - -Scala also has `String` and `Char` types, which you can generally declare with the implicit form: - -```scala -val s = "Bill" -val c = 'a' -``` - -As shown, enclose strings in double-quotes---or triple-quotes for multiline strings---and enclose a character in single-quotes. - -Those data types and their ranges are: - -| Data Type | Possible Values | -| ------------- | --------------- | -| Boolean | `true` or `false` | -| Byte | 8-bit signed two’s complement integer (-2^7 to 2^7-1, inclusive)<br/>-128 to 127 | -| Short | 16-bit signed two’s complement integer (-2^15 to 2^15-1, inclusive)<br/>-32,768 to 32,767 -| Int | 32-bit two’s complement integer (-2^31 to 2^31-1, inclusive)<br/>-2,147,483,648 to 2,147,483,647 | -| Long | 64-bit two’s complement integer (-2^63 to 2^63-1, inclusive)<br/>(-2^63 to 2^63-1, inclusive) | -| Float | 32-bit IEEE 754 single-precision float<br/>1.40129846432481707e-45 to 3.40282346638528860e+38 | -| Double | 64-bit IEEE 754 double-precision float<br/>4.94065645841246544e-324 to 1.79769313486231570e+308 | -| Char | 16-bit unsigned Unicode character (0 to 2^16-1, inclusive)<br/>0 to 65,535 | -| String | a sequence of `Char` | - - - -## `BigInt` and `BigDecimal` - -When you need really large numbers, use the `BigInt` and `BigDecimal` types: - -```scala -val a = BigInt(1_234_567_890_987_654_321L) -val b = BigDecimal(123_456.789) +val z = -3.3F // val z: Float = -3.3 ``` -Where `Double` and `Float` are approximate decimal numbers, `BigDecimal` is used for precise arithmetic, such as when working with currency. - -A great thing about `BigInt` and `BigDecimal` is that they support all the operators you’re used to using with numeric types: +You may also use hexadecimal notation to format integer numbers (normally `Int`, but which also support the +`L` suffix to specify that they are `Long`): ```scala -val b = BigInt(1234567890) // scala.math.BigInt = 1234567890 -val c = b + b // scala.math.BigInt = 2469135780 -val d = b * b // scala.math.BigInt = 1524157875019052100 +val a = 0xACE // val a: Int = 2766 +val b = 0xfd_3aL // val b: Long = 64826 ``` - - -## Two notes about strings - -Scala strings are similar to Java strings, but they have two great additional features: - -- They support string interpolation -- It’s easy to create multiline strings - -### String interpolation - -String interpolation provides a very readable way to use variables inside strings. -For instance, given these three variables: - +Scala supports many different ways to format the same floating point number, e.g. ```scala -val firstName = "John" -val mi = 'C' -val lastName = "Doe" +val q = .25 // val q: Double = 0.25 +val r = 2.5e-1 // val r: Double = 0.25 +val s = .0025e2F // val s: Float = 0.25 ``` +{% endtab %} +{% endtabs %} -You can combine those variables in a string like this: - -```scala -println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" -``` - -Just precede the string with the letter `s`, and then put a `$` symbol before your variable names inside the string. - -To enclose potentially larger expressions inside a string, put them in curly braces: +Scala also has `String` and `Char` types, which you can generally declare with the implicit form: +{% tabs type-string %} +{% tab 'Scala 2 and 3' for=type-string %} ```scala -println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" -val x = -1 -println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +val s = "Bill" +val c = 'a' ``` +{% endtab %} +{% endtabs %} +As shown, enclose strings in double-quotes---or triple-quotes for multiline strings---and enclose a character in single-quotes. -#### Other interpolators - -The `s` that you place before the string is just one possible interpolator. -If you use an `f` instead of an `s`, you can use `printf`-style formatting syntax in the string. -Furthermore, a string interpolator is a just special method and it is possible to define your own. -For instance, some database libraries define the very powerful `sql` interpolator. - - -### Multiline strings - -Multiline strings are created by including the string inside three double-quotes: +Those data types and their ranges are: +| Data Type | Possible Values | +|-----------|--------------------------------------------------------------------------------------------------| +| Boolean | `true` or `false` | +| Byte | 8-bit signed two’s complement integer (-2^7 to 2^7-1, inclusive)<br/>-128 to 127 | +| Short | 16-bit signed two’s complement integer (-2^15 to 2^15-1, inclusive)<br/>-32,768 to 32,767 | +| Int | 32-bit two’s complement integer (-2^31 to 2^31-1, inclusive)<br/>-2,147,483,648 to 2,147,483,647 | +| Long | 64-bit two’s complement integer (-2^63 to 2^63-1, inclusive)<br/>(-2^63 to 2^63-1, inclusive) | +| Float | 32-bit IEEE 754 single-precision float<br/>1.40129846432481707e-45 to 3.40282346638528860e+38 | +| Double | 64-bit IEEE 754 double-precision float<br/>4.94065645841246544e-324 to 1.79769313486231570e+308 | +| Char | 16-bit unsigned Unicode character (0 to 2^16-1, inclusive)<br/>0 to 65,535 | +| String | a sequence of `Char` | + +## Strings + +Scala strings are similar to Java strings though unlike Java (at least before Java 15), +it's easy to create multiline strings with triple quotes: + +{% tabs string-mlines1 %} +{% tab 'Scala 2 and 3' for=string-mlines1 %} ```scala val quote = """The essence of Scala: Fusion of functional and object-oriented programming in a typed setting.""" ``` +{% endtab %} +{% endtabs %} One drawback of this basic approach is that the lines after the first line are indented, and look like this: +{% tabs string-mlines2 %} +{% tab 'Scala 2 and 3' for=string-mlines2 %} ```scala "The essence of Scala: Fusion of functional and object-oriented programming in a typed setting." ``` +{% endtab %} +{% endtabs %} When spacing is important, put a `|` symbol in front of all lines after the first line, and call the `stripMargin` method after the string: +{% tabs string-mlines3 %} +{% tab 'Scala 2 and 3' for=string-mlines3 %} ```scala val quote = """The essence of Scala: |Fusion of functional and object-oriented |programming in a typed setting.""".stripMargin ``` +{% endtab %} +{% endtabs %} Now all of the lines are left-justified inside the string: +{% tabs string-mlines4 %} +{% tab 'Scala 2 and 3' for=string-mlines4 %} ```scala "The essence of Scala: Fusion of functional and object-oriented programming in a typed setting." ``` +{% endtab %} +{% endtabs %} +Scala strings also support powerful string interpolation methods, which we'll talk about +in the [next chapter][string-interpolation]. +## `BigInt` and `BigDecimal` + +When you need really large numbers, use the `BigInt` and `BigDecimal` types: + +{% tabs type-bigint %} +{% tab 'Scala 2 and 3' for=type-bigint %} +```scala +val a = BigInt(1_234_567_890_987_654_321L) +val b = BigDecimal(123_456.789) +``` +{% endtab %} +{% endtabs %} + +Where `Double` and `Float` are approximate decimal numbers, `BigDecimal` is used for precise arithmetic, such as when working with currency. + +A great thing about `BigInt` and `BigDecimal` is that they support all the operators you’re used to using with numeric types: + +{% tabs type-bigint2 %} +{% tab 'Scala 2 and 3' for=type-bigint2 %} +```scala +val b = BigInt(1234567890) // scala.math.BigInt = 1234567890 +val c = b + b // scala.math.BigInt = 2469135780 +val d = b * b // scala.math.BigInt = 1524157875019052100 +``` +{% endtab %} +{% endtabs %} ## Type casting @@ -237,28 +255,33 @@ Value types can be cast in the following way: For example: +{% tabs cast1 %} +{% tab 'Scala 2 and 3' for=cast1 %} ```scala -val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) +val b: Byte = 127 +val i: Int = b // 127 val face: Char = '☺' val number: Int = face // 9786 ``` +{% endtab %} +{% endtabs %} -Casting is unidirectional. -This will not compile: +You can only cast to a type if there is no loss of information. Otherwise, you need to be explicit about the cast: -``` +{% tabs cast2 %} +{% tab 'Scala 2 and 3' for=cast2 %} +```scala val x: Long = 987654321 -val y: Float = x // 9.8765434E8 -val z: Long = y // Does not conform +val y: Float = x.toFloat // 9.8765434E8 (note that `.toFloat` is required because the cast results in precision loss) +val z: Long = y // Error ``` +{% endtab %} +{% endtabs %} 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**. @@ -274,7 +297,7 @@ Alternatives to `null` are discussed in the [Functional Programming chapter][fp] [reference]: {{ site.scala3ref }}/overview.html [matchable]: {{ site.scala3ref }}/other-new-features/matchable.html -[interpolation]: {% link _overviews/core/string-interpolation.md %} [fp]: {% link _overviews/scala3-book/fp-intro.md %} +[string-interpolation]: {% link _overviews/scala3-book/string-interpolation.md %} [option-api]: https://scala-lang.org/api/3.x/scala/Option.html -[safe-null]: {{ site.scala3ref }}/other-new-features/explicit-nulls.html +[safe-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html diff --git a/_overviews/scala3-book/fp-functional-error-handling.md b/_overviews/scala3-book/fp-functional-error-handling.md index 41410e5cf1..e22fc2b4bb 100644 --- a/_overviews/scala3-book/fp-functional-error-handling.md +++ b/_overviews/scala3-book/fp-functional-error-handling.md @@ -2,7 +2,8 @@ title: Functional Error Handling type: section description: This section provides an introduction to functional error handling in Scala 3. -num: 45 +languages: [ru, zh-cn] +num: 46 previous-page: fp-functions-are-values next-page: fp-summary --- @@ -29,6 +30,21 @@ While this first example doesn’t deal with null values, it’s a good way to i Imagine that you want to write a method that makes it easy to convert strings to integer values, and you want an elegant way to handle the exception that’s thrown when your method gets a string like `"Hello"` instead of `"1"`. A first guess at such a method might look like this: + +{% tabs fp-java-try class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Int = + try { + Integer.parseInt(s.trim) + } catch { + case e: Exception => 0 + } +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala def makeInt(s: String): Int = try @@ -36,6 +52,9 @@ def makeInt(s: String): Int = catch case e: Exception => 0 ``` +{% endtab %} + +{% endtabs %} If the conversion works, this method returns the correct `Int` value, but if it fails, the method returns `0`. This might be okay for some purposes, but it’s not really accurate. @@ -56,6 +75,21 @@ The `Some` and `None` classes are subclasses of `Option`, so the solution works Here’s the revised version of `makeInt`: + +{% tabs fp--try-option class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Option[Int] = + try { + Some(Integer.parseInt(s.trim)) + } catch { + case e: Exception => None + } +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala def makeInt(s: String): Option[Int] = try @@ -63,16 +97,25 @@ def makeInt(s: String): Option[Int] = catch case e: Exception => None ``` +{% endtab %} + +{% endtabs %} This code can be read as, “When the given string converts to an integer, return the `Int` wrapped inside a `Some`, such as `Some(1)`. When the string can’t be converted to an integer, an exception is thrown and caught, and the method returns a `None` value.” These examples show how `makeInt` works: +{% tabs fp-try-option-example %} + +{% tab 'Scala 2 and 3' %} ```scala val a = makeInt("1") // Some(1) val b = makeInt("one") // None ``` +{% endtab %} + +{% endtabs %} As shown, the string `"1"` results in a `Some(1)`, and the string `"one"` results in a `None`. This is the essence of the `Option` approach to error handling. @@ -100,11 +143,26 @@ There are two common answers, depending on your needs: One possible solution is to use a `match` expression: +{% tabs fp-option-match class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn’t work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala makeInt(x) match case Some(i) => println(i) case None => println("That didn’t work.") ``` +{% endtab %} + +{% endtabs %} In this example, if `x` can be converted to an `Int`, the expression on the right-hand side of the first `case` clause is evaluated; if `x` can’t be converted to an `Int`, the expression on the right-hand side of the second `case` clause is evaluated. @@ -116,6 +174,22 @@ Another common solution is to use a `for` expression---i.e., the `for`/`yield` c For instance, imagine that you want to convert three strings to integer values, and then add them together. This is how you do that with a `for` expression and `makeInt`: + +{% tabs fp-for-comprehension class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val y = for a <- makeInt(stringA) @@ -124,6 +198,9 @@ val y = for yield a + b + c ``` +{% endtab %} + +{% endtabs %} After that expression runs, `y` will be one of two things: @@ -132,27 +209,55 @@ After that expression runs, `y` will be one of two things: You can test this for yourself: +{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %} + +{% tab 'Scala 2' %} ```scala val stringA = "1" val stringB = "2" val stringC = "3" -val y = for +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for a <- makeInt(stringA) b <- makeInt(stringB) c <- makeInt(stringC) yield a + b + c ``` +{% endtab %} + +{% endtabs %} With that sample data, the variable `y` will have the value `Some(6)`. To see the failure case, change any of those strings to something that won’t convert to an integer. When you do that, you’ll see that `y` is a `None`: +{% tabs fp-for-comprehension-failure-result %} + +{% tab 'Scala 2 and 3' %} ```scala y: Option[Int] = None ``` +{% endtab %} + +{% endtabs %} ## Thinking of Option as a container @@ -178,10 +283,16 @@ They have many of the methods you’d expect from a collection class, including This raises an interesting question: What will these two values print, if anything? +{% tabs fp-option-methods-evaluation %} + +{% tab 'Scala 2 and 3' %} ```scala makeInt("1").foreach(println) makeInt("x").foreach(println) ``` +{% endtab %} + +{% endtabs %} Answer: The first example prints the number `1`, and the second example doesn’t print anything. The first example prints `1` because: @@ -204,12 +315,33 @@ Somewhere in Scala’s history, someone noted that the first example (the `Some` *But* despite having two different possible outcomes, the great thing with `Option` is that there’s really just one path: The code you write to handle the `Some` and `None` possibilities is the same in both cases. The `foreach` examples look like this: +{% tabs fp-another-option-method-example %} + +{% tab 'Scala 2 and 3' %} ```scala makeInt(aString).foreach(println) ``` +{% endtab %} + +{% endtabs %} And the `for` expression looks like this: +{% tabs fp-another-for-comprehension-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val y = for a <- makeInt(stringA) @@ -218,16 +350,34 @@ val y = for yield a + b + c ``` +{% endtab %} + +{% endtabs %} With exceptions you have to worry about handling branching logic, but because `makeInt` returns a value, you only have to write one piece of code to handle both the Happy and Unhappy Paths, and that simplifies your code. Indeed, the only time you have to think about whether the `Option` is a `Some` or a `None` is when you handle the result value, such as in a `match` expression: +{% tabs fp-option-match-handle class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn't work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala makeInt(x) match case Some(i) => println(i) case None => println("That didn't work.") ``` +{% endtab %} + +{% endtabs %} > There are several other ways to handle `Option` values. > See the reference documentation for more details. @@ -239,6 +389,9 @@ makeInt(x) match Getting back to `null` values, a place where a `null` value can silently creep into your code is with a class like this: +{% tabs fp=case-class-nulls %} + +{% tab 'Scala 2 and 3' %} ```scala class Address( var street1: String, @@ -248,10 +401,29 @@ class Address( var zip: String ) ``` +{% endtab %} + +{% endtabs %} While every address on Earth has a `street1` value, the `street2` value is optional. As a result, the `street2` field can be assigned a `null` value: + +{% tabs fp-case-class-nulls-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + null, // <-- D’oh! A null value! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val santa = Address( "1 Main Street", @@ -261,10 +433,17 @@ val santa = Address( "99705" ) ``` +{% endtab %} + +{% endtabs %} Historically, developers have used blank strings and null values in this situation, both of which are hacks to work around the root problem: `street2` is an *optional* field. In Scala---and other modern languages---the correct solution is to declare up front that `street2` is optional: + +{% tabs fp-case-class-with-options %} + +{% tab 'Scala 2 and 3' %} ```scala class Address( var street1: String, @@ -274,9 +453,27 @@ class Address( var zip: String ) ``` +{% endtab %} + +{% endtabs %} Now developers can write more accurate code like this: +{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + None, // 'street2' has no value + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val santa = Address( "1 Main Street", @@ -286,9 +483,27 @@ val santa = Address( "99705" ) ``` +{% endtab %} + +{% endtabs %} or this: +{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala val santa = Address( "123 Main Street", @@ -298,6 +513,9 @@ val santa = Address( "99676" ) ``` +{% endtab %} + +{% endtabs %} diff --git a/_overviews/scala3-book/fp-functions-are-values.md b/_overviews/scala3-book/fp-functions-are-values.md index d2766c65fe..e656d3c9f9 100644 --- a/_overviews/scala3-book/fp-functions-are-values.md +++ b/_overviews/scala3-book/fp-functions-are-values.md @@ -2,7 +2,8 @@ title: Functions Are Values type: section description: This section looks at the use of functions as values in functional programming. -num: 44 +languages: [ru, zh-cn] +num: 45 previous-page: fp-pure-functions next-page: fp-functional-error-handling --- @@ -13,12 +14,18 @@ While every programming language ever created probably lets you write pure funct This feature has many benefits, the most common of which are (a) you can define methods to accept function parameters, and (b) you can pass functions as parameters into methods. You’ve seen this in multiple places in this book, whenever methods like `map` and `filter` are demonstrated: +{% tabs fp-function-as-values-anonymous %} + +{% tab 'Scala 2 and 3' %} ```scala val nums = (1 to 10).toList val doubles = nums.map(_ * 2) // double each value val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4) ``` +{% endtab %} + +{% endtabs %} In those examples, anonymous functions are passed into `map` and `filter`. @@ -26,6 +33,9 @@ In those examples, anonymous functions are passed into `map` and `filter`. In addition to passing anonymous functions into `filter` and `map`, you can also supply them with *methods*: +{% tabs fp-function-as-values-defined %} + +{% tab 'Scala 2 and 3' %} ```scala // two methods def double(i: Int): Int = i * 2 @@ -34,6 +44,9 @@ def underFive(i: Int): Boolean = i < 5 // pass those methods into filter and map val doubles = nums.filter(underFive).map(double) ``` +{% endtab %} + +{% endtabs %} This ability to treat methods and functions as values is a powerful feature that functional programming languages provide. @@ -46,29 +59,53 @@ This ability to treat methods and functions as values is a powerful feature that As you saw in those examples, this is an anonymous function: +{% tabs fp-anonymous-function-short %} + +{% tab 'Scala 2 and 3' %} ```scala _ * 2 ``` +{% endtab %} + +{% endtabs %} As shown in the [higher-order functions][hofs] discussion, that’s a shorthand version of this syntax: +{% tabs fp-anonymous-function-full %} + +{% tab 'Scala 2 and 3' %} ```scala (i: Int) => i * 2 ``` +{% endtab %} + +{% endtabs %} Functions like these are called “anonymous” because they don’t have names. If you want to give one a name, just assign it to a variable: +{% tabs fp-function-assignement %} + +{% tab 'Scala 2 and 3' %} ```scala val double = (i: Int) => i * 2 ``` +{% endtab %} + +{% endtabs %} Now you have a named function, one that’s assigned to a variable. You can use this function just like you use a method: +{% tabs fp-function-used-like-method %} + +{% tab 'Scala 2 and 3' %} ```scala double(2) // 4 ``` +{% endtab %} + +{% endtabs %} In most scenarios it doesn’t matter if `double` is a function or a method; Scala lets you treat them the same way. Behind the scenes, the Scala technology that lets you treat methods just like functions is known as [Eta Expansion][eta]. @@ -78,6 +115,9 @@ And as you’ve seen in the `map` and `filter` examples throughout this book, th If you’re not comfortable with the process of passing functions as parameters into other functions, here are a few more examples you can experiment with: +{% tabs fp-function-as-values-example %} + +{% tab 'Scala 2 and 3' %} ```scala List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE) List("bob", "joe").map(_.capitalize) // List(Bob, Joe) @@ -96,6 +136,9 @@ nums.sortWith(_ > _) // List(11, 7, 5, 3, 1) nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5) ``` +{% endtab %} + +{% endtabs %} [hofs]: {% link _overviews/scala3-book/fun-hofs.md %} diff --git a/_overviews/scala3-book/fp-immutable-values.md b/_overviews/scala3-book/fp-immutable-values.md index c5a4cc3eb3..2226ceac95 100644 --- a/_overviews/scala3-book/fp-immutable-values.md +++ b/_overviews/scala3-book/fp-immutable-values.md @@ -2,7 +2,8 @@ title: Immutable Values type: section description: This section looks at the use of immutable values in functional programming. -num: 42 +languages: [ru, zh-cn] +num: 43 previous-page: fp-what-is-fp next-page: fp-pure-functions --- @@ -22,11 +23,17 @@ This is where higher-order functions like `map` and `filter` come in. For example, imagine that you have a list of names---a `List[String]`---that are all in lowercase, and you want to find all the names that begin with the letter `"j"`, and then you want to capitalize those names. In FP you write this code: +{% tabs fp-list %} + +{% tab 'Scala 2 and 3' %} ```scala val a = List("jane", "jon", "mary", "joe") val b = a.filter(_.startsWith("j")) .map(_.capitalize) ``` +{% endtab %} + +{% endtabs %} As shown, you don’t mutate the original list `a`. Instead, you apply filtering and transformation functions to `a` to create a new collection, and assign that result to the new immutable variable `b`. @@ -34,32 +41,57 @@ Instead, you apply filtering and transformation functions to `a` to create a new Similarly, in FP you don’t create classes with mutable `var` constructor parameters. That is, you don’t write this: +{% tabs fp--class-variables %} + +{% tab 'Scala 2 and 3' %} ```scala // don’t do this in FP class Person(var firstName: String, var lastName: String) --- --- ``` +{% endtab %} + +{% endtabs %} Instead, you typically create `case` classes, whose constructor parameters are `val` by default: +{% tabs fp-immutable-case-class %} + +{% tab 'Scala 2 and 3' %} ```scala case class Person(firstName: String, lastName: String) ``` +{% endtab %} + +{% endtabs %} Now you create a `Person` instance as a `val` field: +{% tabs fp-case-class-creation %} + +{% tab 'Scala 2 and 3' %} ```scala val reginald = Person("Reginald", "Dwight") ``` +{% endtab %} + +{% endtabs %} Then, when you need to make a change to the data, you use the `copy` method that comes with a `case` class to “update the data as you make a copy,” like this: + +{% tabs fp-case-class-copy %} + +{% tab 'Scala 2 and 3' %} ```scala val elton = reginald.copy( firstName = "Elton", // update the first name lastName = "John" // update the last name ) ``` +{% endtab %} + +{% endtabs %} There are other techniques for working with immutable collections and variables, but hopefully these examples give you a taste of the techniques. diff --git a/_overviews/scala3-book/fp-intro.md b/_overviews/scala3-book/fp-intro.md index 03aed71900..99f02ca759 100644 --- a/_overviews/scala3-book/fp-intro.md +++ b/_overviews/scala3-book/fp-intro.md @@ -2,14 +2,15 @@ title: Functional Programming type: chapter description: This chapter provides an introduction to functional programming in Scala 3. -num: 40 +languages: [ru, zh-cn] +num: 41 previous-page: collections-summary next-page: fp-what-is-fp --- Scala lets you write code in an object-oriented programming (OOP) style, a functional programming (FP) style, and also in a hybrid style---using both approaches in combination. -[As Martin Odersky has stated](https://twitter.com/alexelcu/status/996408359514525696), the essence of Scala is a fusion of functional and object-oriented programming in a typed setting: +As stated by Martin Odersky, the creator of Scala, the essence of Scala is a fusion of functional and object-oriented programming in a typed setting: - Functions for the logic - Objects for the modularity diff --git a/_overviews/scala3-book/fp-pure-functions.md b/_overviews/scala3-book/fp-pure-functions.md index 82199b14e0..641eee59ce 100644 --- a/_overviews/scala3-book/fp-pure-functions.md +++ b/_overviews/scala3-book/fp-pure-functions.md @@ -2,7 +2,8 @@ title: Pure Functions type: section description: This section looks at the use of pure functions in functional programming. -num: 43 +languages: [ru, zh-cn] +num: 44 previous-page: fp-immutable-values next-page: fp-functions-are-values --- @@ -86,17 +87,39 @@ These topics are beyond the scope of this document, so to keep things simple it To write pure functions in Scala, just write them using Scala’s method syntax (though you can also use Scala’s function syntax, as well). For instance, here’s a pure function that doubles the input value it’s given: + +{% tabs fp-pure-function %} + +{% tab 'Scala 2 and 3' %} ```scala def double(i: Int): Int = i * 2 ``` +{% endtab %} + +{% endtabs %} If you’re comfortable with recursion, here’s a pure function that calculates the sum of a list of integers: +{% tabs fp-pure-recursive-function class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(xs: List[Int]): Int = xs match { + case Nil => 0 + case head :: tail => head + sum(tail) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala def sum(xs: List[Int]): Int = xs match case Nil => 0 case head :: tail => head + sum(tail) ``` +{% endtab %} + +{% endtabs %} If you understand that code, you’ll see that it meets the pure function definition. diff --git a/_overviews/scala3-book/fp-summary.md b/_overviews/scala3-book/fp-summary.md index 9857501e84..7695293e9d 100644 --- a/_overviews/scala3-book/fp-summary.md +++ b/_overviews/scala3-book/fp-summary.md @@ -2,7 +2,8 @@ title: Summary type: section description: This section summarizes the previous functional programming sections. -num: 46 +languages: [ru, zh-cn] +num: 47 previous-page: fp-functional-error-handling next-page: types-introduction --- diff --git a/_overviews/scala3-book/fp-what-is-fp.md b/_overviews/scala3-book/fp-what-is-fp.md index 0b380447c8..2eca848e60 100644 --- a/_overviews/scala3-book/fp-what-is-fp.md +++ b/_overviews/scala3-book/fp-what-is-fp.md @@ -2,7 +2,8 @@ title: What is Functional Programming? type: section description: This section provides an answer to the question, what is functional programming? -num: 41 +languages: [ru, zh-cn] +num: 42 previous-page: fp-intro next-page: fp-immutable-values --- diff --git a/_overviews/scala3-book/fun-anonymous-functions.md b/_overviews/scala3-book/fun-anonymous-functions.md index 39d87da216..428186b968 100644 --- a/_overviews/scala3-book/fun-anonymous-functions.md +++ b/_overviews/scala3-book/fun-anonymous-functions.md @@ -2,139 +2,195 @@ title: Anonymous Functions type: section description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. -num: 28 +languages: [ru, zh-cn] +num: 29 previous-page: fun-intro next-page: fun-function-variables --- - - An anonymous function---also referred to as a *lambda*---is a block of code that’s passed as an argument to a higher-order function. Wikipedia defines an [anonymous function](https://en.wikipedia.org/wiki/Anonymous_function) as, “a function definition that is not bound to an identifier.” For example, given a list like this: +{% tabs fun-anonymous-1 %} +{% tab 'Scala 2 and 3' %} ```scala val ints = List(1, 2, 3) ``` +{% endtab %} +{% endtabs %} You can create a new list by doubling each element in `ints`, using the `List` class `map` method and your custom anonymous function: +{% tabs fun-anonymous-2 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map(_ * 2) // List(2, 4, 6) ``` +{% endtab %} +{% endtabs %} As the comment shows, `doubledInts` contains the list, `List(2, 4, 6)`. In that example, this portion of the code is an anonymous function: +{% tabs fun-anonymous-3 %} +{% tab 'Scala 2 and 3' %} ```scala _ * 2 ``` +{% endtab %} +{% endtabs %} This is a shorthand way of saying, “Multiply a given element by 2.” - - ## Longer forms Once you’re comfortable with Scala, you’ll use that form all the time to write anonymous functions that use one variable at one spot in the function. But if you prefer, you can also write them using longer forms, so in addition to writing this code: +{% tabs fun-anonymous-4 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map(_ * 2) ``` +{% endtab %} +{% endtabs %} you can also write it using these forms: +{% tabs fun-anonymous-5 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map((i: Int) => i * 2) val doubledInts = ints.map((i) => i * 2) val doubledInts = ints.map(i => i * 2) ``` +{% endtab %} +{% endtabs %} All of these lines have the exact same meaning: Double each element in `ints` to create a new list, `doubledInts`. (The syntax of each form is explained in a few moments.) If you’re familiar with Java, it may help to know that those `map` examples are the equivalent of this Java code: +{% tabs fun-anonymous-5-b %} +{% tab 'Java' %} ```java List<Integer> ints = List.of(1, 2, 3); List<Integer> doubledInts = ints.stream() .map(i -> i * 2) .collect(Collectors.toList()); ``` - - +{% endtab %} +{% endtabs %} ## Shortening anonymous functions When you want to be explicit, you can write an anonymous function using this long form: +{% tabs fun-anonymous-6 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map((i: Int) => i * 2) ``` +{% endtab %} +{% endtabs %} The anonymous function in that expression is this: +{% tabs fun-anonymous-7 %} +{% tab 'Scala 2 and 3' %} ```scala (i: Int) => i * 2 ``` +{% endtab %} +{% endtabs %} If you’re not familiar with this syntax, it helps to think of the `=>` symbol as a transformer, because the expression *transforms* the parameter list on the left side of the symbol (an `Int` variable named `i`) into a new result using the algorithm on the right side of the `=>` symbol (in this case, an expression that doubles the `Int`). - ### Shortening that expression This long form can be shortened, as will be shown in the following steps. First, here’s that longest and most explicit form again: +{% tabs fun-anonymous-8 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map((i: Int) => i * 2) ``` +{% endtab %} +{% endtabs %} Because the Scala compiler can infer from the data in `ints` that `i` is an `Int`, the `Int` declaration can be removed: +{% tabs fun-anonymous-9 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map((i) => i * 2) ``` +{% endtab %} +{% endtabs %} Because there’s only one argument, the parentheses around the parameter `i` aren’t needed: +{% tabs fun-anonymous-10 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map(i => i * 2) ``` +{% endtab %} +{% endtabs %} Because Scala lets you use the `_` symbol instead of a variable name when the parameter appears only once in your function, the code can be simplified even more: +{% tabs fun-anonymous-11 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map(_ * 2) ``` +{% endtab %} +{% endtabs %} ### Going even shorter In other examples, you can simplify your anonymous functions further. For instance, beginning with the most explicit form, you can print each element in `ints` using this anonymous function with the `List` class `foreach` method: +{% tabs fun-anonymous-12 %} +{% tab 'Scala 2 and 3' %} ```scala ints.foreach((i: Int) => println(i)) ``` +{% endtab %} +{% endtabs %} As before, the `Int` declaration isn’t required, and because there’s only one argument, the parentheses around `i` aren’t needed: +{% tabs fun-anonymous-13 %} +{% tab 'Scala 2 and 3' %} ```scala ints.foreach(i => println(i)) ``` +{% endtab %} +{% endtabs %} Because `i` is used only once in the body of the function, the expression can be further simplified with the `_` symbol: +{% tabs fun-anonymous-14 %} +{% tab 'Scala 2 and 3' %} ```scala ints.foreach(println(_)) ``` +{% endtab %} +{% endtabs %} Finally, if an anonymous function consists of one method call that takes a single argument, you don’t need to explicitly name and specify the argument, so you can finally write only the name of the method (here, `println`): +{% tabs fun-anonymous-15 %} +{% tab 'Scala 2 and 3' %} ```scala ints.foreach(println) ``` - - +{% endtab %} +{% endtabs %} diff --git a/_overviews/scala3-book/fun-eta-expansion.md b/_overviews/scala3-book/fun-eta-expansion.md index d48e6c059b..a435a4284b 100644 --- a/_overviews/scala3-book/fun-eta-expansion.md +++ b/_overviews/scala3-book/fun-eta-expansion.md @@ -1,86 +1,133 @@ --- -title: Eta Expansion +title: Eta-Expansion type: section -description: This page discusses Eta Expansion, the Scala technology that automatically and transparently converts methods into functions. -num: 30 +description: This page discusses Eta-Expansion, the Scala technology that automatically and transparently converts methods into functions. +languages: [ru, zh-cn] +num: 32 previous-page: fun-function-variables next-page: fun-hofs --- -When you look at the Scaladoc for the `map` method on Scala collections classes, you see that it’s defined to accept a _function_: +When you look at the Scaladoc for the `map` method on Scala collections classes, you see that it’s defined to accept a _function_ value: + +{% tabs fun_1 %} +{% tab 'Scala 2 and 3' for=fun_1 %} ```scala -def map[B](f: (A) => B): List[B] - ----------- +def map[B](f: A => B): List[B] +// ^^^^^^ function type from `A` to `B` ``` +{% endtab %} +{% endtabs %} + Indeed, the Scaladoc clearly states, “`f` is the _function_ to apply to each element.” But despite that, somehow you can pass a _method_ into `map`, and it still works: +{% tabs fun_2 %} +{% tab 'Scala 2 and 3' %} + ```scala def times10(i: Int) = i * 10 // a method List(1, 2, 3).map(times10) // List(10,20,30) ``` -Have you ever wondered how this works---how you can pass a _method_ into `map`, which expects a _function_? +{% endtab %} +{% endtabs %} -The technology behind this is known as _Eta Expansion_. +Why does this work? The process behind this is known as _eta-expansion_. It converts an expression of _method type_ to an equivalent expression of _function type_, and it does so seamlessly and quietly. +## The differences between methods and functions +The key difference between methods and functions is that _a function is an object_, i.e. it is an instance of a class, and in turn has its own methods (e.g. try `f.apply` on a function `f`). -## The differences between methods and functions +_Methods_ are not values that can be passed around, i.e. they can only be called via method application (e.g. `foo(arg1, arg2, ...)`). Methods can be _converted_ to a value by creating a function value that will call the method when supplied with the required arguments. This is known as eta-expansion. + +More concretely: with automatic eta-expansion, the compiler automatically converts any _method reference_, without supplied arguments, to an equivalent _anonymous function_ that will call the method. For example, the reference to `times10` in the code above gets rewritten to `x => times10(x)`, as seen here: -{% comment %} -NOTE: I got the following “method” definition from this page (https://dotty.epfl.ch/docs/reference/changed-features/eta-expansion-spec.html), but I’m not sure it’s 100% accurate now that methods can exist outside of classes/traits/objects. -I’ve made a few changes to that description that I hope are more accurate and up to date. -{% endcomment %} +{% tabs fun_2_expanded %} +{% tab 'Scala 2 and 3' %} -Historically, _methods_ have been a part of the definition of a class, although in Scala 3 you can now have methods outside of classes, such as [Toplevel definitions][toplevel] and [extension methods][extension]. +```scala +def times10(i: Int) = i * 10 +List(1, 2, 3).map(x => times10(x)) // eta expansion of `.map(times10)` +``` + +{% endtab %} +{% endtabs %} -Unlike methods, _functions_ are complete objects themselves, making them first-class entities. +> For the curious, the term eta-expansion has its origins in the [Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus). -Their syntax is also different. -This example shows how to define a method and a function that perform the same task, determining if the given integer is even: +## When does eta-expansion happen? +Automatic eta-expansion is a desugaring that is context-dependent (i.e. the expansion conditionally activates, depending on the surrounding code of the method reference.) + +{% tabs fun_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +In Scala 2 eta-expansion only occurs automatically when the expected type is a function type. +For example, the following will fail: ```scala -def isEvenMethod(i: Int) = i % 2 == 0 // a method -val isEvenFunction = (i: Int) => i % 2 == 0 // a function +def isLessThan(x: Int, y: Int): Boolean = x < y + +val methods = List(isLessThan) +// ^^^^^^^^^^ +// error: missing argument list for method isLessThan +// Unapplied methods are only converted to functions when a function type is expected. +// You can make this conversion explicit by writing `isLessThan _` or `isLessThan(_,_)` instead of `isLessThan`. ``` -The function truly is an object, so you can use it just like any other variable, such as putting it in a list: +See [below](#manual-eta-expansion) for how to solve this issue with manual eta-expansion. +{% endtab %} + +{% tab 'Scala 3' %} + +New to Scala 3, method references can be used everywhere as a value, they will be automatically converted to a function object with a matching type. e.g. ```scala -val functions = List(isEvenFunction) +def isLessThan(x: Int, y: Int): Boolean = x < y + +val methods = List(isLessThan) // works ``` -Conversely, a method technically isn’t an object, so in Scala 2 you couldn’t put a method in a `List`, at least not directly, as shown in this example: +{% endtab %} +{% endtabs %} + +## Manual eta-expansion + +You can always manually eta-expand a method to a function value, here are some examples how: + +{% tabs fun_6 class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// this example shows the Scala 2 error message -val methods = List(isEvenMethod) - ^ -error: missing argument list for method isEvenMethod -Unapplied methods are only converted to functions when a function type is expected. -You can make this conversion explicit by writing `isEvenMethod _` or `isEvenMethod(_)` instead of `isEvenMethod`. +val methodsA = List(isLessThan _) // way 1: expand all parameters +val methodsB = List(isLessThan(_, _)) // way 2: wildcard application +val methodsC = List((x, y) => isLessThan(x, y)) // way 3: anonymous function ``` -As shown in that error message, there is a manual way to convert a method into a function in Scala 2, but the important part for Scala 3 is that the Eta Expansion technology is improved, so now when you attempt to use a method as a variable, it just works---you don’t have to handle the manual conversion yourself: +{% endtab %} + +{% tab 'Scala 3' %} ```scala -val functions = List(isEvenFunction) // works -val methods = List(isEvenMethod) // works +val methodsA = List(isLessThan(_, _)) // way 1: wildcard application +val methodsB = List((x, y) => isLessThan(x, y)) // way 2: anonymous function ``` -For the purpose of this introductory book, the important things to know are: +{% endtab %} +{% endtabs %} -- Eta Expansion is the Scala technology that lets you use methods just like functions -- The technology has been improved in Scala 3 to be almost completely seamless +## Summary -For more details on how this works, see the [Eta Expansion page][eta_expansion] in the Reference documentation. +For the purpose of this introductory book, the important things to know are: +- eta-expansion is a helpful desugaring that lets you use methods just like functions, +- the automatic eta-expansion been improved in Scala 3 to be almost completely seamless. +For more details on how this works, see the [Eta Expansion page][eta_expansion] in the Reference documentation. [eta_expansion]: {{ site.scala3ref }}/changed-features/eta-expansion.html [extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} diff --git a/_overviews/scala3-book/fun-function-variables.md b/_overviews/scala3-book/fun-function-variables.md index 758a8568b7..248a334edf 100644 --- a/_overviews/scala3-book/fun-function-variables.md +++ b/_overviews/scala3-book/fun-function-variables.md @@ -1,87 +1,130 @@ --- title: Function Variables type: section -description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. -num: 29 +description: This page shows how to use function variables in Scala. +languages: [ru, zh-cn] +num: 30 previous-page: fun-anonymous-functions -next-page: fun-eta-expansion +next-page: fun-partial-functions --- Going back to this example from the previous section: +{% tabs fun-function-variables-1 %} +{% tab 'Scala 2 and 3' %} ```scala val doubledInts = ints.map((i: Int) => i * 2) ``` +{% endtab %} +{% endtabs %} We noted that this part of the expression is an anonymous function: +{% tabs fun-function-variables-2 %} +{% tab 'Scala 2 and 3' %} ```scala (i: Int) => i * 2 ``` +{% endtab %} +{% endtabs %} The reason it’s called *anonymous* is because it’s not assigned to a variable, and therefore doesn’t have a name. However, an anonymous function---also known as a *function literal*---can be assigned to a variable to create a *function variable*: +{% tabs fun-function-variables-3 %} +{% tab 'Scala 2 and 3' %} ```scala val double = (i: Int) => i * 2 ``` +{% endtab %} +{% endtabs %} This creates a function variable named `double`. In this expression, the original function literal is on the right side of the `=` symbol: +{% tabs fun-function-variables-4 %} +{% tab 'Scala 2 and 3' %} ```scala val double = (i: Int) => i * 2 ----------------- ``` +{% endtab %} +{% endtabs %} the new variable name is on the left side: +{% tabs fun-function-variables-5 %} +{% tab 'Scala 2 and 3' %} ```scala val double = (i: Int) => i * 2 ------ ``` +{% endtab %} +{% endtabs %} and the function’s parameter list is underlined here: +{% tabs fun-function-variables-6 %} +{% tab 'Scala 2 and 3' %} ```scala val double = (i: Int) => i * 2 -------- ``` +{% endtab %} +{% endtabs %} Like the parameter list for a method, this means that the `double` function takes one parameter, an `Int` named `i`. You can see in the REPL that `double` has the type `Int => Int`, meaning that it takes a single `Int` parameter and returns an `Int`: +{% tabs fun-function-variables-7 %} +{% tab 'Scala 2 and 3' %} ```scala scala> val double = (i: Int) => i * 2 val double: Int => Int = ... ``` +{% endtab %} +{% endtabs %} ### Invoking the function Now you can call the `double` function like this: +{% tabs fun-function-variables-8 %} +{% tab 'Scala 2 and 3' %} ```scala val x = double(2) // 4 ``` +{% endtab %} +{% endtabs %} You can also pass `double` into a `map` call: +{% tabs fun-function-variables-9 %} +{% tab 'Scala 2 and 3' %} ```scala List(1, 2, 3).map(double) // List(2, 4, 6) ``` +{% endtab %} +{% endtabs %} Furthermore, when you have other functions of the `Int => Int` type: +{% tabs fun-function-variables-10 %} +{% tab 'Scala 2 and 3' %} ```scala val triple = (i: Int) => i * 3 ``` +{% endtab %} +{% endtabs %} you can store them in a `List` or `Map`: +{% tabs fun-function-variables-11 %} +{% tab 'Scala 2 and 3' %} ```scala val functionList = List(double, triple) @@ -90,9 +133,13 @@ val functionMap = Map( "3x" -> triple ) ``` +{% endtab %} +{% endtabs %} If you paste those expressions into the REPL, you’ll see that they have these types: +{% tabs fun-function-variables-12 %} +{% tab 'Scala 2 and 3' %} ```` // a List that contains functions of the type `Int => Int` functionList: List[Int => Int] @@ -101,6 +148,8 @@ functionList: List[Int => Int] // values have the type `Int => Int` functionMap: Map[String, Int => Int] ```` +{% endtab %} +{% endtabs %} diff --git a/_overviews/scala3-book/fun-hofs.md b/_overviews/scala3-book/fun-hofs.md index 0985c38ea2..943845cfc6 100644 --- a/_overviews/scala3-book/fun-hofs.md +++ b/_overviews/scala3-book/fun-hofs.md @@ -2,7 +2,8 @@ title: Higher-Order Functions type: section description: This page demonstrates how to create and use higher-order functions in Scala. -num: 31 +languages: [ru, zh-cn] +num: 33 previous-page: fun-eta-expansion next-page: fun-write-map-function --- @@ -14,8 +15,6 @@ In Scala, HOFs are possible because functions are first-class values. As an important note, while we use the common industry term “higher-order function” in this document, in Scala this phrase applies to both *methods* and *functions*. Thanks to Scala’s [Eta Expansion technology][eta_expansion], they can generally be used in the same places. - - ## From consumer to creator In the examples so far in this book you’ve seen how to be a *consumer* of methods that take other functions as input parameters, such as using HOFs like `map` and `filter`. @@ -31,17 +30,19 @@ In the process you’ll see: As a beneficial side effect of this discussion, once you’re comfortable with this syntax, you’ll use it to define function parameters, anonymous functions, and function variables, and it also becomes easier to read the Scaladoc for higher-order functions. - - ## Understanding filter’s Scaladoc To understand how higher-order functions work, it helps to dig into an example. For instance, you can understand the type of functions `filter` accepts by looking at its Scaladoc. Here’s the `filter` definition in the `List[A]` class: +{% tabs filter-definition %} +{% tab 'Scala 2 and 3' %} ```scala -def filter(p: (A) => Boolean): List[A] +def filter(p: A => Boolean): List[A] ``` +{% endtab %} +{% endtabs %} This states that `filter` is a method that takes a function parameter named `p`. By convention, `p` stands for a *predicate*, which is just a function that returns a `Boolean` value. @@ -51,21 +52,27 @@ At this point, if you don’t know the purpose of the `filter` method, all you Looking specifically at the function parameter `p`, this part of `filter`’s description: +{% tabs filter-definition_1 %} +{% tab 'Scala 2 and 3' %} ```scala -p: (A) => Boolean +p: A => Boolean ``` +{% endtab %} +{% endtabs %} means that whatever function you pass in must take the type `A` as an input parameter and return a `Boolean`. -So if your list is a `List[Int]`, you can replace the generic type `A` with `Int`, and read that signature like this: +So if your list is a `List[Int]`, you can replace the type parameter `A` with `Int`, and read that signature like this: +{% tabs filter-definition_2 %} +{% tab 'Scala 2 and 3' %} ```scala -p: (Int) => Boolean +p: Int => Boolean ``` +{% endtab %} +{% endtabs %} Because `isEven` has this type---it transforms an input `Int` into a resulting `Boolean`---it can be used with `filter`. - - {% comment %} NOTE: (A low-priority issue): The next several sections can be condensed. {% endcomment %} @@ -84,17 +91,25 @@ To create a method that takes a function parameter, all you have to do is: 1. In your method’s parameter list, define the signature of the function you want to accept 2. Use that function inside your method -To demonstrate this, here’s a method that that takes an input parameter named `f`, where `f` is a function: +To demonstrate this, here’s a method that takes an input parameter named `f`, where `f` is a function: +{% tabs sayHello-definition %} +{% tab 'Scala 2 and 3' %} ```scala def sayHello(f: () => Unit): Unit = f() ``` +{% endtab %} +{% endtabs %} This portion of the code---the *type signature*---states that `f` is a function, and defines the types of functions the `sayHello` method will accept: +{% tabs sayHello-definition_1 %} +{% tab 'Scala 2 and 3' %} ```scala f: () => Unit ``` +{% endtab %} +{% endtabs %} Here’s how this works: @@ -108,93 +123,128 @@ Here’s how this works: Now that we’ve defined `sayHello`, let’s create a function to match `f`’s signature so we can test it. The following function takes no input parameters and returns nothing, so it matches `f`’s type signature: +{% tabs helloJoe-definition %} +{% tab 'Scala 2 and 3' %} ```scala def helloJoe(): Unit = println("Hello, Joe") ``` +{% endtab %} +{% endtabs %} Because the type signatures match, you can pass `helloJoe` into `sayHello`: +{% tabs sayHello-usage %} +{% tab 'Scala 2 and 3' %} ```scala sayHello(helloJoe) // prints "Hello, Joe" ``` +{% endtab %} +{% endtabs %} If you’ve never done this before, congratulations: You just defined a method named `sayHello` that takes a function as an input parameter, and then invokes that function in its method body. - ### sayHello can take many functions It’s important to know that the beauty of this approach is not that `sayHello` can take *one* function as an input parameter; the beauty is that it can take *any* function that matches `f`’s signature. For instance, because this next function takes no input parameters and returns nothing, it also works with `sayHello`: +{% tabs bonjourJulien-definition %} +{% tab 'Scala 2 and 3' %} ```scala def bonjourJulien(): Unit = println("Bonjour, Julien") ``` +{% endtab %} +{% endtabs %} Here it is in the REPL: +{% tabs bonjourJulien-usage %} +{% tab 'Scala 2 and 3' %} ```` scala> sayHello(bonjourJulien) Bonjour, Julien ```` +{% endtab %} +{% endtabs %} This is a good start. The only thing to do now is see a few more examples of how to define different type signatures for function parameters. - - ## The general syntax for defining function input parameters In this method: +{% tabs sayHello-definition-2 %} +{% tab 'Scala 2 and 3' %} ```scala def sayHello(f: () => Unit): Unit ``` +{% endtab %} +{% endtabs %} We noted that the type signature for `f` is: +{% tabs sayHello-definition-2_1 %} +{% tab 'Scala 2 and 3' %} ```scala () => Unit ``` +{% endtab %} +{% endtabs %} We know that this means, “a function that takes no input parameters and returns nothing meaningful (given by `Unit`).” To demonstrate more type signature examples, here’s a function that takes a `String` parameter and returns an `Int`: +{% tabs sayHello-definition-2_2 %} +{% tab 'Scala 2 and 3' %} ```scala -f: (String) => Int +f: String => Int ``` +{% endtab %} +{% endtabs %} What kinds of functions take a string and return an integer? Functions like “string length” and checksum are two examples. Similarly, this function takes two `Int` parameters and returns an `Int`: +{% tabs sayHello-definition-2_3 %} +{% tab 'Scala 2 and 3' %} ```scala f: (Int, Int) => Int ``` +{% endtab %} +{% endtabs %} Can you imagine what sort of functions match that signature? The answer is that any function that takes two `Int` input parameters and returns an `Int` matches that signature, so all of these “functions” (methods, really) are a match: +{% tabs add-sub-mul-definitions %} +{% tab 'Scala 2 and 3' %} ```scala def add(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a - b def multiply(a: Int, b: Int): Int = a * b ``` +{% endtab %} +{% endtabs %} As you can infer from these examples, the general syntax for defining function parameter type signatures is: +{% tabs add-sub-mul-definitions_1 %} +{% tab 'Scala 2 and 3' %} ```scala variableName: (parameterTypes ...) => returnType ``` +{% endtab %} +{% endtabs %} > Because functional programming is like creating and combining a series of algebraic equations, it’s common to think about types a *lot* when designing functions and applications. > You might say that you “think in types.” - - ## Taking a function parameter along with other parameters For HOFs to be really useful, they also need some data to work on. @@ -203,46 +253,68 @@ But for a standalone HOF that doesn’t have its own data, it should also accept For instance, here’s a method named `executeNTimes` that has two input parameters: a function, and an `Int`: +{% tabs executeNTimes-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for (i <- 1 to n) f() +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala def executeNTimes(f: () => Unit, n: Int): Unit = for i <- 1 to n do f() ``` +{% endtab %} +{% endtabs %} As the code shows, `executeNTimes` executes the `f` function `n` times. Because a simple `for` loop like this has no return value, `executeNTimes` returns `Unit`. To test `executeNTimes`, define a method that matches `f`’s signature: +{% tabs helloWorld-definition %} +{% tab 'Scala 2 and 3' %} ```scala // a method of type `() => Unit` def helloWorld(): Unit = println("Hello, world") ``` +{% endtab %} +{% endtabs %} Then pass that method into `executeNTimes` along with an `Int`: -```` +{% tabs helloWorld-usage %} +{% tab 'Scala 2 and 3' %} +``` scala> executeNTimes(helloWorld, 3) Hello, world Hello, world Hello, world -```` +``` +{% endtab %} +{% endtabs %} Excellent. The `executeNTimes` method executes the `helloWorld` function three times. - - ### As many parameters as needed Your methods can continue to get as complicated as necessary. For example, this method takes a function of type `(Int, Int) => Int`, along with two input parameters: +{% tabs executeAndPrint-definition %} +{% tab 'Scala 2 and 3' %} ```scala def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = println(f(i, j)) ``` +{% endtab %} +{% endtabs %} Because these `sum` and `multiply` methods match that type signature, they can be passed into `executeAndPrint` along with two `Int` values: +{% tabs executeAndPrint-usage %} +{% tab 'Scala 2 and 3' %} ```scala def sum(x: Int, y: Int) = x + y def multiply(x: Int, y: Int) = x * y @@ -250,8 +322,8 @@ def multiply(x: Int, y: Int) = x * y executeAndPrint(sum, 3, 11) // prints 14 executeAndPrint(multiply, 3, 9) // prints 27 ``` - - +{% endtab %} +{% endtabs %} ## Function type signature consistency @@ -259,9 +331,13 @@ A great thing about learning about Scala’s function type signatures is that th For instance, if you were to write a function that calculates the sum of two integers, you’d write it like this: +{% tabs f-val-definition %} +{% tab 'Scala 2 and 3' %} ```scala val f: (Int, Int) => Int = (a, b) => a + b ``` +{% endtab %} +{% endtabs %} That code consists of the type signature: diff --git a/_overviews/scala3-book/fun-intro.md b/_overviews/scala3-book/fun-intro.md index ba22b69d7a..66cb6bad81 100644 --- a/_overviews/scala3-book/fun-intro.md +++ b/_overviews/scala3-book/fun-intro.md @@ -2,12 +2,13 @@ title: Functions type: chapter description: This chapter looks at all topics related to functions in Scala 3. -num: 27 +languages: [ru, zh-cn] +num: 28 previous-page: methods-summary next-page: fun-anonymous-functions --- Where the previous chapter introduced Scala *methods*, this chapter digs into *functions*. -The topics that are covered include anonymous functions, function variables, and higher-order functions (HOFs), including how to create your own HOFs. +The topics that are covered include anonymous functions, partial functions, function variables, and higher-order functions (HOFs), including how to create your own HOFs. diff --git a/_overviews/scala3-book/fun-partial-functions.md b/_overviews/scala3-book/fun-partial-functions.md new file mode 100644 index 0000000000..fe8aaa50eb --- /dev/null +++ b/_overviews/scala3-book/fun-partial-functions.md @@ -0,0 +1,81 @@ +--- +title: Partial Functions +type: section +description: This page shows how to use partial functions in Scala. +num: 31 +previous-page: fun-function-variables +next-page: fun-eta-expansion +--- + +A partial function is a function that may not be defined for all values of its argument type. In Scala, partial functions +are unary functions implementing the `PartialFunction[A, B]` trait, where `A` is the argument type and `B` the result type. + +To define a partial function, use a `case` identical to those used in `match` expressions: + +{% tabs fun-partial-1 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledOdds: PartialFunction[Int, Int] = { + case i if i % 2 == 1 => i * 2 +} +``` +{% endtab %} +{% endtabs %} + +To check if a partial function is defined for an argument, use the `isDefinedAt` method: + +{% tabs fun-partial-2 %} +{% tab 'Scala 2 and 3' %} +```scala +doubledOdds.isDefinedAt(3) // true +doubledOdds.isDefinedAt(4) // false +``` +{% endtab %} +{% endtabs %} + +Trying to apply a partial function to an argument not belonging to its domain results in `MatchError`: + +{% tabs fun-partial-3 %} +{% tab 'Scala 2 and 3' %} +```scala +doubledOdds(4) // Exception in thread "main" scala.MatchError: 4 +``` +{% endtab %} +{% endtabs %} + +### Using partial functions + +A partial function can be passed as an argument to a method: + +{% tabs fun-partial-4 %} +{% tab 'Scala 2 and 3' %} +```scala +val res = List(1, 2, 3).collect({ case i if i % 2 == 1 => i * 2 }) // List(2, 6) +``` +{% endtab %} +{% endtabs %} + +You can define a default value for arguments not in domain with `applyOrElse`: + +{% tabs fun-partial-5 %} +{% tab 'Scala 2 and 3' %} +```scala +doubledOdds.applyOrElse(4, _ + 1) // 5 +``` +{% endtab %} +{% endtabs %} + +Two partial function can be composed with `orElse`, the second function will be applied for arguments where the first +one is not defined: + +{% tabs fun-partial-6 %} +{% tab 'Scala 2 and 3' %} +```scala +val incrementedEvens: PartialFunction[Int, Int] = { + case i if i % 2 == 0 => i + 1 +} + +val res2 = List(1, 2, 3).collect(doubledOdds.orElse(incrementedEvens)) // List(2, 3, 6) +``` +{% endtab %} +{% endtabs %} \ No newline at end of file diff --git a/_overviews/scala3-book/fun-summary.md b/_overviews/scala3-book/fun-summary.md index e2e9488629..50eb480c27 100644 --- a/_overviews/scala3-book/fun-summary.md +++ b/_overviews/scala3-book/fun-summary.md @@ -1,8 +1,9 @@ --- title: Summary type: section -description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. -num: 34 +description: This page provides a summary of the previous 'Functions' sections. +languages: [ru, zh-cn] +num: 36 previous-page: fun-write-method-returns-function next-page: packaging-imports --- diff --git a/_overviews/scala3-book/fun-write-map-function.md b/_overviews/scala3-book/fun-write-map-function.md index 74cab95213..85fd13b248 100644 --- a/_overviews/scala3-book/fun-write-map-function.md +++ b/_overviews/scala3-book/fun-write-map-function.md @@ -2,7 +2,8 @@ title: Write Your Own map Method type: section description: This page demonstrates how to create and use higher-order functions in Scala. -num: 32 +languages: [ru, zh-cn] +num: 34 previous-page: fun-hofs next-page: fun-write-method-returns-function --- @@ -17,70 +18,118 @@ Focusing only on a `List[Int]`, you state: > I want to write a `map` method that can be used to apply a function to each element in a `List[Int]` that it’s given, returning the transformed elements as a new list. Given that statement, you start to write the method signature. -First, you know that you want to accept a function as a parameter, and that function should transform an `Int` into some generic type `A`, so you write: +First, you know that you want to accept a function as a parameter, and that function should transform an `Int` into some type `A`, so you write: +{% tabs map-accept-func-definition %} +{% tab 'Scala 2 and 3' %} ```scala def map(f: (Int) => A) ``` +{% endtab %} +{% endtabs %} -The syntax for using a generic type requires declaring that type symbol before the parameter list, so you add that: +The syntax for using a type parameter requires declaring it in square brackets `[]` before the parameter list, so you add that: +{% tabs map-type-symbol-definition %} +{% tab 'Scala 2 and 3' %} ```scala def map[A](f: (Int) => A) ``` +{% endtab %} +{% endtabs %} Next, you know that `map` should also accept a `List[Int]`: +{% tabs map-list-int-param-definition %} +{% tab 'Scala 2 and 3' %} ```scala def map[A](f: (Int) => A, xs: List[Int]) ``` +{% endtab %} +{% endtabs %} -Finally, you also know that `map` returns a transformed `List` that contains elements of the generic type `A`: +Finally, you also know that `map` returns a transformed `List` that contains elements of the type `A`: +{% tabs map-with-return-type-definition %} +{% tab 'Scala 2 and 3' %} ```scala def map[A](f: (Int) => A, xs: List[Int]): List[A] = ??? ``` +{% endtab %} +{% endtabs %} That takes care of the method signature. Now all you have to do is write the method body. A `map` method applies the function it’s given to every element in the list it’s given to produce a new, transformed list. One way to do this is with a `for` expression: - +{% tabs for-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala for x <- xs yield f(x) ``` +{% endtab %} +{% endtabs %} `for` expressions often make code surprisingly simple, and for our purposes, that ends up being the entire method body. Putting it together with the method signature, you now have a standalone `map` method that works with a `List[Int]`: +{% tabs map-function class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala def map[A](f: (Int) => A, xs: List[Int]): List[A] = for x <- xs yield f(x) ``` +{% endtab %} +{% endtabs %} ### Make it generic As a bonus, notice that the `for` expression doesn’t do anything that depends on the type inside the `List` being `Int`. -Therefore, you can replace `Int` in the type signature with the generic type parameter `B`: +Therefore, you can replace `Int` in the type signature with the type parameter `B`: +{% tabs map-function-full-generic class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala def map[A, B](f: (B) => A, xs: List[B]): List[A] = for x <- xs yield f(x) ``` +{% endtab %} +{% endtabs %} Now you have a `map` method that works with any `List`. These examples demonstrate that `map` works as desired: +{% tabs map-use-example %} +{% tab 'Scala 2 and 3' %} ```scala -def double(i : Int) = i * 2 +def double(i : Int): Int = i * 2 map(double, List(1, 2, 3)) // List(2, 4, 6) -def strlen(s: String) = s.length +def strlen(s: String): Int = s.length map(strlen, List("a", "bb", "ccc")) // List(1, 2, 3) ``` +{% endtab %} +{% endtabs %} Now that you’ve seen how to write methods that accept functions as input parameters, let’s look at methods that return functions. diff --git a/_overviews/scala3-book/fun-write-method-returns-function.md b/_overviews/scala3-book/fun-write-method-returns-function.md index 825ad0cdfe..28c05b9cf2 100644 --- a/_overviews/scala3-book/fun-write-method-returns-function.md +++ b/_overviews/scala3-book/fun-write-method-returns-function.md @@ -2,7 +2,8 @@ title: Creating a Method That Returns a Function type: section description: This page demonstrates how to create and use higher-order functions in Scala. -num: 33 +languages: [ru, zh-cn] +num: 35 previous-page: fun-write-map-function next-page: fun-summary --- @@ -19,48 +20,72 @@ Once again we start with a problem statement: Given that statement, you can start building `greet`. You know it’s going to be a method: +{% tabs fun-write-method-returns-function-1 %} +{% tab 'Scala 2 and 3' %} ```scala def greet() ``` +{% endtab %} +{% endtabs %} You also know this method will return a function that (a) takes a `String` parameter, and (b) prints that string using `println`. Therefore that function has the type, `String => Unit`: +{% tabs fun-write-method-returns-function-2 %} +{% tab 'Scala 2 and 3' %} ```scala def greet(): String => Unit = ??? ---------------- ``` +{% endtab %} +{% endtabs %} Now you just need a method body. You know that the method needs to return a function, and that function takes a `String` and prints it. This anonymous function matches that description: +{% tabs fun-write-method-returns-function-3 %} +{% tab 'Scala 2 and 3' %} ```scala (name: String) => println(s"Hello, $name") ``` +{% endtab %} +{% endtabs %} Now you just return that function from the method: +{% tabs fun-write-method-returns-function-4 %} +{% tab 'Scala 2 and 3' %} ```scala // a method that returns a function def greet(): String => Unit = (name: String) => println(s"Hello, $name") ``` +{% endtab %} +{% endtabs %} Because this method returns a function, you get the function by calling `greet()`. This is a good step to do in the REPL because it verifies the type of the new function: +{% tabs fun-write-method-returns-function-5 %} +{% tab 'Scala 2 and 3' %} ```` scala> val greetFunction = greet() val greetFunction: String => Unit = Lambda.... ----------------------------- ```` +{% endtab %} +{% endtabs %} Now you can call `greetFunction`: +{% tabs fun-write-method-returns-function-6 %} +{% tab 'Scala 2 and 3' %} ```scala greetFunction("Joe") // prints "Hello, Joe" ``` +{% endtab %} +{% endtabs %} Congratulations, you just created a method that returns a function, and then executed that function. @@ -71,29 +96,43 @@ Congratulations, you just created a method that returns a function, and then exe Our method would be more useful if you could pass in a greeting, so let’s do that. All you have to do is pass the greeting in as a parameter to the `greet` method, and use it in the string inside `println`: +{% tabs fun-write-method-returns-function-7 %} +{% tab 'Scala 2 and 3' %} ```scala def greet(theGreeting: String): String => Unit = (name: String) => println(s"$theGreeting, $name") ``` +{% endtab %} +{% endtabs %} Now when you call your method, the process is more flexible because you can change the greeting. This is what it looks like when you create a function from this method: +{% tabs fun-write-method-returns-function-8 %} +{% tab 'Scala 2 and 3' %} ```` scala> val sayHello = greet("Hello") val sayHello: String => Unit = Lambda..... ------------------------ ```` +{% endtab %} +{% endtabs %} The REPL type signature output shows that `sayHello` is a function that takes a `String` input parameter and returns `Unit` (nothing). So now when you give `sayHello` a `String`, it prints the greeting: +{% tabs fun-write-method-returns-function-9 %} +{% tab 'Scala 2 and 3' %} ```scala sayHello("Joe") // prints "Hello, Joe" ``` +{% endtab %} +{% endtabs %} You can also change the greeting to create new functions, as desired: +{% tabs fun-write-method-returns-function-10 %} +{% tab 'Scala 2 and 3' %} ```scala val sayCiao = greet("Ciao") val sayHola = greet("Hola") @@ -101,6 +140,8 @@ val sayHola = greet("Hola") sayCiao("Isabella") // prints "Ciao, Isabella" sayHola("Carlos") // prints "Hola, Carlos" ``` +{% endtab %} +{% endtabs %} @@ -115,27 +156,53 @@ A first thing you know is that you want to create a method that (a) takes a “d Furthermore, because that function prints a string that it’s given, you know it has the type `String => Unit`. With that information you write the method signature: +{% tabs fun-write-method-returns-function-11 %} +{% tab 'Scala 2 and 3' %} ```scala def createGreetingFunction(desiredLanguage: String): String => Unit = ??? ``` +{% endtab %} +{% endtabs %} Next, because you know that the possible functions you’ll return take a string and print it, you can write two anonymous functions for the English and French languages: +{% tabs fun-write-method-returns-function-12 %} +{% tab 'Scala 2 and 3' %} ```scala (name: String) => println(s"Hello, $name") (name: String) => println(s"Bonjour, $name") ``` +{% endtab %} +{% endtabs %} Inside a method it might be a little more readable if you give those anonymous functions some names, so let’s assign them to two variables: +{% tabs fun-write-method-returns-function-13 %} +{% tab 'Scala 2 and 3' %} ```scala val englishGreeting = (name: String) => println(s"Hello, $name") val frenchGreeting = (name: String) => println(s"Bonjour, $name") ``` +{% endtab %} +{% endtabs %} Now all you need to do is (a) return `englishGreeting` if the `desiredLanguage` is English, and (b) return `frenchGreeting` if the `desiredLanguage` is French. One way to do that is with a `match` expression: +{% tabs fun-write-method-returns-function-14 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = { + val englishGreeting = (name: String) => println(s"Hello, $name") + val frenchGreeting = (name: String) => println(s"Bonjour, $name") + desiredLanguage match { + case "english" => englishGreeting + case "french" => frenchGreeting + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala def createGreetingFunction(desiredLanguage: String): String => Unit = val englishGreeting = (name: String) => println(s"Hello, $name") @@ -144,23 +211,33 @@ def createGreetingFunction(desiredLanguage: String): String => Unit = case "english" => englishGreeting case "french" => frenchGreeting ``` +{% endtab %} +{% endtabs %} And that’s the final method. Notice that returning a function value from a method is no different than returning a string or integer value. This is how `createGreetingFunction` builds a French-greeting function: +{% tabs fun-write-method-returns-function-15 %} +{% tab 'Scala 2 and 3' %} ```scala val greetInFrench = createGreetingFunction("french") greetInFrench("Jonathan") // prints "Bonjour, Jonathan" ``` +{% endtab %} +{% endtabs %} And this is how it builds an English-greeting function: +{% tabs fun-write-method-returns-function-16 %} +{% tab 'Scala 2 and 3' %} ```scala val greetInEnglish = createGreetingFunction("english") greetInEnglish("Joe") // prints "Hello, Joe" ``` +{% endtab %} +{% endtabs %} If you’re comfortable with that code---congratulations---you now know how to write methods that return functions. diff --git a/_overviews/scala3-book/interacting-with-java.md b/_overviews/scala3-book/interacting-with-java.md index 5b0b99286b..00a3c5aa8a 100644 --- a/_overviews/scala3-book/interacting-with-java.md +++ b/_overviews/scala3-book/interacting-with-java.md @@ -2,7 +2,8 @@ title: Interacting with Java type: chapter description: This page demonstrates how Scala code can interact with Java, and how Java code can interact with Scala code. -num: 72 +languages: [ru, zh-cn] +num: 73 previous-page: tools-worksheets next-page: scala-for-java-devs --- @@ -38,209 +39,282 @@ Note that the Java examples in this section assume that you’re using Java 11 o ## How to use Java collections in Scala -When you’re writing Scala code and need to use a Java collection class, you _can_ just use the class as-is. -However, if you want to use the class in a Scala `for` loop, or want to take advantage of the higher-order functions on the Scala collections classes, you’ll want to convert the Java collection to a Scala collection. +When you’re writing Scala code and an API either requires or produces a Java collection class (from the `java.util` package), then it is valid to directly use or create the collection as you would in Java. + +However, for idiomatic usage in Scala, such as `for` loops over the collection, or to apply higher-order functions such as `map` and `filter`, you can create a proxy that behaves like a Scala collection. Here’s an example of how this works. -Given this Java `ArrayList`: +Given this API that returns `java.util.List[String]`: +{% tabs foo-definition %} +{% tab Java %} ```java -// java -public class JavaClass { - public static List<String> getStrings() { - return new ArrayList<String>(List.of("a", "b", "c")); +public interface Foo { + static java.util.List<String> getStrings() { + return List.of("a", "b", "c"); } } ``` +{% endtab %} +{% endtabs %} + +You can convert that Java list to a Scala `Seq`, using the conversion utilities in the Scala `scala.jdk.CollectionConverters` object: -You can convert that Java list to a Scala `Seq`, using the conversion utilities in the Scala _scala.jdk.CollectionConverters_ package: +{% tabs foo-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.jdk.CollectionConverters._ +import scala.collection.mutable + +def testList() = { + println("Using a Java List in Scala") + val javaList: java.util.List[String] = Foo.getStrings() + val scalaSeq: mutable.Seq[String] = javaList.asScala + for (s <- scalaSeq) println(s) +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala import scala.jdk.CollectionConverters.* +import scala.collection.mutable -def testList() = +def testList() = println("Using a Java List in Scala") - val javaList: java.util.List[String] = JavaClass.getStrings() - val scalaSeq: Seq[String] = javaList.asScala.toSeq - scalaSeq.foreach(println) + val javaList: java.util.List[String] = Foo.getStrings() + val scalaSeq: mutable.Seq[String] = javaList.asScala for s <- scalaSeq do println(s) ``` +{% endtab %} +{% endtabs %} -Of course that code can be shortened, but the individual steps are shown here to demonstrate exactly how the conversion process works. - +In the above code `javaList.asScala` creates a wrapper that adapts a `java.util.List` to Scala's `mutable.Seq` collection. ## How to use Java `Optional` in Scala -When you need to use the Java `Optional` class in your Scala code, import the _scala.jdk.OptionConverters_ object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`. +When you are interacting with an API that uses the `java.util.Optional` class in your Scala code, it is fine to construct and use as in Java. -To demonstrate this, here’s a Java class with two `Optional<String>` values, one containing a string and the other one empty: +However, for idiomatic usage in Scala, such as use with `for`, you can convert it to a Scala `Option`. -```java -// java -import java.util.Optional; +To demonstrate this, here’s a Java API that returns an `Optional[String]` value: -public class JavaClass { - static Optional<String> oString = Optional.of("foo"); - static Optional<String> oEmptyString = Optional.empty(); +{% tabs bar-definition %} +{% tab Java %} +```java +public interface Bar { + static java.util.Optional<String> optionalString() { + return Optional.of("hello"); + } } ``` +{% endtab %} +{% endtabs %} -Now in your Scala code you can access those fields. -If you just access them directly, they’ll both be `Optional` values: +First import all members from the `scala.jdk.OptionConverters` object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`: +{% tabs bar-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala import java.util.Optional +import scala.jdk.OptionConverters._ -val optionalString = JavaClass.oString // Optional[foo] -val eOptionalString = JavaClass.oEmptyString // Optional.empty +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala ``` - -But by using the _scala.jdk.OptionConverters_ methods, you can convert them to Scala `Option` values: - +{% endtab %} +{% tab 'Scala 3' %} ```scala import java.util.Optional import scala.jdk.OptionConverters.* -val optionalString = JavaClass.oString // Optional[foo] -val optionString = optionalString.toScala // Some(foo) - -val eOptionalString = JavaClass.oEmptyString // Optional.empty -val eOptionString = eOptionalString.toScala // None +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala ``` - - +{% endtab %} +{% endtabs %} ## Extending Java interfaces in Scala If you need to use Java interfaces in your Scala code, extend them just as though they are Scala traits. For example, given these three Java interfaces: +{% tabs animal-definition %} +{% tab Java %} ```java -// java -interface Animal { +public interface Animal { void speak(); } -interface Wagging { +public interface Wagging { void wag(); } -interface Running { +public interface Running { // an implemented method default void run() { System.out.println("I’m running"); } } ``` +{% endtab %} +{% endtabs %} you can create a `Dog` class in Scala just as though you were using traits. -All you have to do is implement the `speak` and `wag` methods: +Because `run` has a default implementation, you only need to implement the `speak` and `wag` methods: +{% tabs animal-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -class Dog extends Animal, Wagging, Running: +class Dog extends Animal with Wagging with Running { def speak = println("Woof") def wag = println("Tail is wagging") +} -@main def useJavaInterfaceInScala = - val d = new Dog +def useJavaInterfaceInScala = { + val d = new Dog() d.speak d.wag + d.run +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +class Dog extends Animal, Wagging, Running: + def speak = println("Woof") + def wag = println("Tail is wagging") +def useJavaInterfaceInScala = + val d = Dog() + d.speak + d.wag + d.run +``` +{% endtab %} +{% endtabs %} +Also notice that in Scala, Java methods defined with empty parameter lists can be called either as in Java, `.wag()`, or you can choose to not use parentheses `.wag`. ## How to use Scala collections in Java When you need to use a Scala collection class in your Java code, use the methods of Scala’s `scala.jdk.javaapi.CollectionConverters` object in your Java code to make the conversions work. -For example, if you have a `List[String]` like this in a Scala class: +For example, suppose that a Scala API returns a `List[String]` like this: + +{% tabs baz-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -class ScalaClass: - val strings = List("a", "b") +object Baz { + val strings: List[String] = List("a", "b", "c") +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Baz: + val strings: List[String] = List("a", "b", "c") ``` +{% endtab %} +{% endtabs %} You can access that Scala `List` in your Java code like this: +{% tabs baz-usage %} +{% tab Java %} ```java -// java import scala.jdk.javaapi.CollectionConverters; -// create an instance of the Scala class -ScalaClass sc = new ScalaClass(); +// access the `strings` method with `Baz.strings()` +scala.collection.immutable.List<String> xs = Baz.strings(); -// access the `strings` field as `sc.strings()` -scala.collection.immutable.List<String> xs = sc.strings(); - -// convert the Scala `List` a Java `List<String>` java.util.List<String> listOfStrings = CollectionConverters.asJava(xs); -listOfStrings.forEach(System.out::println); + +for (String s: listOfStrings) { + System.out.println(s); +} ``` +{% endtab %} +{% endtabs %} That code can be shortened, but the full steps are shown to demonstrate how the process works. -Here are a few things to notice in that code: - -- In your Java code, you create an instance of `ScalaClass` just like an instance of a Java class -- `ScalaClass` has a field named `strings`, but from Java you access that field as a method, i.e., as `sc.strings()` - +Be sure to notice that while `Baz` has a field named `strings`, from Java the field appears as a method, so must be called with parentheses `.strings()`. ## How to use Scala `Option` in Java When you need to use a Scala `Option` in your Java code, you can convert the `Option` to a Java `Optional` value using the `toJava` method of the Scala `scala.jdk.javaapi.OptionConverters` object. -To demonstrate this, create a Scala class with two `Option[String]` values, one containing a string and the other one empty: +For example, suppose that a Scala API returns an `Option[String]` like this: +{% tabs qux-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -object ScalaObject: - val someString = Option("foo") - val noneString: Option[String] = None +object Qux { + val optString: Option[String] = Option("hello") +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Qux: + val optString: Option[String] = Option("hello") +``` +{% endtab %} +{% endtabs %} -Then in your Java code, convert those `Option[String]` values into `java.util.Optional[String]` using the `toJava` method from the `scala.jdk.javaapi.OptionConverters` object: +Then you can access that Scala `Option` in your Java code like this: +{% tabs qux-usage %} +{% tab Java %} ```java -// java import java.util.Optional; -import static scala.jdk.javaapi.OptionConverters.toJava; +import scala.Option; +import scala.jdk.javaapi.OptionConverters; -public class JUseScalaOptionInJava { - public static void main(String[] args) { - Optional<String> stringSome = toJava(ScalaObject.someString()); // Optional[foo] - Optional<String> stringNone = toJava(ScalaObject.noneString()); // Optional.empty - System.out.printf("stringSome = %s\n", stringSome); - System.out.printf("stringNone = %s\n", stringNone); - } -} +Option<String> scalaOptString = Qux.optString(); +Optional<String> javaOptString = OptionConverters.toJava(scalaOptString); ``` +{% endtab %} +{% endtabs %} -The two Scala `Option` fields are now available as Java `Optional` values. - - +That code can be shortened, but the full steps are shown to demonstrate how the process works. +Be sure to notice that while `Qux` has a field named `optString`, from Java the field appears as a method, so must be called with parentheses `.optString()`. ## How to use Scala traits in Java -With Java 11 you can use a Scala trait just like a Java interface, even if the trait has implemented methods. +From Java 8 you can use a Scala trait just like a Java interface, even if the trait has implemented methods. For example, given these two Scala traits, one with an implemented method and one with only an interface: +{% tabs scala-trait-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait ScalaAddTrait { + def sum(x: Int, y: Int) = x + y // implemented +} + +trait ScalaMultiplyTrait { + def multiply(x: Int, y: Int): Int // abstract +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala trait ScalaAddTrait: - def sum(x: Int, y: Int) = x + y // implemented + def sum(x: Int, y: Int) = x + y // implemented trait ScalaMultiplyTrait: def multiply(x: Int, y: Int): Int // abstract ``` +{% endtab %} +{% endtabs %} A Java class can implement both of those interfaces, and define the `multiply` method: +{% tabs scala-trait-usage %} +{% tab Java %} ```java -// java class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait { public int multiply(int a, int b) { return a * b; @@ -251,6 +325,8 @@ JavaMath jm = new JavaMath(); System.out.println(jm.sum(3,4)); // 7 System.out.println(jm.multiply(3,4)); // 12 ``` +{% endtab %} +{% endtabs %} @@ -261,29 +337,45 @@ But if for some reason you have a Scala method that does throw an exception, and For example, this Scala `exceptionThrower` method is annotated to declare that it throws an `Exception`: +{% tabs except-throw-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -object SExceptionThrower: - @throws(classOf[Exception]) - def exceptionThrower = +object SExceptionThrower { + @throws[Exception] + def exceptionThrower = throw new Exception("Idiomatic Scala methods don’t throw exceptions") +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object SExceptionThrower: + @throws[Exception] + def exceptionThrower = + throw Exception("Idiomatic Scala methods don’t throw exceptions") ``` +{% endtab %} +{% endtabs %} As a result, you’ll need to handle the exception in your Java code. For instance, this code won’t compile because I don’t handle the exception: +{% tabs except-throw-usage %} +{% tab Java %} ```java -// java: won’t compile because the exception isn’t handled +// won’t compile because the exception isn’t handled public class ScalaExceptionsInJava { public static void main(String[] args) { SExceptionThrower.exceptionThrower(); } } ``` +{% endtab %} +{% endtabs %} The compiler gives this error: -```` +````plain [error] ScalaExceptionsInJava: unreported exception java.lang.Exception; must be caught or declared to be thrown [error] SExceptionThrower.exceptionThrower() @@ -302,28 +394,43 @@ This is probably not what you want, because the Java code may not account for th When a Scala method has a varargs parameter and you want to use that method in Java, mark the Scala method with the `@varargs` annotation. For example, the `printAll` method in this Scala class declares a `String*` varargs field: +{% tabs vararg-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.annotation.varargs + +object VarargsPrinter { + @varargs def printAll(args: String*): Unit = args.foreach(println) +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala import scala.annotation.varargs object VarargsPrinter: - @varargs def printAll(args: String*): Unit = args.foreach(println) + @varargs def printAll(args: String*): Unit = args.foreach(println) ``` +{% endtab %} +{% endtabs %} Because `printAll` is declared with the `@varargs` annotation, it can be called from a Java program with a variable number of parameters, as shown in this example: -```scala -// java +{% tabs vararg-usage %} +{% tab Java %} +```java public class JVarargs { public static void main(String[] args) { VarargsPrinter.printAll("Hello", "world"); } } ``` +{% endtab %} +{% endtabs %} When this code is run, it results in the following output: -```` +````plain Hello world ```` @@ -334,25 +441,43 @@ world In Scala you might want to create a method name using a symbolic character: +{% tabs add-definition %} +{% tab 'Scala 2 and 3' %} ```scala def +(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} + +That method name won’t work well in Java, but what you can do in Scala is provide an “alternate” name for the method with the `targetName` annotation, which will be the name of the method when used from Java: -That method name won’t work well in Java, but what you can do in Scala 3 is provide an “alternate” name for the method---an alias---that will work in Java: +{% tabs add-2-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.annotation.targetName +object Adder { + @targetName("add") def +(a: Int, b: Int) = a + b +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -import scala.annotation.alpha +import scala.annotation.targetName -class Adder: - @alpha("add") def +(a: Int, b: Int) = a + b +object Adder: + @targetName("add") def +(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} Now in your Java code you can use the aliased method name `add`: -```scala -var adder = new Adder(); -int x = adder.add(1,1); +{% tabs add-2-usage %} +{% tab Java %} +```java +int x = Adder.add(1,1); System.out.printf("x = %d\n", x); ``` - - +{% endtab %} +{% endtabs %} diff --git a/_overviews/scala3-book/introduction.md b/_overviews/scala3-book/introduction.md index 3d3558203d..b3798aeabb 100644 --- a/_overviews/scala3-book/introduction.md +++ b/_overviews/scala3-book/introduction.md @@ -2,6 +2,7 @@ title: Introduction type: chapter description: This page begins the overview documentation of the Scala 3 language. +languages: [ru, zh-cn] num: 1 previous-page: next-page: scala-features @@ -12,19 +13,21 @@ The goal of this book is to provide an informal introduction to the Scala langua It touches on all Scala topics, in a relatively light manner. If at any time while you’re reading this book and want more information on a specific feature, you’ll find links to our [_Reference_ documentation][reference], which covers many new features of the Scala language in more detail. +<blockquote class="help-info"> +<i class="fa fa-info"></i>  If you are interested in the archived Scala 2 edition of the book, you +can <a href="/overviews/scala-book/introduction.html">access it here</a>. We are currently in the process of +merging the two books and you can <a href="{% link scala3/contribute-to-docs.md %}">help us</a>. +</blockquote> + Over the course of this book, we hope to demonstrate that Scala is a beautiful, expressive programming language, with a clean, modern syntax, which supports functional programming (FP) and object-oriented programming (OOP), and that provides a safe static type system. Scala’s syntax, grammar, and features have been re-thought, debated in an open process, and updated in 2020 to be clearer and easier to understand than ever before. The book begins with a whirlwind tour of many of Scala’s features in the [“A Taste of Scala” section][taste]. After that tour, the sections that follow it provide more details on those language features. -{% comment %} -We should have a link structure on the whole tour here -{% endcomment %} +## A bit of background -> We are still in the process of writing the book. -> You can [help us improve it][contributing] +Scala was created by [Martin Odersky](https://en.wikipedia.org/wiki/Martin_Odersky), who studied under [Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth), who created Pascal and several other languages. Mr. Odersky is one of the co-designers of Generic Java, and is also known as the “father” of the `javac` compiler. -[contributing]: {% link scala3/contribute-to-docs.md %} [reference]: {{ site.scala3ref }}/overview.html [taste]: {% link _overviews/scala3-book/taste-intro.md %} diff --git a/_overviews/scala3-book/methods-intro.md b/_overviews/scala3-book/methods-intro.md index 1c1414746c..59e91c3c6c 100644 --- a/_overviews/scala3-book/methods-intro.md +++ b/_overviews/scala3-book/methods-intro.md @@ -2,7 +2,8 @@ title: Methods type: chapter description: This section introduces methods in Scala 3. -num: 23 +languages: [ru, zh-cn] +num: 24 previous-page: domain-modeling-fp next-page: methods-most --- diff --git a/_overviews/scala3-book/methods-main-methods.md b/_overviews/scala3-book/methods-main-methods.md index bee342a45f..78071efb49 100644 --- a/_overviews/scala3-book/methods-main-methods.md +++ b/_overviews/scala3-book/methods-main-methods.md @@ -1,42 +1,53 @@ --- -title: main Methods +title: Main Methods in Scala 3 type: section description: This page describes how 'main' methods and the '@main' annotation work in Scala 3. -num: 25 +languages: [ru, zh-cn] +num: 26 previous-page: methods-most next-page: methods-summary +scala3: true +versionSpecific: true --- +<h5>Writing one line programs</h5> Scala 3 offers a new way to define programs that can be invoked from the command line: Adding a `@main` annotation to a method turns it into entry point of an executable program: +{% tabs method_1 %} +{% tab 'Scala 3 Only' for=method_1 %} + ```scala -@main def hello() = println("Hello, world") +@main def hello() = println("Hello, World") ``` -Just save that line of code in a file named something like *Hello.scala*---the filename doesn’t have to match the method name---and compile it with `scalac`: - -```bash -$ scalac Hello.scala -``` +{% endtab %} +{% endtabs %} -Then run it with `scala`: +To run this program, save the line of code in a file named as e.g. *Hello.scala*---the filename doesn’t have to match the method name---and run it with `scala`: ```bash -$ scala hello -Hello, world +$ scala run Hello.scala +Hello, World ``` A `@main` annotated method can be written either at the top-level (as shown), or inside a statically accessible object. In either case, the name of the program is in each case the name of the method, without any object prefixes. +Learn more about the `@main` annotation by reading the following sections, or by watching this video: +<div style="text-align: center"> + <iframe width="560" height="315" src="https://www.youtube.com/embed/uVMGPrH5_Uc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> +</div> ### Command line arguments With this approach your `@main` method can handle command line arguments, and those arguments can have different types. For example, given this `@main` method that takes an `Int`, a `String`, and a varargs `String*` parameter: +{% tabs method_2 %} +{% tab 'Scala 3 Only' for=method_2 %} + ```scala @main def happyBirthday(age: Int, name: String, others: String*) = val suffix = (age % 100) match @@ -49,32 +60,60 @@ For example, given this `@main` method that takes an `Int`, a `String`, and a va val sb = StringBuilder(s"Happy $age$suffix birthday, $name") for other <- others do sb.append(" and ").append(other) - sb.toString + println(sb.toString) ``` -When you compile that code, it creates a main program named `happyBirthday` that’s called like this: +{% endtab %} +{% endtabs %} + +Pass the arguments after `--`: ``` -$ scala happyBirthday 23 Lisa Peter +$ scala run happyBirthday.scala -- 23 Lisa Peter Happy 23rd Birthday, Lisa and Peter! ``` As shown, the `@main` method can have an arbitrary number of parameters. -For each parameter type there must be an instance of the *scala.util.FromString* type class that converts an argument `String` to the required parameter type. +For each parameter type there must be a [given instance]({% link _overviews/scala3-book/ca-context-parameters.md %}) of the `scala.util.CommandLineParser.FromString` type class that converts an argument `String` to the required parameter type. Also as shown, a main method’s parameter list can end in a repeated parameter like `String*` that takes all remaining arguments given on the command line. The program implemented from an `@main` method checks that there are enough arguments on the command line to fill in all parameters, and that the argument strings can be converted to the required types. If a check fails, the program is terminated with an error message: ``` -$ scala happyBirthday 22 +$ scala run happyBirthday.scala -- 22 Illegal command line after first argument: more arguments expected -$ scala happyBirthday sixty Fred +$ scala run happyBirthday.scala -- sixty Fred Illegal command line: java.lang.NumberFormatException: For input string: "sixty" ``` +## User-defined types as parameters + +As mentioned up above, the compiler looks for a given instance of the +`scala.util.CommandLineParser.FromString` typeclass for the type of the +argument. For example, let's say you have a custom `Color` type that you want to +use as a parameter. You would do this like you see below: + +{% tabs method_3 %} +{% tab 'Scala 3 Only' for=method_3 %} + +```scala +enum Color: + case Red, Green, Blue + +given CommandLineParser.FromString[Color] with + def fromString(value: String): Color = Color.valueOf(value) + +@main def run(color: Color): Unit = + println(s"The color is ${color.toString}") +``` + +{% endtab %} +{% endtabs %} +This works the same for your own user types in your program as well as types you +might be using from another library. ## The details @@ -82,10 +121,13 @@ The Scala compiler generates a program from an `@main` method `f` as follows: - It creates a class named `f` in the package where the `@main` method was found. - The class has a static method `main` with the usual signature of a Java `main` method: it takes an `Array[String]` as argument and returns `Unit`. -- The generated `main` method calls method `f` with arguments converted using methods in the `scala.util.CommandLineParser` object. +- The generated `main` method calls method `f` with arguments converted using methods in the `scala.util.CommandLineParser.FromString` object. For instance, the `happyBirthday` method above generates additional code equivalent to the following class: +{% tabs method_4 %} +{% tab 'Scala 3 Only' for=method_4 %} + ```scala final class happyBirthday { import scala.util.{CommandLineParser as CLP} @@ -94,7 +136,7 @@ final class happyBirthday { happyBirthday( CLP.parseArgument[Int](args, 0), CLP.parseArgument[String](args, 1), - CLP.parseRemainingArguments[String](args, 2)) + CLP.parseRemainingArguments[String](args, 2)*) catch { case error: CLP.ParseError => CLP.showError(error) } @@ -105,35 +147,40 @@ final class happyBirthday { > This feature is not available for user programs in Scala. > Regular “static” members are generated in Scala using objects instead. +{% endtab %} +{% endtabs %} - -## Scala 3 compared to Scala 2 +## Backwards Compatibility with Scala 2 `@main` methods are the recommended way to generate programs that can be invoked from the command line in Scala 3. They replace the previous approach in Scala 2, which was to create an `object` that extends the `App` class: -```scala -// scala 2 -object happyBirthday extends App { - // needs by-hand parsing of the command line arguments ... -} -``` - The previous functionality of `App`, which relied on the “magic” `DelayedInit` trait, is no longer available. `App` still exists in limited form for now, but it doesn’t support command line arguments and will be deprecated in the future. -If programs need to cross-build between Scala 2 and Scala 3, it’s recommended to use an explicit `main` method with an `Array[String]` argument instead: +If programs need to cross-build between Scala 2 and Scala 3, it’s recommended to use an `object` with an explicit `main` method and a single `Array[String]` argument instead: + +{% tabs method_5 %} +{% tab 'Scala 2 and 3' %} ```scala -object happyBirthday: - def main(args: Array[String]) = println("Hello, world") +object happyBirthday { + private def happyBirthday(age: Int, name: String, others: String*) = { + ... // same as before + } + def main(args: Array[String]): Unit = + happyBirthday(args(0).toInt, args(1), args.drop(2).toIndexedSeq:_*) +} ``` -If you place that code in a file named *happyBirthday.scala*, you can then compile it with `scalac` and run it with `scala`, as shown previously: +> note that here we use `:_*` to pass a vararg argument, which remains in Scala 3 for backwards compatibility. -```bash -$ scalac happyBirthday.scala +{% endtab %} +{% endtabs %} -$ scala happyBirthday -Hello, world +If you place that code in a file named *happyBirthday.scala*, you can then compile and run it with `scala`, as shown previously: + +```bash +$ scala run happyBirthday.scala -- 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! ``` diff --git a/_overviews/scala3-book/methods-most.md b/_overviews/scala3-book/methods-most.md index 233c51b847..2a282cdf28 100644 --- a/_overviews/scala3-book/methods-most.md +++ b/_overviews/scala3-book/methods-most.md @@ -2,7 +2,8 @@ title: Method Features type: section description: This section introduces Scala 3 methods, including main methods, extension methods, and more. -num: 24 +languages: [ru, zh-cn] +num: 25 previous-page: methods-intro next-page: methods-main-methods --- @@ -13,15 +14,29 @@ This section introduces the various aspects of how to define and call methods in Scala methods have many features, including these: -- Generic (type) parameters +- Type parameters - Default parameter values - Multiple parameter groups - Context-provided parameters - By-name parameters -- ... +- and more... Some of these features are demonstrated in this section, but when you’re defining a “simple” method that doesn’t use those features, the syntax looks like this: +{% tabs method_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = { + // the method body + // goes here +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_1 %} + ```scala def methodName(param1: Type1, param2: Type2): ReturnType = // the method body @@ -29,6 +44,9 @@ def methodName(param1: Type1, param2: Type2): ReturnType = end methodName // this is optional ``` +{% endtab %} +{% endtabs %} + In that syntax: - The keyword `def` is used to define a method @@ -41,27 +59,40 @@ In that syntax: Here are two examples of a one-line method named `add` that takes two `Int` input parameters. The first version explicitly shows the method’s `Int` return type, and the second does not: +{% tabs method_2 %} +{% tab 'Scala 2 and 3' for=method_2 %} + ```scala def add(a: Int, b: Int): Int = a + b def add(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} + It is recommended to annotate publicly visible methods with their return type. Declaring the return type can make it easier to understand it when you look at it months or years later, or when you look at another person’s code. - - ## Calling methods Invoking a method is straightforward: +{% tabs method_3 %} +{% tab 'Scala 2 and 3' for=method_3 %} + ```scala val x = add(1, 2) // 3 ``` +{% endtab %} +{% endtabs %} + The Scala collections classes have dozens of built-in methods. These examples show how to call them: +{% tabs method_4 %} +{% tab 'Scala 2 and 3' for=method_4 %} + ```scala val x = List(1, 2, 3) @@ -70,18 +101,34 @@ x.contains(1) // true x.map(_ * 10) // List(10, 20, 30) ``` +{% endtab %} +{% endtabs %} + Notice: - `size` takes no arguments, and returns the number of elements in the list - The `contains` method takes one argument, the value to search for - `map` takes one argument, a function; in this case an anonymous function is passed into it - - ## Multiline methods When a method is longer than one line, start the method body on the second line, indented to the right: +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = { + // imagine that this body requires multiple lines + val sum = a + b + sum * 2 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_5 %} + ```scala def addThenDouble(a: Int, b: Int): Int = // imagine that this body requires multiple lines @@ -89,6 +136,9 @@ def addThenDouble(a: Int, b: Int): Int = sum * 2 ``` +{% endtab %} +{% endtabs %} + In that method: - `sum` is an immutable local variable; it can’t be accessed outside of the method @@ -96,20 +146,32 @@ In that method: When you paste that code into the REPL, you’ll see that it works as desired: +{% tabs method_6 %} +{% tab 'Scala 2 and 3' for=method_6 %} + ```scala scala> addThenDouble(1, 1) res0: Int = 4 ``` +{% endtab %} +{% endtabs %} + Notice that there’s no need for a `return` statement at the end of the method. Because almost everything in Scala is an _expression_---meaning that each line of code returns (or _evaluates to_) a value---there’s no need to use `return`. This becomes more clear when you condense that method and write it on one line: +{% tabs method_7 %} +{% tab 'Scala 2 and 3' for=method_7 %} + ```scala def addThenDouble(a: Int, b: Int): Int = (a + b) * 2 ``` +{% endtab %} +{% endtabs %} + The body of a method can use all the different features of the language: - `if`/`else` expressions @@ -122,6 +184,21 @@ The body of a method can use all the different features of the language: As an example of a real-world multiline method, this `getStackTraceAsString` method converts its `Throwable` input parameter into a well-formatted `String`: +{% tabs method_8 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_8 %} + +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter() + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_8 %} + ```scala def getStackTraceAsString(t: Throwable): String = val sw = StringWriter() @@ -129,32 +206,57 @@ def getStackTraceAsString(t: Throwable): String = sw.toString ``` +{% endtab %} +{% endtabs %} + In that method: - The first line assigns a new instance of `StringWriter` to the value binder `sw` - The second line stores the stack trace content into the `StringWriter` - The third line yields the `String` representation of the stack trace - ## Default parameter values Method parameters can have default values. In this example, default values are given for both the `timeout` and `protocol` parameters: +{% tabs method_9 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_9 %} + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = { + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // more code here ... +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_9 %} + ```scala def makeConnection(timeout: Int = 5_000, protocol: String = "http") = println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") // more code here ... ``` +{% endtab %} +{% endtabs %} + Because the parameters have default values, the method can be called in these ways: +{% tabs method_10 %} +{% tab 'Scala 2 and 3' for=method_10 %} + ```scala makeConnection() // timeout = 5000, protocol = http makeConnection(2_000) // timeout = 2000, protocol = http makeConnection(3_000, "https") // timeout = 3000, protocol = https ``` +{% endtab %} +{% endtabs %} + Here are a few key points about those examples: - In the first example no arguments are provided, so the method uses the default parameter values of `5_000` and `http` @@ -163,13 +265,14 @@ Here are a few key points about those examples: Notice that by using default parameter values, it appears to the consumer that they can use three different overridden methods. - - ## Named parameters If you prefer, you can also use the names of the method parameters when calling a method. For instance, `makeConnection` can also be called in these ways: +{% tabs method_11 %} +{% tab 'Scala 2 and 3' for=method_11 %} + ```scala makeConnection(timeout=10_000) makeConnection(protocol="https") @@ -177,15 +280,27 @@ makeConnection(timeout=10_000, protocol="https") makeConnection(protocol="https", timeout=10_000) ``` +{% endtab %} +{% endtabs %} + In some frameworks named parameters are heavily used. They’re also very useful when multiple method parameters have the same type: +{% tabs method_12 %} +{% tab 'Scala 2 and 3' for=method_12 %} + ```scala engage(true, true, true, false) ``` +{% endtab %} +{% endtabs %} + Without help from an IDE that code can be hard to read, but this code is much more clear and obvious: +{% tabs method_13 %} +{% tab 'Scala 2 and 3' for=method_13 %} + ```scala engage( speedIsSet = true, @@ -195,7 +310,8 @@ engage( ) ``` - +{% endtab %} +{% endtabs %} ## A suggestion about methods that take no parameters @@ -208,30 +324,56 @@ When you create arity-0 methods: For example, this method performs a side effect, so it’s declared with empty parentheses: +{% tabs method_14 %} +{% tab 'Scala 2 and 3' for=method_14 %} + ```scala def speak() = println("hi") ``` +{% endtab %} +{% endtabs %} + Doing this requires callers of the method to use open parentheses when calling the method: +{% tabs method_15 %} +{% tab 'Scala 2 and 3' for=method_15 %} + ```scala speak // error: "method speak must be called with () argument" speak() // prints "hi" ``` +{% endtab %} +{% endtabs %} + While this is just a convention, following it dramatically improves code readability: It makes it easier to understand at a glance that an arity-0 method performs side effects. {% comment %} Some of that wording comes from this page: https://docs.scala-lang.org/style/method-invocation.html {% endcomment %} - - ## Using `if` as a method body Because `if`/`else` expressions return a value, they can be used as the body of a method. Here’s a method named `isTruthy` that implements the Perl definitions of `true` and `false`: +{% tabs method_16 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_16 %} + +```scala +def isTruthy(a: Any) = { + if (a == 0 || a == "" || a == false) + false + else + true +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_16 %} + ```scala def isTruthy(a: Any) = if a == 0 || a == "" || a == false then @@ -240,8 +382,14 @@ def isTruthy(a: Any) = true ``` +{% endtab %} +{% endtabs %} + These examples show how that method works: +{% tabs method_17 %} +{% tab 'Scala 2 and 3' for=method_17 %} + ```scala isTruthy(0) // false isTruthy("") // false @@ -249,28 +397,62 @@ isTruthy("hi") // true isTruthy(1.0) // true ``` - +{% endtab %} +{% endtabs %} ## Using `match` as a method body A `match` expression can also be used as the entire method body, and often is. Here’s another version of `isTruthy`, written with a `match` expression : +{% tabs method_18 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_18 %} + +```scala +def isTruthy(a: Any) = a match { + case 0 | "" | false => false + case _ => true +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_18 %} + ```scala def isTruthy(a: Matchable) = a match case 0 | "" | false => false case _ => true ``` -This method works just like the previous method that used an `if`/`else` expression. We use `Matchable` instead of `Any` as the parameter's type to accept any value that supports pattern matching. +> This method works just like the previous method that used an `if`/`else` expression. We use `Matchable` instead of `Any` as the parameter's type to accept any value that supports pattern matching. -For more details on the `Matchable` trait, see the [Reference documentation][reference_matchable]. +> For more details on the `Matchable` trait, see the [Reference documentation][reference_matchable]. +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +{% endtab %} +{% endtabs %} ## Controlling visibility in classes In classes, objects, traits, and enums, Scala methods are public by default, so the `Dog` instance created here can access the `speak` method: +{% tabs method_19 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_19 %} + +```scala +class Dog { + def speak() = println("Woof") +} + +val d = new Dog +d.speak() // prints "Woof" +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_19 %} + ```scala class Dog: def speak() = println("Woof") @@ -279,9 +461,30 @@ val d = new Dog d.speak() // prints "Woof" ``` +{% endtab %} +{% endtabs %} + Methods can also be marked as `private`. This makes them private to the current class, so they can’t be called nor overridden in subclasses: +{% tabs method_20 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_20 %} + +```scala +class Animal { + private def breathe() = println("I’m breathing") +} + +class Cat extends Animal { + // this method won’t compile + override def breathe() = println("Yo, I’m totally breathing") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_20 %} + ```scala class Animal: private def breathe() = println("I’m breathing") @@ -291,8 +494,38 @@ class Cat extends Animal: override def breathe() = println("Yo, I’m totally breathing") ``` +{% endtab %} +{% endtabs %} + If you want to make a method private to the current class and also allow subclasses to call it or override it, mark the method as `protected`, as shown with the `speak` method in this example: +{% tabs method_21 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_21 %} + +```scala +class Animal { + private def breathe() = println("I’m breathing") + def walk() = { + breathe() + println("I’m walking") + } + protected def speak() = println("Hello?") +} + +class Cat extends Animal { + override def speak() = println("Meow") +} + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // won’t compile because it’s private +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_21 %} + ```scala class Animal: private def breathe() = println("I’m breathing") @@ -310,13 +543,15 @@ cat.speak() cat.breathe() // won’t compile because it’s private ``` +{% endtab %} +{% endtabs %} + The `protected` setting means: - The method (or field) can be accessed by other instances of the same class - It is not visible by other code in the current package - It is available to subclasses - ## Objects can contain methods Earlier you saw that traits and classes can have methods. @@ -324,6 +559,38 @@ The Scala `object` keyword is used to create a singleton class, and an object ca This is a nice way to group a set of “utility” methods. For instance, this object contains a collection of methods that work on strings: +{% tabs method_22 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_22 %} + +```scala +object StringUtils { + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_22 %} + ```scala object StringUtils: @@ -349,39 +616,68 @@ object StringUtils: end StringUtils ``` - +{% endtab %} +{% endtabs %} ## Extension methods -Extension methods are discussed in the [Extension methods section][extension] of the Contextual Abstraction chapter. -Their main purpose is to let you add new functionality to closed classes. -As shown in that section, imagine that you have a `Circle` class, but you can’t change its source code. -For instance, it may be defined like this in a third-party library: +There are many situations where you would like to add functionality to closed classes. +For example, imagine that you have a `Circle` class, but you can’t change its source code. +It could be defined like this in a third-party library: + +{% tabs method_23 %} +{% tab 'Scala 2 and 3' for=method_23 %} ```scala case class Circle(x: Double, y: Double, radius: Double) ``` +{% endtab %} +{% endtabs %} + When you want to add methods to this class, you can define them as extension methods, like this: +{% tabs method_24 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_24 %} + +```scala +implicit class CircleOps(c: Circle) { + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +} +``` +In Scala 2 use an `implicit class`, find out more details [here](/overviews/core/implicit-classes.html). + +{% endtab %} +{% tab 'Scala 3' for=method_24 %} + ```scala extension (c: Circle) def circumference: Double = c.radius * math.Pi * 2 def diameter: Double = c.radius * 2 def area: Double = math.Pi * c.radius * c.radius ``` +In Scala 3 use the new `extension` construct. For more details see chapters in [this book][extension], or the [Scala 3 reference][reference-ext]. + +[reference-ext]: {{ site.scala3ref }}/contextual/extension-methods.html +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +{% endtab %} +{% endtabs %} Now when you have a `Circle` instance named `aCircle`, you can call those methods like this: +{% tabs method_25 %} +{% tab 'Scala 2 and 3' for=method_25 %} + ```scala aCircle.circumference aCircle.diameter aCircle.area ``` -See the [Extension methods section][reference_extension_methods] of this book, and the [“Extension methods” Reference page][reference] for more details. - - +{% endtab %} +{% endtabs %} ## Even more @@ -394,13 +690,13 @@ There’s even more to know about methods, including how to: - Handle exceptions - Use vararg input parameters - Write methods that have multiple parameter groups (partially-applied functions) -- Create methods that have generic type parameters - -See the [Reference documentation][reference] for more details on these features. - +- Create methods that have type parameters +{% comment %} +Jamie: there really needs better linking here - previously it was to the Scala 3 Reference, which doesnt cover any +of this +{% endcomment %} +See the other chapters in this book for more details on these features. -[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} [reference_extension_methods]: {{ site.scala3ref }}/contextual/extension-methods.html -[reference]: {{ site.scala3ref }}/overview.html [reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_overviews/scala3-book/methods-summary.md b/_overviews/scala3-book/methods-summary.md index 9894c6d4c9..eafac85889 100644 --- a/_overviews/scala3-book/methods-summary.md +++ b/_overviews/scala3-book/methods-summary.md @@ -2,7 +2,8 @@ title: Summary type: section description: This section summarizes the previous sections on Scala 3 methods. -num: 26 +languages: [ru, zh-cn] +num: 27 previous-page: methods-main-methods next-page: fun-intro --- @@ -17,7 +18,7 @@ There’s even more to know about methods, including how to: - Handle exceptions - Use vararg input parameters - Write methods that have multiple parameter groups (partially-applied functions) -- Create methods that have generic type parameters +- Create methods that have type parameters See the [Reference documentation][reference] for more details on these features. diff --git a/_overviews/scala3-book/packaging-imports.md b/_overviews/scala3-book/packaging-imports.md index 07450f72f8..f5665c28fa 100644 --- a/_overviews/scala3-book/packaging-imports.md +++ b/_overviews/scala3-book/packaging-imports.md @@ -2,7 +2,8 @@ title: Packaging and Imports type: chapter description: A discussion of using packages and imports to organize your code, build related modules of code, control scope, and help prevent namespace collisions. -num: 35 +languages: [ru, zh-cn] +num: 37 previous-page: fun-summary next-page: collections-intro --- @@ -20,24 +21,25 @@ With Scala you can: These features are demonstrated in the following examples. - - ## Creating a package Packages are created by declaring one or more package names at the top of a Scala file. For example, when your domain name is _acme.com_ and you’re working in the _model_ package of an application named _myapp_, your package declaration looks like this: +{% tabs packaging-imports-1 %} +{% tab 'Scala 2 and 3' %} ```scala package com.acme.myapp.model class Person ... ``` +{% endtab %} +{% endtabs %} By convention, package names should be all lower case, and the formal naming convention is *\<top-level-domain>.\<domain-name>.\<project-name>.\<module-name>*. Although it’s not required, package names typically follow directory structure names, so if you follow this convention, a `Person` class in this project will be found in a *MyApp/src/main/scala/com/acme/myapp/model/Person.scala* file. - ### Using multiple packages in the same file The syntax shown above applies to the entire source file: all the definitions in the file @@ -45,7 +47,24 @@ The syntax shown above applies to the entire source file: all the definitions in at the beginning of the file. Alternatively, it is possible to write package clauses that apply only to the definitions -they contain: +they contain: + +{% tabs packaging-imports-0 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package users { + + package administrators { // the full name of this package is users.administrators + class AdminUser // the full name of this class users.administrators.AdminUser + } + package normalusers { // the full name of this package is users.normalusers + class NormalUser // the full name of this class is users.normalusers.NormalUser + } +} +``` + +{% endtab %} +{% tab 'Scala 3' %} ```scala package users: @@ -56,14 +75,14 @@ package users: package normalusers: // the full name of this package is users.normalusers class NormalUser // the full name of this class is users.normalusers.NormalUser ``` +{% endtab %} +{% endtabs %} Note that the package names are followed by a colon, and that the definitions within a package are indented. The advantages of this approach are that it allows for package nesting, and provides more obvious control of scope and encapsulation, especially within the same file. - - ## Import statements, Part 1 Import statements are used to access entities in other packages. @@ -75,12 +94,27 @@ Import statements fall into two main categories: If you’re used to a language like Java, the first class of import statements is similar to what Java uses, with a slightly different syntax that allows for more flexibility. These examples demonstrate some of that flexibility: -```` +{% tabs packaging-imports-2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import users._ // import everything from the `users` package +import users.User // import only the `User` class +import users.{User, UserPreferences} // import only two selected members +import users.{UserPreferences => UPrefs} // rename a member as you import it +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-2 %} + +```scala import users.* // import everything from the `users` package import users.User // import only the `User` class import users.{User, UserPreferences} // import only two selected members import users.{UserPreferences as UPrefs} // rename a member as you import it -```` +``` + +{% endtab %} +{% endtabs %} Those examples are meant to give you a taste of how the first class of `import` statements work. They’re explained more in the subsections that follow. @@ -92,90 +126,165 @@ A note before moving on: > Import clauses are not required for accessing members of the same package. - - ### Importing one or more members In Scala you can import one member from a package like this: +{% tabs packaging-imports-3 %} +{% tab 'Scala 2 and 3' %} ```scala import scala.concurrent.Future ``` +{% endtab %} +{% endtabs %} and multiple members like this: +{% tabs packaging-imports-4 %} +{% tab 'Scala 2 and 3' %} ```scala import scala.concurrent.Future import scala.concurrent.Promise import scala.concurrent.blocking ``` +{% endtab %} +{% endtabs %} When importing multiple members, you can import them more concisely like this: +{% tabs packaging-imports-5 %} +{% tab 'Scala 2 and 3' %} ```scala import scala.concurrent.{Future, Promise, blocking} ``` +{% endtab %} +{% endtabs %} When you want to import everything from the *scala.concurrent* package, use this syntax: +{% tabs packaging-imports-6 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.concurrent._ +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-6 %} + ```scala import scala.concurrent.* ``` +{% endtab %} +{% endtabs %} ### Renaming members on import Sometimes it can help to rename entities when you import them to avoid name collisions. For instance, if you want to use the Scala `List` class and also the *java.util.List* class at the same time, you can rename the *java.util.List* class when you import it: +{% tabs packaging-imports-7 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{List => JavaList} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-7 %} + ```scala import java.util.{List as JavaList} ``` +{% endtab %} +{% endtabs %} Now you use the name `JavaList` to refer to that class, and use `List` to refer to the Scala list class. You can also rename multiple members at one time using this syntax: +{% tabs packaging-imports-8 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{Date => JDate, HashMap => JHashMap, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-8 %} + ```scala import java.util.{Date as JDate, HashMap as JHashMap, *} ``` -That line of code says, “Rename the `Date` and `HashMap` classes as shown, and import everything else in the _java.util_ package without renaming any other members.” +{% endtab %} +{% endtabs %} +That line of code says, “Rename the `Date` and `HashMap` classes as shown, and import everything else in the _java.util_ package without renaming any other members.” ### Hiding members on import You can also *hide* members during the import process. This `import` statement hides the *java.util.Random* class, while importing everything else in the *java.util* package: +{% tabs packaging-imports-9 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{Random => _, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-9 %} + ```scala import java.util.{Random as _, *} ``` +{% endtab %} +{% endtabs %} If you try to access the `Random` class it won’t work, but you can access all other members from that package: +{% tabs packaging-imports-10 %} +{% tab 'Scala 2 and 3' %} ```scala val r = new Random // won’t compile new ArrayList // works ``` +{% endtab %} +{% endtabs %} #### Hiding multiple members To hide multiple members during the import process, list them before using the final wildcard import: +{% tabs packaging-imports-11 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{List => _, Map => _, Set => _, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-11 %} + ```scala scala> import java.util.{List as _, Map as _, Set as _, *} ``` +{% endtab %} +{% endtabs %} Once again those classes are hidden, but you can use all other classes in *java.util*: +{% tabs packaging-imports-12 %} +{% tab 'Scala 2 and 3' %} ```scala scala> new ArrayList[String] val res0: java.util.ArrayList[String] = [] ``` +{% endtab %} +{% endtabs %} Because those Java classes are hidden, you can also use the Scala `List`, `Set`, and `Map` classes without having a naming collision: +{% tabs packaging-imports-13 %} +{% tab 'Scala 2 and 3' %} ```scala scala> val a = List(1, 2, 3) val a: List[Int] = List(1, 2, 3) @@ -186,32 +295,75 @@ val b: Set[Int] = Set(1, 2, 3) scala> val c = Map(1 -> 1, 2 -> 2) val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2) ``` - +{% endtab %} +{% endtabs %} ### Use imports anywhere In Scala, `import` statements can be anywhere. They can be used at the top of a source code file: +{% tabs packaging-imports-14 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package foo + +import scala.util.Random + +class ClassA { + def printRandom(): Unit = { + val r = new Random // use the imported class + // more code here... + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-14 %} + ```scala package foo import scala.util.Random class ClassA: - def printRandom: + def printRandom(): Unit = val r = new Random // use the imported class // more code here... ``` +{% endtab %} +{% endtabs %} You can also use `import` statements closer to the point where they are needed, if you prefer: +{% tabs packaging-imports-15 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package foo + +class ClassA { + import scala.util.Random // inside ClassA + def printRandom(): Unit = { + val r = new Random + // more code here... + } +} + +class ClassB { + // the Random class is not visible here + val r = new Random // this code will not compile +} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-15 %} + ```scala package foo class ClassA: import scala.util.Random // inside ClassA - def printRandom { + def printRandom(): Unit = val r = new Random // more code here... @@ -220,6 +372,8 @@ class ClassB: val r = new Random // this code will not compile ``` +{% endtab %} +{% endtabs %} ### “Static” imports @@ -227,19 +381,43 @@ When you want to import members in a way similar to the Java “static import” Use this syntax to import all static members of the Java `Math` class: +{% tabs packaging-imports-16 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.lang.Math._ +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-16 %} + ```scala import java.lang.Math.* ``` +{% endtab %} +{% endtabs %} Now you can access static `Math` class methods like `sin` and `cos` without having to precede them with the class name: +{% tabs packaging-imports-17 class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -import java.lang.Math.* +import java.lang.Math._ val a = sin(0) // 0.0 val b = cos(PI) // -1.0 ``` +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-17 %} + +```scala +import java.lang.Math.* + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` +{% endtab %} +{% endtabs %} ### Packages imported by default @@ -252,45 +430,62 @@ The members of the Scala object `Predef` are also imported by default. > If you ever wondered why you can use classes like `List`, `Vector`, `Map`, etc., without importing them, they’re available because of definitions in the `Predef` object. - - ### Handling naming conflicts In the rare event there’s a naming conflict and you need to import something from the root of the project, prefix the package name with `_root_`: -``` +{% tabs packaging-imports-18 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala package accounts -import _root_.accounts.* +import _root_.accounts._ ``` +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-18 %} +```scala +package accounts + +import _root_.accounts.* +``` +{% endtab %} +{% endtabs %} ## Importing `given` instances -As you’ll see in the [Contextual Abstractions][contextual] chapter, a special form of the `import` statement is used to import `given` instances. +As you’ll see in the [Contextual Abstractions][contextual] chapter, in Scala 3 a special form of the `import` statement is used to import `given` instances. The basic form is shown in this example: +{% tabs packaging-imports-19 %} +{% tab 'Scala 3 only' %} ```scala object A: class TC - given tc as TC + given tc: TC def f(using TC) = ??? object B: import A.* // import all non-given members import A.given // import the given instance ``` +{% endtab %} +{% endtabs %} In this code, the `import A.*` clause of object `B` imports all members of `A` *except* the `given` instance `tc`. Conversely, the second import, `import A.given`, imports *only* that `given` instance. The two `import` clauses can also be merged into one: +{% tabs packaging-imports-20 %} +{% tab 'Scala 3 only' %} ```scala object B: import A.{given, *} ``` - +{% endtab %} +{% endtabs %} +In Scala 2, that style of import does not exist. Implicit definitions are always imported by the wildcard import. ### Discussion The wildcard selector `*` brings all definitions other than givens or extensions into scope, whereas a `given` selector brings all *givens*---including those resulting from extensions---into scope. @@ -302,98 +497,133 @@ These rules have two main benefits: - It enables importing all givens without importing anything else. This is particularly important since givens can be anonymous, so the usual use of named imports is not practical. - ### By-type imports Since givens can be anonymous, it’s not always practical to import them by their name, and wildcard imports are typically used instead. *By-type imports* provide a more specific alternative to wildcard imports, which makes it more clear what is imported: +{% tabs packaging-imports-21 %} +{% tab 'Scala 3 only' %} ```scala import A.{given TC} ``` +{% endtab %} +{% endtabs %} This imports any `given` in `A` that has a type which conforms to `TC`. Importing givens of several types `T1,...,Tn` is expressed by multiple `given` selectors: +{% tabs packaging-imports-22 %} +{% tab 'Scala 3 only' %} ```scala import A.{given T1, ..., given Tn} ``` +{% endtab %} +{% endtabs %} Importing all `given` instances of a parameterized type is expressed by wildcard arguments. For example, when you have this `object`: +{% tabs packaging-imports-23 %} +{% tab 'Scala 3 only' %} ```scala object Instances: - given intOrd as Ordering[Int] - given listOrd[T: Ordering] as Ordering[List[T]] - given ec as ExecutionContext = ... - given im as Monoid[Int] + given intOrd: Ordering[Int] + given listOrd[T: Ordering]: Ordering[List[T]] + given ec: ExecutionContext = ... + given im: Monoid[Int] ``` +{% endtab %} +{% endtabs %} This import statement imports the `intOrd`, `listOrd`, and `ec` instances, but leaves out the `im` instance because it doesn’t fit any of the specified bounds: +{% tabs packaging-imports-24 %} +{% tab 'Scala 3 only' %} ```scala import Instances.{given Ordering[?], given ExecutionContext} ``` +{% endtab %} +{% endtabs %} By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, this import clause imports `im`, `intOrd`, and `listOrd`, but leaves out `ec`: +{% tabs packaging-imports-25 %} +{% tab 'Scala 3 only' %} ```scala import Instances.{im, given Ordering[?]} ``` - +{% endtab %} +{% endtabs %} ### An example As a concrete example, imagine that you have this `MonthConversions` object that contains two `given` definitions: +{% tabs packaging-imports-26 %} +{% tab 'Scala 3 only' %} + ```scala object MonthConversions: trait MonthConverter[A]: def convert(a: A): String - given intMonthConverter as MonthConverter[Int]: - def convert(i: Int): String = + given intMonthConverter: MonthConverter[Int] with + def convert(i: Int): String = i match case 1 => "January" case 2 => "February" // more cases here ... - given stringMonthConverter as MonthConverter[String]: - def convert(s: String): String = + given stringMonthConverter: MonthConverter[String] with + def convert(s: String): String = s match case "jan" => "January" case "feb" => "February" // more cases here ... -} ``` +{% endtab %} +{% endtabs %} To import those givens into the current scope, use these two `import` statements: +{% tabs packaging-imports-27 %} +{% tab 'Scala 3 only' %} + ```scala import MonthConversions.* import MonthConversions.{given MonthConverter[?]} ``` +{% endtab %} +{% endtabs %} Now you can create a method that uses those `given` instances: +{% tabs packaging-imports-28 %} +{% tab 'Scala 3 only' %} + ```scala def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = monthConverter.convert(a) ``` +{% endtab %} +{% endtabs %} Then you can use that method in your application: +{% tabs packaging-imports-29 %} +{% tab 'Scala 3 only' %} + ```scala @main def main = println(genericMonthConverter(1)) // January println(genericMonthConverter("jan")) // January ``` +{% endtab %} +{% endtabs %} As mentioned, one of the key design benefits of the “import given” syntax is to make it clear where givens in scope come from, and it’s clear in these `import` statements that the givens come from the `MonthConversions` object. - - [contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_overviews/scala3-book/scala-features.md b/_overviews/scala3-book/scala-features.md index b5bd769e29..c1d1ca834c 100644 --- a/_overviews/scala3-book/scala-features.md +++ b/_overviews/scala3-book/scala-features.md @@ -1,7 +1,8 @@ --- -title: Scala 3 Features +title: Scala Features type: chapter -description: This page discusses the main features of the Scala 3 programming language. +description: This page discusses the main features of the Scala programming language. +languages: [ru, zh-cn] num: 2 previous-page: introduction next-page: why-scala-3 @@ -50,13 +51,15 @@ Second, with the use of lambdas and higher-order functions, you write your code As the functional programming saying goes, in Scala you write _what_ you want, not _how_ to achieve it. That is, we don’t write imperative code like this: +{% tabs scala-features-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-1 %} ```scala import scala.collection.mutable.ListBuffer def double(ints: List[Int]): List[Int] = { val buffer = new ListBuffer[Int]() for (i <- ints) { - buffer += i * 2 + buffer += i * 2 } buffer.toList } @@ -64,13 +67,34 @@ def double(ints: List[Int]): List[Int] = { val oldNumbers = List(1, 2, 3) val newNumbers = double(oldNumbers) ``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-1 %} +```scala +import scala.collection.mutable.ListBuffer + +def double(ints: List[Int]): List[Int] = + val buffer = new ListBuffer[Int]() + for i <- ints do + buffer += i * 2 + buffer.toList + +val oldNumbers = List(1, 2, 3) +val newNumbers = double(oldNumbers) +``` +{% endtab %} +{% endtabs %} That code instructs the compiler what to do on a step-by-step basis. Instead, we write high-level, functional code using higher-order functions and lambdas like this to compute the same result: +{% tabs scala-features-2 %} +{% tab 'Scala 2 and 3' for=scala-features-2 %} ```scala val newNumbers = oldNumbers.map(_ * 2) ``` +{% endtab %} +{% endtabs %} + As you can see, that code is much more concise, easier to read, and easier to maintain. @@ -80,13 +104,20 @@ As you can see, that code is much more concise, easier to read, and easier to ma Scala has a concise, readable syntax. For instance, variables are created concisely, and their types are clear: +{% tabs scala-features-3 %} +{% tab 'Scala 2 and 3' for=scala-features-3 %} ```scala val nums = List(1,2,3) val p = Person("Martin", "Odersky") ``` +{% endtab %} +{% endtabs %} + Higher-order functions and lambdas make for concise code that’s readable: +{% tabs scala-features-4 %} +{% tab 'Scala 2 and 3' for=scala-features-4 %} ```scala nums.map(i => i * 2) // long form nums.map(_ * 2) // short form @@ -94,9 +125,29 @@ nums.map(_ * 2) // short form nums.filter(i => i > 1) nums.filter(_ > 1) ``` +{% endtab %} +{% endtabs %} Traits, classes, and methods are defined with a clean, light syntax: +{% tabs scala-features-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-5 %} +```scala mdoc +trait Animal { + def speak(): Unit +} + +trait HasTail { + def wagTail(): Unit +} + +class Dog extends Animal with HasTail { + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") +} +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-5 %} ```scala trait Animal: def speak(): Unit @@ -105,9 +156,12 @@ trait HasTail: def wagTail(): Unit class Dog extends Animal, HasTail: - def speak() = println("Woof") - def wagTail() = println("⎞⎜⎛ ⎞⎜⎛") + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") ``` +{% endtab %} +{% endtabs %} + Studies have shown that the time a developer spends _reading_ code to _writing_ code is at least a 10:1 ratio, so writing code that is concise _and_ readable is important. @@ -117,15 +171,33 @@ Studies have shown that the time a developer spends _reading_ code to _writing_ Scala is a statically-typed language, but thanks to its type inference capabilities it feels dynamic. All of these expressions look like a dynamically-typed language like Python or Ruby, but they’re all Scala: +{% tabs scala-features-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-6 %} +```scala +val s = "Hello" +val p = Person("Al", "Pacino") +val sum = nums.reduceLeft(_ + _) +val y = for (i <- nums) yield i * 2 +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-6 %} ```scala val s = "Hello" val p = Person("Al", "Pacino") val sum = nums.reduceLeft(_ + _) val y = for i <- nums yield i * 2 -val z = nums.filter(_ > 100) - .filter(_ < 10_000) - .map(_ * 2) +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) ``` +{% endtab %} +{% endtabs %} + As Heather Miller states, Scala is considered to be a [strong, statically-typed language](https://heather.miller.am/blog/types-in-scala.html), and you get all the benefits of static types: @@ -137,7 +209,7 @@ As Heather Miller states, Scala is considered to be a [strong, statically-typed - You can refactor your code with confidence - Method type declarations tell readers what the method does, and help serve as documentation - Scalability and maintainability: types help ensure correctness across arbitrarily large applications and development teams -- Strong typing in combination with excellent inference enables mechanisms like [contextual abstraction]({{ site.scala3ref }}/contextual.html) that allows you to omit boilerplate code. Often, this boilerplate code can be inferred by the compiler, based on type definitions and a given context. +- Strong typing in combination with excellent inference enables mechanisms like [contextual abstraction]({{ site.scala3ref }}/contextual) that allows you to omit boilerplate code. Often, this boilerplate code can be inferred by the compiler, based on type definitions and a given context. {% comment %} In that list: @@ -172,7 +244,7 @@ In particular, the type system supports: - [Intersection types]({% link _overviews/scala3-book/types-intersection.md %}) - [Union types]({% link _overviews/scala3-book/types-union.md %}) - [Type lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html) -- [`given` instances and `using` clauses]({% link _overviews/scala3-book/ca-given-using-clauses.md %}) +- [`given` instances and `using` clauses]({% link _overviews/scala3-book/ca-context-parameters.md %}) - [Extension methods]({% link _overviews/scala3-book/ca-extension-methods.md %}) - [Type classes]({% link _overviews/scala3-book/ca-type-classes.md %}) - [Multiversal equality]({% link _overviews/scala3-book/ca-multiversal-equality.md %}) @@ -220,7 +292,7 @@ Boxing and unboxing is completely transparent to the user. ### Supports FP/OOP fusion {% comment %} -NOTE: This text in the first line comes from this slide: https://twitter.com/alexelcu/status/996408359514525696 +NOTE: This text in the first line comes from this slide: https://x.com/alexelcu/status/996408359514525696 {% endcomment %} The essence of Scala is the fusion of functional programming and object-oriented programming in a typed setting: @@ -242,7 +314,7 @@ In Scala, a context parameter directly leads to an inferred argument term that c Use cases for this concept include implementing [type classes]({% link _overviews/scala3-book/ca-type-classes.md %}), establishing context, dependency injection, expressing capabilities, computing new types, and proving relationships between them. Scala 3 makes this process more clear than ever before. -Read about contextual abstractions in the [Reference documentation]({{ site.scala3ref }}/contextual.html). +Read about contextual abstractions in the [Reference documentation]({{ site.scala3ref }}/contextual). ### Client & server @@ -266,20 +338,28 @@ In regards to the second point, large libraries like [Akka](https://akka.io) and In regards to the first point, Java classes and libraries are used in Scala applications every day. For instance, in Scala you can read files with a Java `BufferedReader` and `FileReader`: +{% tabs scala-features-7 %} +{% tab 'Scala 2 and 3' for=scala-features-7 %} ```scala import java.io.* val br = BufferedReader(FileReader(filename)) // read the file with `br` ... ``` +{% endtab %} +{% endtabs %} Using Java code in Scala is generally seamless. Java collections can also be used in Scala, and if you want to use Scala’s rich collection class methods with them, you can convert them with just a few lines of code: +{% tabs scala-features-8 %} +{% tab 'Scala 2 and 3' for=scala-features-8 %} ```scala import scala.jdk.CollectionConverters.* val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq ``` +{% endtab %} +{% endtabs %} ### Wealth of libraries @@ -287,8 +367,7 @@ val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq As you’ll see in the third section of this page, Scala libraries and frameworks like these have been written to power busy websites and work with huge datasets: 1. The [Play Framework](https://www.playframework.com) is a lightweight, stateless, developer-friendly, web-friendly architecture for creating highly-scalable applications -2. [Lagom](https://www.lagomframework.com) is a microservices framework that helps you decompose your legacy monolith and build, test, and deploy entire systems of reactive microservices -3. [Apache Spark](https://spark.apache.org) is a unified analytics engine for big data processing, with built-in modules for streaming, SQL, machine learning and graph processing +2. [Apache Spark](https://spark.apache.org) is a unified analytics engine for big data processing, with built-in modules for streaming, SQL, machine learning and graph processing The [Awesome Scala list](https://github.com/lauris/awesome-scala) shows dozens of additional open source tools that developers have created to build Scala applications. @@ -303,7 +382,7 @@ Assuming you told someone about the previous high-level features and then they s ## Lower-level language features -Where the previous section covered high-level features of Scala 3, it’s interesting to note that at a high level you can make the same statements about both Scala 2 and Scala 3. +Where the previous section covered high-level features of Scala, it’s interesting to note that at a high level you can make the same statements about both Scala 2 and Scala 3. A decade ago Scala started with a strong foundation of desirable features, and as you’ll see in this section, those benefits have been improved with Scala 3. At a “sea level” view of the details---i.e., the language features programmers use everyday---Scala 3 has significant advantages over Scala 2: @@ -420,10 +499,9 @@ Some of the more notable libraries are listed below. - The [Play Framework](https://www.playframework.com) followed the Ruby on Rails model to become a lightweight, stateless, developer-friendly, web-friendly architecture for highly-scalable applications - [Scalatra](https://scalatra.org) is a tiny, high-performance, async web framework, inspired by Sinatra -- [Finatra](https://twitter.github.io/finatra) is Scala services built on TwitterServer and Finagle +- [Finatra](https://twitter.github.io/finatra) is Scala services built for X - [Scala.js](https://www.scala-js.org) is a strongly-typed replacement for JavaScript that provides a safer way to build robust front-end web applications - [ScalaJs-React](https://github.com/japgolly/scalajs-react) lifts Facebook’s React library into Scala.js, and endeavours to make it as type-safe and Scala-friendly as possible -- [Lagom](https://www.lagomframework.com) is a microservices framework that helps you decompose your legacy monolith and build, test, and deploy entire systems of Reactive microservices HTTP(S) libraries: @@ -458,7 +536,7 @@ Serialization: ### AI, machine learning -- [BigDL](https://github.com/intel-analytics/BigDL) (Distributed Deep Learning Framework for Apache Spark) for Apache Spark +- [BigDL](https://github.com/intel-analytics/BigDL) (Distributed Deep Learning Framework for Apache Spark) - [TensorFlow Scala](https://github.com/eaplatanios/tensorflow_scala) @@ -492,7 +570,7 @@ As this page shows, Scala has many terrific programming language features at a h [reference]: {{ site.scala3ref }}/overview.html [multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} [extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} -[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} [opaque_types]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_overviews/scala3-book/scala-for-java-devs.md b/_overviews/scala3-book/scala-for-java-devs.md index fc7c8ab82b..0ba8361778 100644 --- a/_overviews/scala3-book/scala-for-java-devs.md +++ b/_overviews/scala3-book/scala-for-java-devs.md @@ -2,7 +2,8 @@ title: Scala for Java Developers type: chapter description: This page is for Java developers who are interested in learning about Scala 3. -num: 73 +languages: [zh-cn] +num: 74 previous-page: interacting-with-java next-page: scala-for-javascript-devs --- @@ -359,7 +360,7 @@ This section compares Java interfaces to Scala traits, including how classes ext <tbody> <tr> <td class="java-block"> - <code>public interface Marker;</code> + <code>public interface Marker {};</code> </td> </tr> <tr> @@ -512,8 +513,8 @@ These interfaces and traits have concrete, implemented methods (default methods) <br> <br>// mix in the traits as DavidBanner <br>// is created - <br>val hulk = new DavidBanner with Big, - <br>  Angry, Green</code> + <br>val hulk = new DavidBanner with Big with Angry with Green + </code> </td> </tr> </tbody> @@ -812,7 +813,7 @@ Called a _ternary operator_ in Java: <code>val monthAsString = day match <br>  case 1 => "January" <br>  case 2 => "February" - <br>  _ => "Other" + <br>  case _ => "Other" </code> </td> </tr> @@ -977,8 +978,8 @@ val a = Array("a", "b") as being backed by this Java `String[]`: -```scala -String[] a = ["a", "b"]; +```java +String[] a = {"a", "b"}; ``` However, a Scala `Array` also has all of the functional methods you expect in a Scala collection, including `map` and `filter`: @@ -1063,7 +1064,7 @@ Pair<String, Integer> pair = Triplet<String, Integer, Double> triplet = Triplet.with("Eleven", 11, 11.0); -Quartet<String, Integer, Double,Person> triplet = +Quartet<String, Integer, Double, Person> quartet = Quartet.with("Eleven", 11, 11.0, new Person("Eleven")); ``` @@ -1305,7 +1306,7 @@ This includes: [equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} [error-handling]: {% link _overviews/scala3-book/fp-functional-error-handling.md %} [extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} -[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} [hofs]: {% link _overviews/scala3-book/fun-hofs.md %} [imports]: {% link _overviews/scala3-book/packaging-imports.md %} [modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_overviews/scala3-book/scala-for-javascript-devs.md b/_overviews/scala3-book/scala-for-javascript-devs.md index 462a1017ce..26c672ae99 100644 --- a/_overviews/scala3-book/scala-for-javascript-devs.md +++ b/_overviews/scala3-book/scala-for-javascript-devs.md @@ -2,7 +2,8 @@ title: Scala for JavaScript Developers type: chapter description: This chapter provides an introduction to Scala 3 for JavaScript developers -num: 74 +languages: [zh-cn] +num: 75 previous-page: scala-for-java-devs next-page: scala-for-python-devs --- @@ -50,7 +51,7 @@ Also at a high level, some of the differences between JavaScript and Scala are: - As a pure OOP language and a pure FP language, Scala encourages a fusion of OOP and FP, with functions for the logic and immutable objects for modularity - Scala has state of the art, third-party, open source functional programming libraries - Everything in Scala is an _expression_: constructs like `if` statements, `for` loops, `match` expressions, and even `try`/`catch` expressions all have return values -- The [Scala Native](https://scala-native.readthedocs.io/en/v0.3.9-docs) project lets you write “systems” level code, and also compiles to native executables +- The [Scala Native](https://scala-native.org/) project lets you write “systems” level code, and also compiles to native executables ### Programming level differences @@ -937,10 +938,10 @@ val nums = List(1, 2, 3) <tr> <td class="scala-block"> <code>// preferred - <br>for i <- ints do println(i) + <br>for i <- nums do println(i) <br> <br>// also available - <br>for (i <- ints) println(i)</code> + <br>for (i <- nums) println(i)</code> </td> </tr> </tbody> @@ -968,8 +969,8 @@ val nums = List(1, 2, 3) <tr> <td class="scala-block"> <code>// preferred - <br>for i <- ints do - <br>  val i = i * 2 + <br>for i <- nums do + <br>  val j = i * 2 <br>  println(j) <br> <br>// also available @@ -991,7 +992,7 @@ val nums = List(1, 2, 3) <code>let str = "ab"; <br>for (let i = 1; i < 3; i++) { <br>  for (var j = 0; j < str.length; j++) { - <br>    for (let k = 1; k < 11; k++) { + <br>    for (let k = 1; k < 11; k += 5) { <br>      let c = str.charAt(j); <br>      console.log(`i: ${i} j: ${c} k: ${k}`); <br>    } @@ -1365,7 +1366,7 @@ There are other concepts in Scala which currently have no equivalent in JavaScri [control]: {% link _overviews/scala3-book/control-structures.md %} [extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} [fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} -[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} [hofs]: {% link _overviews/scala3-book/fun-hofs.md %} [intersection-types]: {% link _overviews/scala3-book/types-intersection.md %} [modeling-fp]: {% link _overviews/scala3-book/domain-modeling-fp.md %} diff --git a/_overviews/scala3-book/scala-for-python-devs.md b/_overviews/scala3-book/scala-for-python-devs.md index 320bb80ecf..147f5977f7 100644 --- a/_overviews/scala3-book/scala-for-python-devs.md +++ b/_overviews/scala3-book/scala-for-python-devs.md @@ -2,9 +2,10 @@ title: Scala for Python Developers type: chapter description: This page is for Python developers who are interested in learning about Scala 3. -num: 75 +languages: [zh-cn] +num: 76 previous-page: scala-for-javascript-devs -next-page: +next-page: where-next --- {% include_relative scala4x.css %} @@ -49,10 +50,11 @@ At a high level, Scala shares these *similarities* with Python: Also at a high level, the _differences_ between Python and Scala are: - Python is dynamically typed, and Scala is statically typed + - Though it's dynamically typed, Python supports "gradual typing" with type hints, which are checked by static type checkers, like `mypy` - Though it’s statically typed, Scala features like type inference make it feel like a dynamic language - Python is interpreted, and Scala code is compiled to _.class_ files, and runs on the Java Virtual Machine (JVM) - In addition to running on the JVM, the [Scala.js](https://www.scala-js.org) project lets you use Scala as a JavaScript replacement -- The [Scala Native](https://scala-native.readthedocs.io/en/v0.3.9-docs) project lets you write “systems” level code, and compiles to native executables +- The [Scala Native](https://scala-native.org/) project lets you write “systems” level code, and compiles to native executables - Everything in Scala is an _expression_: constructs like `if` statements, `for` loops, `match` expressions, and even `try`/`catch` expressions all have return values - Scala idioms favor immutability by default: you’re encouraged to use immutable variables and immutable collections - Scala has excellent support for [concurrent and parallel programming][concurrency] @@ -67,6 +69,7 @@ This section looks at the similarities you’ll see between Python and Scala whe - The syntax for defining methods is similar - Both have lists, dictionaries (maps), sets, and tuples - Both have comprehensions for mapping and filtering +- Both have terrific IDE support - With Scala 3’s [toplevel definitions][toplevel] you can put method, field, and other definitions anywhere - One difference is that Python can operate without even declaring a single method, while Scala 3 can’t do _everything_ at the toplevel; for instance, a [main method][main-method] (`@main def`) is required to start a Scala application @@ -82,7 +85,6 @@ Also at a programming level, these are some of the differences you’ll see ever - The syntax that’s used to define functions that are passed into methods is the same syntax that’s used to define anonymous functions - Scala variables and parameters are defined with the `val` (immutable) or `var` (mutable) keywords - Scala idioms prefer immutable data structures -- Scala has terrific IDE support with IntelliJ IDEA and Microsoft VS Code - Comments: Python uses `#` for comments; Scala uses the C, C++, and Java style: `//`, `/*...*/`, and `/**...*/` - Naming conventions: The Python standard is to use underscores like `my_list`; Scala uses `myList` - Scala is statically typed, so you declare types for method parameters, method return values, and in other places @@ -94,7 +96,7 @@ Also at a programming level, these are some of the differences you’ll see ever - Scala has state-of-the-art open source functional programming libraries (see the [“Awesome Scala” list](https://github.com/lauris/awesome-scala)) - You can create your own “control structures” and DSLs, thanks to features like objects, by-name parameters, infix notation, optional parentheses, extension methods, higher-order functions, and more - Scala code can run in the JVM and even be compiled to native images (using [Scala Native](https://github.com/scala-native/scala-native) and [GraalVM](https://www.graalvm.org)) for high performance -- Many other goodies: case classes, companion classes and objects, macros, [union][union-types] and [intersection][intersection-types] types, [toplevel definitions][toplevel], numeric literals, multiple parameter lists, and more +- Many other goodies: companion classes and objects, macros, numeric literals, multiple parameter lists, [intersection][intersection-types] types, type-level programming, and more ### Features compared with examples @@ -239,6 +241,53 @@ x += 1 However, the rule of thumb in Scala is to always use `val` unless the variable specifically needs to be mutated. +## FP style records + +Scala case classes are similar to Python frozen dataclasses. + +### Constructor definition: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>from dataclasses import dataclass, replace + <br> + <br>@dataclass(frozen=True) + <br>class Person: + <br>  name: str + <br>  age: int</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>case class Person(name: String, age: Int)</code> + </td> + </tr> + </tbody> +</table> + +### Create and use an instance: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>p = Person("Alice", 42) + <br>p.name   # Alice + <br>p2 = replace(p, age=43)</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val p = Person("Alice", 42) + <br>p.name   // Alice + <br>val p2 = p.copy(age = 43)</code> + </td> + </tr> + </tbody> +</table> + ## OOP style classes and methods This section provides comparisons of features related to OOP-style classes and methods. @@ -332,7 +381,7 @@ This section provides comparisons of features related to OOP-style classes and m ## Interfaces, traits, and inheritance If you’re familiar with Java 8 and newer, Scala traits are similar to those Java interfaces. -Traits are used all the time in Scala, while Python interfaces and abstract classes are used much less often. +Traits are used all the time in Scala, while Python interfaces (Protocols) and abstract classes are used much less often. Therefore, rather than attempt to compare the two, this example shows how to use Scala traits to build a small solution to a simulated math problem: ```scala @@ -1009,7 +1058,7 @@ If you’re used to using these methods with lambda expressions in Python, you To demonstrate this functionality, here are two sample lists: ```scala -numbers = (1,2,3) // python +numbers = [1,2,3] // python val numbers = List(1,2,3) // scala ``` @@ -1109,7 +1158,8 @@ Those lists are used in the following table, that shows how to apply mapping and ### Scala collections methods Scala collections classes have over 100 functional methods to simplify your code. -In addition to `map`, `filter`, and `reduce`, other commonly-used methods are listed below. +In Python, some of these functions are available in the `itertools` module. +In addition to `map`, `filter`, and `reduce`, other commonly-used methods in Scala are listed below. In those method examples: - `c` refers to a collection @@ -1295,12 +1345,10 @@ Follow the links below for more details: - Most concepts related to [contextual abstractions][contextual], such as [extension methods][extension-methods], [type classes][type-classes], implicit values - Scala allows multiple parameter lists, which enables features like partially-applied functions, and the ability to create your own DSLs -- Case classes, which are extremely useful for functional programming and pattern matching - The ability to create your own control structures and DSLs -- Pattern matching and `match` expressions - [Multiversal equality][multiversal]: the ability to control at compile time what equality comparisons make sense - Infix methods -- Macros and metaprogramming +- Macros ## Scala and virtual environments @@ -1313,7 +1361,7 @@ sbt compile automatically resolves all dependencies for that particular project. The location of downloaded dependencies is largely an implementation detail of the build tool, and users do not have to interact with these downloaded dependencies directly. For example, if we delete the whole sbt dependencies cache, on the next compilation of the project, sbt simply resolves and downloads all the required dependencies again, automatically. -This differs from Python, were by default dependencies are installed in system-wide or user-wide directories, so to obtain an isolated environment on a per-project basis one has to create a corresponding virtual environment. For example, using the `venv` module, we might create one for a particular project like so +This differs from Python, where by default dependencies are installed in system-wide or user-wide directories, so to obtain an isolated environment on a per-project basis one has to create a corresponding virtual environment. For example, using the `venv` module, we might create one for a particular project like so ``` cd myapp @@ -1338,6 +1386,6 @@ None of this manual process is necessary in Scala. [modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %} [multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} [toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} -[type-classes]: {% link _overviews/scala3-book/types-type-classes.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} [union-types]: {% link _overviews/scala3-book/types-union.md %} </div> diff --git a/_overviews/scala3-book/scala-tools.md b/_overviews/scala3-book/scala-tools.md index 52e4bc67b7..4469a7283d 100644 --- a/_overviews/scala3-book/scala-tools.md +++ b/_overviews/scala3-book/scala-tools.md @@ -2,7 +2,8 @@ title: Scala Tools type: chapter description: This chapter looks at two commonly-used Scala tools, sbt and ScalaTest. -num: 69 +languages: [ru, zh-cn] +num: 70 previous-page: concurrency next-page: tools-sbt --- diff --git a/_overviews/scala3-book/scala4x.css b/_overviews/scala3-book/scala4x.css index c7d34705bb..1772c03ac8 100644 --- a/_overviews/scala3-book/scala4x.css +++ b/_overviews/scala3-book/scala4x.css @@ -10,12 +10,7 @@ background: #fdfdf7; border-collapse: collapse; } -.content-primary .scala3-comparison-page th, -.content-primary .scala3-comparison-page td -{ - border-top: 1px solid #E5EAEA; - border-bottom: 1px solid #E5EAEA; -} + .content-primary .scala3-comparison-page table td.python-block, .content-primary .scala3-comparison-page table td.java-block, .content-primary .scala3-comparison-page table td.javascript-block, diff --git a/_overviews/scala3-book/string-interpolation.md b/_overviews/scala3-book/string-interpolation.md new file mode 100644 index 0000000000..1ba335e3b7 --- /dev/null +++ b/_overviews/scala3-book/string-interpolation.md @@ -0,0 +1,370 @@ +--- +title: String Interpolation +type: chapter +description: This page provides more information about creating strings and using string interpolation. +languages: [ru, zh-cn] +num: 18 +previous-page: first-look-at-types +next-page: control-structures +redirect_from: + - /overviews/core/string-interpolation.html +--- + +## Introduction + +String interpolation provides a way to use variables inside strings. +For instance: + +{% tabs example-1 %} +{% tab 'Scala 2 and 3' for=example-1 %} +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` +{% endtab %} +{% endtabs %} + +Using string interpolation consists of putting an `s` in front of your string +quotes, and prefixing any variable names with a `$` symbol. + +## String Interpolators + +The `s` that you place before the string is just one possible interpolator that Scala +provides. + +Scala provides three string interpolation methods out of the box: `s`, `f` and `raw`. +Further, a string interpolator is just a special method, so it is possible to define your +own. For instance, some database libraries define a `sql` interpolator that returns a +database query. + +### The `s` Interpolator (`s`-Strings) + +Prepending `s` to any string literal allows the usage of variables directly in the string. You've already seen an example here: + +{% tabs example-2 %} +{% tab 'Scala 2 and 3' for=example-2 %} +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` +{% endtab %} +{% endtabs %} + +Here, the `$name` and `$age` placeholders in the string are replaced by the results of +calling `name.toString` and `age.toString`, respectively. The `s`-String will have +access to all variables that are currently in scope. + +While it may seem obvious, it's important to note here that string interpolation will _not_ happen in normal string literals: + +{% tabs example-3 %} +{% tab 'Scala 2 and 3' for=example-3 %} +```scala +val name = "James" +val age = 30 +println("$name is $age years old") // "$name is $age years old" +``` +{% endtab %} +{% endtabs %} + +String interpolators can also take arbitrary expressions. For example: + +{% tabs example-4 %} +{% tab 'Scala 2 and 3' for=example-4 %} +```scala +println(s"2 + 2 = ${2 + 2}") // "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // "x.abs = 1" +``` +{% endtab %} +{% endtabs %} + +Any arbitrary expression can be embedded in `${}`. + +For some special characters, it is necessary to escape them when embedded within a string. +To represent an actual dollar sign you can double it `$$`, like here: + +{% tabs example-5 %} +{% tab 'Scala 2 and 3' for=example-5 %} +```scala +println(s"New offers starting at $$14.99") // "New offers starting at $14.99" +``` +{% endtab %} +{% endtabs %} + +Double quotes also need to be escaped. This can be done by using triple quotes as shown: + +{% tabs example-6 %} +{% tab 'Scala 2 and 3' for=example-6 %} +```scala +println(s"""{"name":"James"}""") // `{"name":"James"}` +``` +{% endtab %} +{% endtabs %} + +Finally, all multi-line string literals can also be interpolated + +{% tabs example-7 %} +{% tab 'Scala 2 and 3' for=example-7 %} +```scala +println(s"""name: "$name", + |age: $age""".stripMargin) +``` + +This will print as follows: + +``` +name: "James" +age: 30 +``` +{% endtab %} +{% endtabs %} + +### The `f` Interpolator (`f`-Strings) + +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: + +{% tabs example-8 %} +{% tab 'Scala 2 and 3' for=example-8 %} +```scala +val height = 1.9d +val name = "James" +println(f"$name%s is $height%2.2f meters tall") // "James is 1.90 meters tall" +``` +{% endtab %} +{% endtabs %} + +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: + +{% tabs f-interpolator-error class=tabs-scala-version %} + +{% tab 'Scala 2' for=f-interpolator-error %} +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +<console>:9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ +``` +{% endtab %} + +{% tab 'Scala 3' for=f-interpolator-error %} +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +-- Error: ---------------------------------------------------------------------- +1 |f"$height%4d" + | ^^^^^^ + | Found: (height : Double), Required: Int, Long, Byte, Short, BigInt +1 error found + +``` +{% endtab %} +{% endtabs %} + +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][java-format-docs]. If there is no `%` character after a variable +definition a formatter of `%s` (`String`) is assumed. + +Finally, as in Java, use `%%` to get a literal `%` character in the output string: + +{% tabs literal-percent %} +{% tab 'Scala 2 and 3' for=literal-percent %} +```scala +println(f"3/19 is less than 20%%") // "3/19 is less than 20%" +``` +{% endtab %} +{% endtabs %} + +### 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: + +{% tabs example-9 %} +{% tab 'Scala 2 and 3' for=example-9 %} +```scala +scala> s"a\nb" +res0: String = +a +b +``` +{% endtab %} +{% endtabs %} + +Here the `s` string interpolator replaced the characters `\n` with a return character. The `raw` interpolator will not do that. + +{% tabs example-10 %} +{% tab 'Scala 2 and 3' for=example-10 %} +```scala +scala> raw"a\nb" +res1: String = a\nb +``` +{% endtab %} +{% endtabs %} + +The raw interpolator is useful when you want to avoid having expressions like `\n` turn into a return character. + +Furthermore, the raw interpolator allows the usage of variables, which are replaced with their value, just as the s interpolator. + +{% tabs example-11 %} +{% tab 'Scala 2 and 3' for=example-11 %} +```scala +scala> val foo = 42 +scala> raw"a\n$foo" +res1: String = a\n42 +``` +{% endtab %} +{% endtabs %} + +## Advanced Usage + +In addition to the three default string interpolators, users can define their own. + +The literal `s"Hi $name"` is parsed by Scala as a _processed_ string literal. +This means that the compiler does some additional work to this literal. The specifics +of processed strings and string interpolation are described in [SIP-11][sip-11], but +here's a quick example to help illustrate how they work. + +### Custom Interpolators + +In Scala, all processed string literals are simple code transformations. Anytime the compiler encounters a processed string literal of the form: + +{% tabs example-12 %} +{% tab 'Scala 2 and 3' for=example-12 %} +```scala +id"string content" +``` +{% endtab %} +{% endtabs %} + +it transforms it into a method call (`id`) on an instance of [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). +This method can also be available on implicit scope. +To define our own string interpolation, we need to create an implicit class (Scala 2) or an `extension` method (Scala 3) that adds a new method to `StringContext`. + +As a trivial example, let's assume we have a simple `Point` class and want to create a custom interpolator that turns `p"a,b"` into a `Point` object. + +{% tabs custom-interpolator-1 %} +{% tab 'Scala 2 and 3' for=custom-interpolator-1 %} +```scala +case class Point(x: Double, y: Double) + +val pt = p"1,-2" // Point(1.0,-2.0) +``` +{% endtab %} +{% endtabs %} + +We'd create a custom `p`-interpolator by first implementing a `StringContext` extension +with something like: + +{% tabs custom-interpolator-2 class=tabs-scala-version %} + +{% tab 'Scala 2' for=custom-interpolator-2 %} +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Any*): Point = ??? +} +``` + +**Note:** It's important to extend `AnyVal` in Scala 2.x to prevent runtime instantiation on each interpolation. See the [value class]({% link _overviews/core/value-classes.md %}) documentation for more. + +{% endtab %} + +{% tab 'Scala 3' for=custom-interpolator-2 %} +```scala +extension (sc: StringContext) + def p(args: Any*): Point = ??? +``` +{% endtab %} + +{% endtabs %} + +Once this extension is in scope and the Scala compiler encounters `p"some string"`, it +will process `some string` to turn it into String tokens and expression arguments for +each embedded variable in the string. + +For example, `p"1, $someVar"` would turn into: + +{% tabs extension-desugaring class=tabs-scala-version %} + +{% tab 'Scala 2' for=extension-desugaring %} +```scala +new StringContext("1, ", "").p(someVar) +``` + +The implicit class is then used to rewrite it to the following: + +```scala +new PointHelper(new StringContext("1, ", "")).p(someVar) +``` +{% endtab %} + +{% tab 'Scala 3' for=extension-desugaring %} +```scala +StringContext("1, ","").p(someVar) +``` +{% endtab %} + +{% endtabs %} + +As a result, each of the fragments of the processed String are exposed in the +`StringContext.parts` member, while any expressions values in the string are passed in +to the method's `args` parameter. + +### Example Implementation + +A naive implementation of our Point interpolator method might look something like below, +though a more sophisticated method may choose to have more precise control over the +processing of the string `parts` and expression `args` instead of reusing the +`s`-Interpolator. + +{% tabs naive-implementation class=tabs-scala-version %} + +{% tab 'Scala 2' for=naive-implementation %} +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Double*): Point = { + // reuse the `s`-interpolator and then split on ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } +} + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` +{% endtab %} + +{% tab 'Scala 3' for=naive-implementation %} +```scala +extension (sc: StringContext) + def p(args: Double*): Point = { + // reuse the `s`-interpolator and then split on ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` +{% endtab %} +{% endtabs %} + +While string interpolators were originally used to create some form of a String, the use +of custom interpolators as above can allow for powerful syntactic shorthand, and the +community has already made swift use of this syntax for things like ANSI terminal color +expansion, executing SQL queries, magic `$"identifier"` representations, and many others. + +[java-format-docs]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#detail +[value-class]: {% link _overviews/core/value-classes.md %} +[sip-11]: {% link _sips/sips/string-interpolation.md %} diff --git a/_overviews/scala3-book/taste-collections.md b/_overviews/scala3-book/taste-collections.md index fe1232914f..773f823ce4 100644 --- a/_overviews/scala3-book/taste-collections.md +++ b/_overviews/scala3-book/taste-collections.md @@ -2,23 +2,24 @@ title: Collections type: section description: This page provides a high-level overview of the main features of the Scala 3 programming language. +languages: [ru, zh-cn] num: 13 previous-page: taste-objects next-page: taste-contextual-abstractions --- - The Scala library has a rich set of collection classes, and those classes have a rich set of methods. Collections classes are available in both immutable and mutable forms. - - ## Creating lists To give you a taste of how these work, here are some examples that use the `List` class, which is an immutable, linked-list class. These examples show different ways to create a populated `List`: +{% tabs collection_1 %} +{% tab 'Scala 2 and 3' for=collection_1 %} + ```scala val a = List(1, 2, 3) // a: List[Int] = List(1, 2, 3) @@ -30,7 +31,8 @@ val f = List.range(1, 5) // f: List[Int] = List(1, 2, 3, 4) val g = List.range(1, 10, 3) // g: List[Int] = List(1, 4, 7) ``` - +{% endtab %} +{% endtabs %} ## `List` methods @@ -38,6 +40,9 @@ Once you have a populated list, the following examples show some of the methods Notice that these are all functional methods, meaning that they don’t mutate the collection they’re called on, but instead return a new collection with the updated elements. The result that’s returned by each expression is shown in the comment on each line: +{% tabs collection_2 %} +{% tab 'Scala 2 and 3' for=collection_2 %} + ```scala // a sample list val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) @@ -60,8 +65,14 @@ nums.map(_.toUpperCase) // List("ONE", "TWO") nums.flatMap(_.toUpperCase) // List('O', 'N', 'E', 'T', 'W', 'O') ``` +{% endtab %} +{% endtabs %} + These examples show how the “foldLeft” and “reduceLeft” methods are used to sum the values in a sequence of integers: +{% tabs collection_3 %} +{% tab 'Scala 2 and 3' for=collection_3 %} + ```scala val firstTen = (1 to 10).toList // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -69,35 +80,57 @@ firstTen.reduceLeft(_ + _) // 55 firstTen.foldLeft(100)(_ + _) // 155 (100 is a “seed” value) ``` -There are many more methods available to Scala collections classes, and they’re demonstrated in the [Collections chapter][collections], and in the [API Documentation][api]. - +{% endtab %} +{% endtabs %} +There are many more methods available to Scala collections classes, and they’re demonstrated in the [Collections chapter][collections], and in the [API Documentation][api]. ## Tuples The Scala _tuple_ is a type that lets you easily put a collection of different types in the same container. For example, given this `Person` case class: +{% tabs collection_4 %} +{% tab 'Scala 2 and 3' for=collection_4 %} + ```scala case class Person(name: String) ``` +{% endtab %} +{% endtabs %} + This is how you create a tuple that contains an `Int`, a `String`, and a custom `Person` value: +{% tabs collection_5 %} +{% tab 'Scala 2 and 3' for=collection_5 %} + ```scala val t = (11, "eleven", Person("Eleven")) ``` +{% endtab %} +{% endtabs %} + Once you have a tuple, you can access its values by binding them to variables, or access them by number: +{% tabs collection_6 %} +{% tab 'Scala 2 and 3' for=collection_6 %} + ```scala t(0) // 11 t(1) // "eleven" t(2) // Person("Eleven") ``` +{% endtab %} +{% endtabs %} + You can also use this _extractor_ approach to assign the tuple fields to variable names: +{% tabs collection_7 %} +{% tab 'Scala 2 and 3' for=collection_7 %} + ```scala val (num, str, person) = t @@ -107,12 +140,12 @@ val (num, str, person) = t // val person: Person = Person(Eleven) ``` +{% endtab %} +{% endtabs %} + Tuples are nice for those times when you want to put a collection of heterogeneous types in a little collection-like structure. See the [Reference documentation][reference] for more tuple details. - - - [collections]: {% link _overviews/scala3-book/collections-intro.md %} [api]: https://scala-lang.org/api/3.x/ [reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/taste-contextual-abstractions.md b/_overviews/scala3-book/taste-contextual-abstractions.md index 097800b288..60d21d1643 100644 --- a/_overviews/scala3-book/taste-contextual-abstractions.md +++ b/_overviews/scala3-book/taste-contextual-abstractions.md @@ -2,6 +2,7 @@ title: Contextual Abstractions type: section description: This section provides an introduction to Contextual Abstractions in Scala 3. +languages: [ru, zh-cn] num: 14 previous-page: taste-collections next-page: taste-toplevel-definitions @@ -18,12 +19,18 @@ Those parameters are called _Context Parameters_ because they are inferred by th For instance, consider a program that sorts a list of addresses by two criteria: the city name and then street name. +{% tabs contextual_1 %} +{% tab 'Scala 2 and 3' for=contextual_1 %} + ```scala val addresses: List[Address] = ... addresses.sortBy(address => (address.city, address.street)) ``` +{% endtab %} +{% endtabs %} + The `sortBy` method takes a function that returns, for every address, the value to compare it with the other addresses. In this case, we pass a function that returns a pair containing the city name and the street name. @@ -38,10 +45,25 @@ It is convenient to omit it because we know `String`s are generally compared usi However, it is also possible to pass it explicitly: +{% tabs contextual_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=contextual_2 %} + +```scala +addresses.sortBy(address => (address.city, address.street))(Ordering.Tuple2(Ordering.String, Ordering.String)) +``` + +{% endtab %} +{% tab 'Scala 3' for=contextual_2 %} + ```scala addresses.sortBy(address => (address.city, address.street))(using Ordering.Tuple2(Ordering.String, Ordering.String)) ``` +in Scala 3 `using` in an argument list to `sortBy` signals passing the context parameter explicitly, avoiding ambiguity. + +{% endtab %} +{% endtabs %} + In this case, the `Ordering.Tuple2(Ordering.String, Ordering.String)` instance is exactly the one that is otherwise inferred by the compiler. In other words both examples produce the same program. @@ -50,7 +72,5 @@ They help developers write pieces of code that are extensible and concise at the For more details, see the [Contextual Abstractions chapter][contextual] of this book, and also the [Reference documentation][reference]. - - [contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} [reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/taste-control-structures.md b/_overviews/scala3-book/taste-control-structures.md index b55e9433b3..4b58abbf00 100644 --- a/_overviews/scala3-book/taste-control-structures.md +++ b/_overviews/scala3-book/taste-control-structures.md @@ -2,6 +2,7 @@ title: Control Structures type: section description: This section demonstrates Scala 3 control structures. +languages: [ru, zh-cn] num: 8 previous-page: taste-vars-data-types next-page: taste-modeling @@ -18,11 +19,26 @@ Scala has the control structures you find in other programming languages, and al These structures are demonstrated in the following examples. +## `if`/`else` +Scala’s `if`/`else` control structure looks similar to other languages. -## `if`/`else` +{% tabs if-else class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else %} -Scala’s `if`/`else` control structure looks similar to other languages: +```scala +if (x < 0) { + println("negative") +} else if (x == 0) { + println("zero") +} else { + println("positive") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else %} ```scala if x < 0 then @@ -33,52 +49,66 @@ else println("positive") ``` +{% endtab %} +{% endtabs %} + Note that this really is an _expression_---not a _statement_. This means that it returns a value, so you can assign the result to a variable: +{% tabs if-else-expression class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else-expression %} + +```scala +val x = if (a < b) { a } else { b } +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else-expression %} + ```scala val x = if a < b then a else b ``` +{% endtab %} +{% endtabs %} + As you’ll see throughout this book, _all_ Scala control structures can be used as expressions. > An expression returns a result, while a statement does not. > Statements are typically used for their side-effects, such as using `println` to print to the console. -The `if`/`else` control struture in Scala 2 was constructed differently, with parentheses required and curly brackets instead of the keyword `then`. - -```scala -// Scala 2 syntax -if (test1) { - doX() -} else if (test2) { - doY() -} else { - doZ() -} -``` - ## `for` loops and expressions The `for` keyword is used to create a `for` loop. This example shows how to print every element in a `List`: +{% tabs for-loop class=tabs-scala-version %} +{% tab 'Scala 2' for=for-loop %} + ```scala val ints = List(1, 2, 3, 4, 5) -for i <- ints do println(i) +for (i <- ints) println(i) ``` -The code `i <- ints` is referred to as a _generator_, and the code that follows the `do` keyword is the _body_ of the loop. +> The code `i <- ints` is referred to as a _generator_. In any generator `p <- e`, the expression `e` can generate zero or many bindings to the pattern `p`. +> The code that follows the closing parentheses of the generator is the _body_ of the loop. -The old syntax in Scala 2 for this control structure was: +{% endtab %} + +{% tab 'Scala 3' for=for-loop %} ```scala -// Scala 2 syntax -for (i <- ints) println(i) +val ints = List(1, 2, 3, 4, 5) + +for i <- ints do println(i) ``` -Again, note the usage of parentheses and the new `for`-`do` in Scala 3. +> The code `i <- ints` is referred to as a _generator_, and the code that follows the `do` keyword is the _body_ of the loop. + +{% endtab %} +{% endtabs %} ### Guards @@ -86,6 +116,18 @@ You can also use one or more `if` expressions inside a `for` loop. These are referred to as _guards_. This example prints all of the numbers in `ints` that are greater than `2`: +{% tabs for-guards class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards %} + +```scala +for (i <- ints if i > 2) + println(i) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards %} + ```scala for i <- ints @@ -94,10 +136,31 @@ do println(i) ``` +{% endtab %} +{% endtabs %} + You can use multiple generators and guards. This loop iterates over the numbers `1` to `3`, and for each number it also iterates over the characters `a` to `c`. However, it also has two guards, so the only time the print statement is called is when `i` has the value `2` and `j` is the character `b`: +{% tabs for-guards-multi class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards-multi %} + +```scala +for { + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +} { + println(s"i = $i, j = $j") // prints: "i = 2, j = b" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards-multi %} + ```scala for i <- 1 to 3 @@ -108,6 +171,8 @@ do println(s"i = $i, j = $j") // prints: "i = 2, j = b" ``` +{% endtab %} +{% endtabs %} ### `for` expressions @@ -116,13 +181,41 @@ The `for` keyword has even more power: When you use the `yield` keyword instead A few examples demonstrate this. Using the same `ints` list as the previous example, this code creates a new list, where the value of each element in the new list is twice the value of the elements in the original list: +{% tabs for-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expression_1 %} + +```` +scala> val doubles = for (i <- ints) yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +{% endtab %} + +{% tab 'Scala 3' for=for-expression_1 %} + ```` scala> val doubles = for i <- ints yield i * 2 val doubles: List[Int] = List(2, 4, 6, 8, 10) ```` +{% endtab %} +{% endtabs %} + Scala’s control structure syntax is flexible, and that `for` expression can be written in several other ways, depending on your preference: +{% tabs for-expressioni_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_2 %} + +```scala +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_2 %} + ```scala val doubles = for i <- ints yield i * 2 // style shown above val doubles = for (i <- ints) yield i * 2 @@ -130,15 +223,49 @@ val doubles = for (i <- ints) yield (i * 2) val doubles = for { i <- ints } yield (i * 2) ``` +{% endtab %} +{% endtabs %} + This example shows how to capitalize the first character in each string in the list: +{% tabs for-expressioni_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_3 %} + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for (name <- names) yield name.capitalize +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_3 %} + ```scala val names = List("chris", "ed", "maurice") val capNames = for name <- names yield name.capitalize ``` +{% endtab %} +{% endtabs %} + Finally, this `for` expression iterates over a list of strings, and returns the length of each string, but only if that length is greater than `4`: +{% tabs for-expressioni_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_4 %} + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = + for (f <- fruits if f.length > 4) yield f.length + +// fruitLengths: List[Int] = List(5, 6, 6) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_4 %} + ```scala val fruits = List("apple", "banana", "lime", "orange") @@ -153,14 +280,33 @@ yield // fruitLengths: List[Int] = List(5, 6, 6) ``` -`for` loops and expressions are covered in more detail in the [Control Structures sections][control] of this book, and in the [Reference documentation]({{ site.scala3ref }}/other-new-features/control-syntax.html). - +{% endtab %} +{% endtabs %} +`for` loops and expressions are covered in more detail in the [Control Structures sections][control] of this book, and in the [Reference documentation]({{ site.scala3ref }}/other-new-features/control-syntax.html). ## `match` expressions Scala has a `match` expression, which in its most basic use is like a Java `switch` statement: +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' for=match %} + +```scala +val i = 1 + +// later in the code ... +i match { + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match %} + ```scala val i = 1 @@ -171,8 +317,26 @@ i match case _ => println("other") ``` +{% endtab %} +{% endtabs %} + However, `match` really is an expression, meaning that it returns a result based on the pattern match, which you can bind to a variable: +{% tabs match-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_1 %} + +```scala +val result = i match { + case 1 => "one" + case 2 => "two" + case _ => "other" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_1 %} + ```scala val result = i match case 1 => "one" @@ -180,8 +344,33 @@ val result = i match case _ => "other" ``` +{% endtab %} +{% endtabs %} + `match` isn’t limited to working with just integer values, it can be used with any data type: +{% tabs match-expression_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_2 %} + +```scala +val p = Person("Fred") + +// later in the code +p match { + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_2 %} + ```scala val p = Person("Fred") @@ -195,17 +384,46 @@ p match case _ => println("Watch the Flintstones!") ``` + +{% endtab %} +{% endtabs %} + In fact, a `match` expression can be used to test a variable against many different types of patterns. This example shows (a) how to use a `match` expression as the body of a method, and (b) how to match all the different types shown: +{% tabs match-expression_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_3 %} + ```scala // getClassAsString is a method that takes a single argument of any type. -def getClassAsString(x: Matchable): String = x match +def getClassAsString(x: Any): String = x match { case s: String => s"'$s' is a String" case i: Int => "Int" case d: Double => "Double" case l: List[_] => "List" case _ => "Unknown" +} + +// examples +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +Because the method `getClassAsString` takes a parameter value of type `Any`, it can be decomposed by any kind of +pattern. + +{% endtab %} +{% tab 'Scala 3' for=match-expression_3 %} + +```scala +// getClassAsString is a method that takes a single argument of any type. +def getClassAsString(x: Matchable): String = x match + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[?] => "List" + case _ => "Unknown" // examples getClassAsString(1) // Int @@ -217,17 +435,36 @@ The method `getClassAsString` takes as a parameter a value of type [Matchable]({ any type supporting pattern matching (some types don’t support pattern matching because this could break encapsulation). +{% endtab %} +{% endtabs %} + There’s _much_ more to pattern matching in Scala. Patterns can be nested, results of patterns can be bound, and pattern matching can even be user-defined. See the pattern matching examples in the [Control Structures chapter][control] for more details. - - ## `try`/`catch`/`finally` Scala’s `try`/`catch`/`finally` control structure lets you catch exceptions. It’s similar to Java, but its syntax is consistent with `match` expressions: +{% tabs try class=tabs-scala-version %} +{% tab 'Scala 2' for=try %} + +```scala +try { + writeTextToFile(text) +} catch { + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +} finally { + println("Clean up your resources here.") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=try %} + ```scala try writeTextToFile(text) @@ -238,28 +475,51 @@ finally println("Clean up your resources here.") ``` - +{% endtab %} +{% endtabs %} ## `while` loops Scala also has a `while` loop construct. -It’s one-line syntax looks like this: +Its one-line syntax looks like this: + +{% tabs while_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_1 %} ```scala -while x >= 0 do x = f(x) +while (x >= 0) { x = f(x) } ``` -In Scala 2, the syntax was a bit different. The condition was surrounded by parentheses, and -there was no `do` keyword: +{% endtab %} + +{% tab 'Scala 3' for=while_1 %} ```scala -while (x >= 0) { x = f(x) } +while x >= 0 do x = f(x) ``` - Scala 3 still supports the Scala 2 syntax for the sake of compatibility. +{% endtab %} +{% endtabs %} + The `while` loop multiline syntax looks like this: +{% tabs while_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_2 %} + +```scala +var x = 1 + +while (x < 3) { + println(x) + x += 1 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=while_2 %} + ```scala var x = 1 @@ -270,13 +530,12 @@ do x += 1 ``` - +{% endtab %} +{% endtabs %} ## Custom control structures Thanks to features like by-name parameters, infix notation, fluent interfaces, optional parentheses, extension methods, and higher-order functions, you can also create your own code that works just like a control structure. You’ll learn more about this in the [Control Structures][control] section. - - [control]: {% link _overviews/scala3-book/control-structures.md %} diff --git a/_overviews/scala3-book/taste-functions.md b/_overviews/scala3-book/taste-functions.md index b103dba666..e73024bca0 100644 --- a/_overviews/scala3-book/taste-functions.md +++ b/_overviews/scala3-book/taste-functions.md @@ -2,13 +2,13 @@ title: First-Class Functions type: section description: This page provides an introduction to functions in Scala 3. +languages: [ru, zh-cn] num: 11 previous-page: taste-methods next-page: taste-objects --- - Scala has most features you’d expect in a functional programming language, including: - Lambdas (anonymous functions) @@ -21,26 +21,34 @@ The `map` method of the `List` class is a typical example of a higher-order func These two examples are equivalent, and show how to multiply each number in a list by `2` by passing a lambda into the `map` method: + +{% tabs function_1 %} +{% tab 'Scala 2 and 3' for=function_1 %} ```scala val a = List(1, 2, 3).map(i => i * 2) // List(2,4,6) val b = List(1, 2, 3).map(_ * 2) // List(2,4,6) ``` +{% endtab %} +{% endtabs %} Those examples are also equivalent to the following code, which uses a `double` method instead of a lambda: + +{% tabs function_2 %} +{% tab 'Scala 2 and 3' for=function_2 %} ```scala def double(i: Int): Int = i * 2 val a = List(1, 2, 3).map(i => double(i)) // List(2,4,6) val b = List(1, 2, 3).map(double) // List(2,4,6) ``` +{% endtab %} +{% endtabs %} > If you haven’t seen the `map` method before, it applies a given function to every element in a list, yielding a new list that contains the resulting values. Passing lambdas to higher-order functions on collections classes (like `List`) is a part of the Scala experience, something you’ll do every day. - - ## Immutable collections When you work with immutable collections like `List`, `Vector`, and the immutable `Map` and `Set` classes, it’s important to know that these functions don’t mutate the collection they’re called on; instead, they return a new collection with the updated data. @@ -48,6 +56,9 @@ As a result, it’s also common to chain them together in a “fluent” style t For instance, this example shows how to filter a collection twice, and then multiply each element in the remaining collection: + +{% tabs function_3 %} +{% tab 'Scala 2 and 3' for=function_3 %} ```scala // a sample list val nums = (1 to 10).toList // List(1,2,3,4,5,6,7,8,9,10) @@ -59,9 +70,9 @@ val x = nums.filter(_ > 3) // result: x == List(40, 50, 60) ``` +{% endtab %} +{% endtabs %} In addition to higher-order functions being used throughout the standard library, you can also [create your own][higher-order]. - - [higher-order]: {% link _overviews/scala3-book/fun-hofs.md %} diff --git a/_overviews/scala3-book/taste-hello-world.md b/_overviews/scala3-book/taste-hello-world.md index 110d55ba91..52fc532e5e 100644 --- a/_overviews/scala3-book/taste-hello-world.md +++ b/_overviews/scala3-book/taste-hello-world.md @@ -2,54 +2,164 @@ title: Hello, World! type: section description: This section demonstrates a Scala 3 'Hello, World!' example. +languages: [ru, zh-cn] num: 5 previous-page: taste-intro next-page: taste-repl --- +> **Hint**: in the following examples try picking your preferred Scala version. -A Scala 3 “Hello, world!” example goes as follows. -First, put this code in a file named _Hello.scala_: +## Your First Scala Program + +A Scala “Hello, World!” example goes as follows. +First, put this code in a file named _hello.scala_: + + +<!-- Display Hello World for each Scala Version --> +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-demo %} +```scala +object hello { + def main(args: Array[String]) = { + println("Hello, World!") + } +} +``` +> In this code, we defined a method named `main`, inside a Scala `object` named `hello`. +> An `object` in Scala is similar to a `class`, but defines a singleton instance that you can pass around. +> `main` takes an input parameter named `args` that must be typed as `Array[String]`, (ignore `args` for now). + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} ```scala -@main def hello() = println("Hello, world!") +@main def hello() = println("Hello, World!") ``` +> In this code, `hello` is a method. +> It’s defined with `def`, and declared to be a “main” method with the `@main` annotation. +> It prints the `"Hello, World!"` string to standard output (STDOUT) using the `println` method. + +{% endtab %} -In this code, `hello` is a method. -It’s defined with `def`, and declared to be a “main” method with the `@main` annotation. -It prints the `"Hello, world!"` string to standard output (STDOUT) using the `println` method. +{% endtabs %} +<!-- End tabs --> -Next, compile the code with `scalac`: +Next, compile and run the code with `scala`: ```bash -$ scalac Hello.scala +$ scala run hello.scala ``` -If you’re coming to Scala from Java, `scalac` is just like `javac`, so that command creates several files: +The command should produce an output similar to: +``` +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Hello, World! +``` -```bash -$ ls -1 -Hello$package$.class -Hello$package.class -Hello$package.tasty -Hello.scala -hello.class -hello.tasty +Assuming that worked, congratulations, you just compiled and ran your first Scala application. + +> More information about sbt and other tools that make Scala development easier can be found in the [Scala Tools][scala_tools] chapter. +> The Scala CLI documentation can be found [here](https://scala-cli.virtuslab.org/). + +## Ask For User Input + +In our next example let's ask for the user's name before we greet them! + +There are several ways to read input from a command-line, but a simple way is to use the +`readLine` method in the _scala.io.StdIn_ object. To use it, you need to first import it, like this: + +{% tabs import-readline %} +{% tab 'Scala 2 and 3' for=import-readline %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +To demonstrate how this works, let’s create a little example. Put this source code in a file named _helloInteractive.scala_: + +<!-- Display interactive Hello World application for each Scala Version --> +{% tabs hello-world-interactive class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +object helloInteractive { + + def main(args: Array[String]) = { + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") + } + +} ``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +@main def helloInteractive() = + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") +``` +{% endtab %} + +{% endtabs %} +<!-- End tabs --> -Like Java, the _.class_ files are bytecode files, and they’re ready to run in the JVM. +In this code we save the result of `readLine` to a variable called `name`, we then +use the `+` operator on strings to join `"Hello, "` with `name` and `"!"`, making one single string value. -Now you can run the `hello` method with the `scala` command: +> You can learn more about using `val` by reading [Variables and Data Types](/scala3/book/taste-vars-data-types.html). + +Then run the code with `scala`. This time the program will pause after asking for your name, +and wait until you type a name and press return on the keyboard, looking like this: ```bash -$ scala hello -Hello, world! +$ scala run helloInteractive.scala +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Please enter your name: +▌ ``` -Assuming that worked, congratulations, you just compiled and ran your first Scala application. +When you enter your name at the prompt, the final interaction should look like this: -> More information about sbt and other tools that make Scala development easier can be found in the [Scala Tools][scala_tools] chapter. +```bash +$ scala run helloInteractive.scala +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Please enter your name: +Alvin Alexander +Hello, Alvin Alexander! +``` -[scala_tools]: {% link _overviews/scala3-book/scala-tools.md %} +### A Note about Imports +As you saw in this application, sometimes certain methods, or other kinds of definitions that we'll see later, +are not available unless you use an `import` clause like so: +{% tabs import-readline-2 %} +{% tab 'Scala 2 and 3' for=import-readline-2 %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +Imports help you write code in a few ways: + - you can put code in multiple files, to help avoid clutter, and to help navigate large projects. + - you can use a code library, perhaps written by someone else, that has useful functionality + - you can know where a certain definition comes from (especially if it was not written in the current file). + +[scala_tools]: {% link _overviews/scala3-book/scala-tools.md %} diff --git a/_overviews/scala3-book/taste-intro.md b/_overviews/scala3-book/taste-intro.md index 044bf7fa93..9d93b317cf 100644 --- a/_overviews/scala3-book/taste-intro.md +++ b/_overviews/scala3-book/taste-intro.md @@ -2,6 +2,7 @@ title: A Taste of Scala type: chapter description: This chapter provides a high-level overview of the main features of the Scala 3 programming language. +languages: [ru, zh-cn] num: 4 previous-page: why-scala-3 next-page: taste-hello-world @@ -11,7 +12,51 @@ next-page: taste-hello-world This chapter provides a whirlwind tour of the main features of the Scala 3 programming language. After this initial tour, the rest of the book provides more details on these features, and the [Reference documentation][reference] provides _many_ more details. -> Throughout this book, we recommend you to experiment with the examples on [Scastie](https://scastie.scala-lang.org), or in the Scala REPL, which is demonstrated shortly. +## Setting Up Scala +Throughout this chapter, and the rest of the book, we encourage you to try out the examples by either copying +them or typing them out manually. The tools necessary to follow along with the examples on your own computer +can be installed by following our [getting started guide][get-started]. + +> Alternatively you can run the examples in a web browser with [Scastie](https://scastie.scala-lang.org), a +> fully online editor and code-runner for Scala. + +## Comments + +One good thing to know up front is that comments in Scala are just like comments in Java (and many other languages): + +{% tabs comments %} +{% tab 'Scala 2 and 3' for=comments %} +```scala +// a single line comment + +/* + * a multiline comment + */ + +/** + * also a multiline comment + */ +``` +{% endtab %} +{% endtabs %} + +## IDEs + +The two main IDEs (integrated development environments) for Scala are: + +- [IntelliJ IDEA](/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.html) +- [Visual Studio Code](https://scalameta.org/metals/docs/editors/vscode/) + +## Naming conventions + +Another good thing to know is that Scala naming conventions follow the same “camel case” style as Java: + +- Class names: `Person`, `StoreEmployee` +- Variable names: `name`, `firstName` +- Method names: `convertToInt`, `toUpper` + +More on conventions used while writing Scala code can be found in the [Style Guide](/style/index.html). [reference]: {{ site.scala3ref }}/overview.html +[get-started]: {% link _overviews/getting-started/install-scala.md %} diff --git a/_overviews/scala3-book/taste-methods.md b/_overviews/scala3-book/taste-methods.md index 2718059bb1..6c54818805 100644 --- a/_overviews/scala3-book/taste-methods.md +++ b/_overviews/scala3-book/taste-methods.md @@ -2,6 +2,7 @@ title: Methods type: section description: This section provides an introduction to defining and using methods in Scala 3. +languages: [ru, zh-cn] num: 10 previous-page: taste-modeling next-page: taste-functions @@ -13,99 +14,120 @@ next-page: taste-functions Scala classes, case classes, traits, enums, and objects can all contain methods. The syntax of a simple method looks like this: +{% tabs method_1 %} +{% tab 'Scala 2 and 3' for=method_1 %} ```scala def methodName(param1: Type1, param2: Type2): ReturnType = // the method body // goes here ``` +{% endtab %} +{% endtabs %} Here are a few examples: +{% tabs method_2 %} +{% tab 'Scala 2 and 3' for=method_2 %} ```scala def sum(a: Int, b: Int): Int = a + b def concatenate(s1: String, s2: String): String = s1 + s2 ``` +{% endtab %} +{% endtabs %} You don’t have to declare a method’s return type, so you can write those methods like this, if you prefer: +{% tabs method_3 %} +{% tab 'Scala 2 and 3' for=method_3 %} ```scala def sum(a: Int, b: Int) = a + b def concatenate(s1: String, s2: String) = s1 + s2 ``` +{% endtab %} +{% endtabs %} This is how you call those methods: +{% tabs method_4 %} +{% tab 'Scala 2 and 3' for=method_4 %} ```scala val x = sum(1, 2) val y = concatenate("foo", "bar") ``` +{% endtab %} +{% endtabs %} Here’s an example of a multiline method: +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` +{% endtab %} + +{% tab 'Scala 3' for=method_5 %} ```scala def getStackTraceAsString(t: Throwable): String = val sw = new StringWriter t.printStackTrace(new PrintWriter(sw)) sw.toString ``` +{% endtab %} +{% endtabs %} Method parameters can also have default values. In this example, the `timeout` parameter has a default value of `5000`: +{% tabs method_6 %} +{% tab 'Scala 2 and 3' for=method_6 %} ```scala def makeConnection(url: String, timeout: Int = 5000): Unit = println(s"url=$url, timeout=$timeout") ``` +{% endtab %} +{% endtabs %} Because a default `timeout` value is supplied in the method declaration, the method can be called in these two ways: +{% tabs method_7 %} +{% tab 'Scala 2 and 3' for=method_7 %} ```scala makeConnection("https://localhost") // url=http://localhost, timeout=5000 makeConnection("https://localhost", 2500) // url=http://localhost, timeout=2500 ``` +{% endtab %} +{% endtabs %} Scala also supports the use of _named parameters_ when calling a method, so you can also call that method like this, if you prefer: +{% tabs method_8 %} +{% tab 'Scala 2 and 3' for=method_8 %} ```scala makeConnection( url = "https://localhost", timeout = 2500 ) ``` +{% endtab %} +{% endtabs %} Named parameters are particularly useful when multiple method parameters have the same type. At a glance, with this method you may wonder which parameters are set to `true` or `false`: -```scala -engage(true, true, true, false) -``` - -Without help from an IDE that code can be hard to read, but this code is much more obvious: +{% tabs method_9 %} +{% tab 'Scala 2 and 3' for=method_9 %} ```scala -engage( - speedIsSet = true, - directionIsSet = true, - picardSaidMakeItSo = true, - turnedOffParkingBrake = false -) +engage(true, true, true, false) ``` - - -## Extension methods - -_Extension methods_ let you add new methods to closed classes. -For instance, if you want to add two methods named `hello` and `aloha` to the `String` class, declare them as extension methods: - -```scala -extension (s: String) - def hello: String = s"Hello, ${s.capitalize}!" - def aloha: String = s"Aloha, ${s.capitalize}!" - -"world".hello // "Hello, World!" -"friend".aloha // "Aloha, Friend!" -``` +{% endtab %} +{% endtabs %} The `extension` keyword declares that you’re about to define one or more extension methods on the parameter that’s put in parentheses. As shown with this example, the parameter `s` of type `String` can then be used in the body of your extension methods. @@ -114,6 +136,9 @@ This next example shows how to add a `makeInt` method to the `String` class. Here, `makeInt` takes a parameter named `radix`. The code doesn’t account for possible string-to-integer conversion errors, but skipping that detail, the examples show how it works: +{% tabs extension %} +{% tab 'Scala 3 Only' %} + ```scala extension (s: String) def makeInt(radix: Int): Int = Integer.parseInt(s, radix) @@ -123,13 +148,12 @@ extension (s: String) "100".makeInt(2) // Int = 4 ``` - +{% endtab %} +{% endtabs %} ## See also Scala Methods can be much more powerful: they can take type parameters and context parameters. They are covered in detail in the [Domain Modeling][data-1] section. - - [data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_overviews/scala3-book/taste-modeling.md b/_overviews/scala3-book/taste-modeling.md index 20c605c6f9..3e391d745a 100644 --- a/_overviews/scala3-book/taste-modeling.md +++ b/_overviews/scala3-book/taste-modeling.md @@ -2,6 +2,7 @@ title: Domain Modeling type: section description: This section provides an introduction to data modeling in Scala 3. +languages: [ru, zh-cn] num: 9 previous-page: taste-control-structures next-page: taste-methods @@ -12,12 +13,9 @@ next-page: taste-methods NOTE: I kept the OOP section first, assuming that most readers will be coming from an OOP background. {% endcomment %} - Scala supports both functional programming (FP) and object-oriented programming (OOP), as well as a fusion of the two paradigms. This section provides a quick overview of data modeling in OOP and FP. - - ## OOP Domain Modeling When writing code in an OOP style, your two main tools for data encapsulation are _traits_ and _classes_. @@ -37,6 +35,29 @@ Later, when you want to create concrete implementations of attributes and behavi As an example of how to use traits as interfaces, here are three traits that define well-organized and modular behaviors for animals like dogs and cats: +{% tabs traits class=tabs-scala-version %} +{% tab 'Scala 2' for=traits %} + +```scala +trait Speaker { + def speak(): String // has no body, so it’s abstract +} + +trait TailWagger { + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") +} + +trait Runner { + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits %} + ```scala trait Speaker: def speak(): String // has no body, so it’s abstract @@ -50,17 +71,51 @@ trait Runner: def stopRunning(): Unit = println("Stopped running") ``` +{% endtab %} +{% endtabs %} + Given those traits, here’s a `Dog` class that extends all of those traits while providing a behavior for the abstract `speak` method: +{% tabs traits-class class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-class %} + +```scala +class Dog(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Woof!" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-class %} + ```scala class Dog(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Woof!" ``` +{% endtab %} +{% endtabs %} + Notice how the class extends the traits with the `extends` keyword. Similarly, here’s a `Cat` class that implements those same traits while also overriding two of the concrete methods it inherits: +{% tabs traits-override class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-override %} + +```scala +class Cat(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-override %} + ```scala class Cat(name: String) extends Speaker, TailWagger, Runner: def speak(): String = "Meow" @@ -68,8 +123,28 @@ class Cat(name: String) extends Speaker, TailWagger, Runner: override def stopRunning(): Unit = println("No need to stop") ``` +{% endtab %} +{% endtabs %} + These examples show how those classes are used: +{% tabs traits-use class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-use %} + +```scala +val d = new Dog("Rover") +println(d.speak()) // prints "Woof!" + +val c = new Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-use %} + ```scala val d = Dog("Rover") println(d.speak()) // prints "Woof!" @@ -80,14 +155,34 @@ c.startRunning() // "Yeah ... I don’t run" c.stopRunning() // "No need to stop" ``` +{% endtab %} +{% endtabs %} + If that code makes sense---great, you’re comfortable with traits as interfaces. If not, don’t worry, they’re explained in more detail in the [Domain Modeling][data-1] chapter. - ### Classes Scala _classes_ are used in OOP-style programming. -Here’s an example of a class that models a “person.” In OOP fields are typically mutable, so `firstName` and `lastName` are both declared as `var` parameters: +Here’s an example of a class that models a “person.” In OOP, fields are typically mutable, so `firstName` and `lastName` are both declared as `var` parameters: + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_1 %} + +```scala +class Person(var firstName: String, var lastName: String) { + def printFullName() = println(s"$firstName $lastName") +} + +val p = new Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_1 %} ```scala class Person(var firstName: String, var lastName: String): @@ -99,15 +194,32 @@ p.lastName = "Legend" p.printFullName() // "John Legend" ``` +{% endtab %} +{% endtabs %} + Notice that the class declaration creates a constructor: +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_2 %} + +```scala +// this code uses that constructor +val p = new Person("John", "Stephens") +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_2 %} + ```scala // this code uses that constructor val p = Person("John", "Stephens") ``` -Constructors and other class-related topics are covered in the [Domain Modeling][data-1] chapter. +{% endtab %} +{% endtabs %} +Constructors and other class-related topics are covered in the [Domain Modeling][data-1] chapter. ## FP Domain Modeling @@ -117,23 +229,59 @@ I didn’t include that because I didn’t know if enums are intended to replace the Scala2 “sealed trait + case class” pattern. How to resolve? {% endcomment %} -When writing code in an FP style, you’ll use these constructs: +When writing code in an FP style, you’ll use these concepts: -- Enums to define ADTs -- Case classes -- Traits +- Algebraic Data Types to define the data +- Traits for functionality on the data. +### Enumerations and Sum Types -### Enums +Sum types are one way to model algebraic data types (ADTs) in Scala. + +They are used when data can be represented with different choices. -The `enum` construct is a great way to model algebraic data types (ADTs) in Scala 3. For instance, a pizza has three main attributes: - Crust size - Crust type - Toppings -These are concisely modeled with enums: +These are concisely modeled with enumerations, which are sum types that only contain singleton values: + +{% tabs enum_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_1 %} + +In Scala 2 `sealed` classes and `case object` are combined to define an enumeration: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_1 %} + +Scala 3 offers the `enum` construct for defining enumerations: ```scala enum CrustSize: @@ -146,7 +294,31 @@ enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` -Once you have an enum you can use it in all of the ways you normally use a trait, class, or object: +{% endtab %} +{% endtabs %} + +Once you have an enumeration you can import its members as ordinary values: + +{% tabs enum_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_2 %} + +```scala +import CrustSize._ +val currentCrustSize = Small + +// enums in a `match` expression +currentCrustSize match { + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") +} + +// enums in an `if` statement +if (currentCrustSize == Small) println("Small crust size") +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_2 %} ```scala import CrustSize.* @@ -162,7 +334,26 @@ currentCrustSize match if currentCrustSize == Small then println("Small crust size") ``` -Here’s another example of how to create and use an ADT with Scala: +{% endtab %} +{% endtabs %} + +Here’s another example of how to create a sum type with Scala, this would not be called an enumeration because the `Succ` case has parameters: + +{% tabs enum_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_3 %} + +```scala +sealed abstract class Nat +object Nat { + case object Zero extends Nat + case class Succ(pred: Nat) extends Nat +} +``` + +Sum Types are covered in detail in the [Domain Modeling]({% link _overviews/scala3-book/domain-modeling-tools.md %}) section of this book. + +{% endtab %} +{% tab 'Scala 3' for=enum_3 %} ```scala enum Nat: @@ -170,12 +361,15 @@ enum Nat: case Succ(pred: Nat) ``` -Enums are covered in detail in the [Domain Modeling][data-1] section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). +Enums are covered in detail in the [Domain Modeling]({% link _overviews/scala3-book/domain-modeling-tools.md %}) section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). +{% endtab %} +{% endtabs %} -### Case classes +### Product Types + +A product type is an algebraic data type (ADT) that only has one shape, for example a singleton object, represented in Scala by a `case` object; or an immutable structure with accessible fields, represented by a `case` class. -The Scala `case` class lets you model concepts with immutable data structures. A `case` class has all of the functionality of a `class`, and also has additional features baked in that make them useful for functional programming. When the compiler sees the `case` keyword in front of a `class` it has these effects and benefits: @@ -186,7 +380,6 @@ When the compiler sees the `case` keyword in front of a `class` it has these eff - `equals` and `hashCode` methods are generated to implement structural equality. - A default `toString` method is generated, which is helpful for debugging. - {% comment %} NOTE: Julien had a comment about how he decides when to use case classes vs classes. Add something here? {% endcomment %} @@ -195,6 +388,9 @@ You _can_ manually add all of those methods to a class yourself, but since those This code demonstrates several `case` class features: +{% tabs case-class %} +{% tab 'Scala 2 and 3' for=case-class %} + ```scala // define a case class case class Person( @@ -218,8 +414,9 @@ val p2 = p.copy(name = "Elton John") p2 // : Person = Person(Elton John,Singer) ``` -See the [Domain Modeling][data-1] sections for many more details on `case` classes. - +{% endtab %} +{% endtabs %} +See the [Domain Modeling][data-1] sections for many more details on `case` classes. [data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_overviews/scala3-book/taste-objects.md b/_overviews/scala3-book/taste-objects.md index 4fbb325eb2..479182bfa2 100644 --- a/_overviews/scala3-book/taste-objects.md +++ b/_overviews/scala3-book/taste-objects.md @@ -2,6 +2,7 @@ title: Singleton Objects type: section description: This section provides an introduction to the use of singleton objects in Scala 3. +languages: [ru, zh-cn] num: 12 previous-page: taste-functions next-page: taste-collections @@ -18,27 +19,43 @@ Objects have several uses: In this situation, that class is also called a _companion class_. - They’re used to implement traits to create _modules_. - - ## “Utility” methods Because an `object` is a Singleton, its methods can be accessed like `static` methods in a Java class. For example, this `StringUtils` object contains a small collection of string-related methods: + +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_1 %} +```scala +object StringUtils { + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +} +``` +{% endtab %} + +{% tab 'Scala 3' for=object_1 %} ```scala object StringUtils: def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty def leftTrim(s: String): String = s.replaceAll("^\\s+", "") def rightTrim(s: String): String = s.replaceAll("\\s+$", "") ``` +{% endtab %} +{% endtabs %} Because `StringUtils` is a singleton, its methods can be called directly on the object: +{% tabs object_2 %} +{% tab 'Scala 2 and 3' for=object_2 %} ```scala val x = StringUtils.isNullOrEmpty("") // true val x = StringUtils.isNullOrEmpty("a") // false ``` - +{% endtab %} +{% endtabs %} ## Companion objects @@ -47,6 +64,27 @@ Use a companion object for methods and values which aren’t specific to instanc This example demonstrates how the `area` method in the companion class can access the private `calculateArea` method in its companion object: +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_3 %} +```scala +import scala.math._ + +class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` +{% endtab %} + +{% tab 'Scala 3' for=object_3 %} ```scala import scala.math.* @@ -61,13 +99,36 @@ object Circle: val circle1 = Circle(5.0) circle1.area // Double = 78.53981633974483 ``` - +{% endtab %} +{% endtabs %} ## Creating modules from traits Objects can also be used to implement traits to create modules. This technique takes two traits and combines them to create a concrete `object`: +{% tabs object_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_4 %} +```scala +trait AddService { + def add(a: Int, b: Int) = a + b +} + +trait MultiplyService { + def multiply(a: Int, b: Int) = a * b +} + +// implement those traits as a concrete object +object MathService extends AddService with MultiplyService + +// use the object +import MathService._ +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` +{% endtab %} + +{% tab 'Scala 3' for=object_4 %} ```scala trait AddService: def add(a: Int, b: Int) = a + b @@ -83,6 +144,8 @@ import MathService.* println(add(1,1)) // 2 println(multiply(2,2)) // 4 ``` +{% endtab %} +{% endtabs %} {% comment %} NOTE: I don’t know if this is worth keeping, but I’m leaving it here as a comment for now. @@ -90,5 +153,3 @@ NOTE: I don’t know if this is worth keeping, but I’m leaving it here as a co > You may read that objects are used to _reify_ traits into modules. > _Reify_ means, “to take an abstract concept and turn it into something concrete.” This is what happens in these examples, but “implement” is a more familiar word for most people than “reify.” {% endcomment %} - - diff --git a/_overviews/scala3-book/taste-repl.md b/_overviews/scala3-book/taste-repl.md index 50820b473f..784eaca131 100644 --- a/_overviews/scala3-book/taste-repl.md +++ b/_overviews/scala3-book/taste-repl.md @@ -2,6 +2,7 @@ title: The REPL type: section description: This section provides an introduction to the Scala REPL. +languages: [ru, zh-cn] num: 6 previous-page: taste-hello-world next-page: taste-vars-data-types @@ -11,18 +12,36 @@ next-page: taste-vars-data-types The Scala REPL (“Read-Evaluate-Print-Loop”) is a command-line interpreter that you use as a “playground” area to test your Scala code. You start a REPL session by running the `scala` or `scala3` command depending on your installation at your operating system command line, where you’ll see a “welcome” prompt like this: + +{% tabs command-line class=tabs-scala-version %} + +{% tab 'Scala 2' for=command-line %} ```bash $ scala -Welcome to Scala 3.0.0 (OpenJDK 64-Bit Server VM, Java 11.0.9). -Type in expressions for evaluation. -Or try :help. +Welcome to Scala {{site.scala-version}} (OpenJDK 64-Bit Server VM, Java 1.8.0_342). +Type in expressions for evaluation. Or try :help. scala> _ ``` +{% endtab %} + +{% tab 'Scala 3' for=command-line %} +```bash +$ scala +Welcome to Scala {{site.scala-3-version}} (1.8.0_322, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> _ +``` +{% endtab %} + +{% endtabs %} The REPL is a command-line interpreter, so it sits there waiting for you to type something. Now you can type Scala expressions to see how they work: +{% tabs expression-one %} +{% tab 'Scala 2 and 3' for=expression-one %} ```` scala> 1 + 1 val res0: Int = 2 @@ -30,20 +49,28 @@ val res0: Int = 2 scala> 2 + 2 val res1: Int = 4 ```` +{% endtab %} +{% endtabs %} As shown in the output, if you don’t assign a variable to the result of an expression, the REPL creates variables named `res0`, `res1`, etc., for you. You can use these variable names in subsequent expressions: +{% tabs expression-two %} +{% tab 'Scala 2 and 3' for=expression-two %} ```` scala> val x = res0 * 10 val x: Int = 20 ```` +{% endtab %} +{% endtabs %} Notice that the REPL output also shows the result of your expressions. You can run all sorts of experiments in the REPL. This example shows how to create and then call a `sum` method: +{% tabs expression-three %} +{% tab 'Scala 2 and 3' for=expression-three %} ```` scala> def sum(a: Int, b: Int): Int = a + b def sum(a: Int, b: Int): Int @@ -51,6 +78,8 @@ def sum(a: Int, b: Int): Int scala> sum(2, 2) val res2: Int = 4 ```` +{% endtab %} +{% endtabs %} If you prefer a browser-based playground environment, you can also use [scastie.scala-lang.org](https://scastie.scala-lang.org). diff --git a/_overviews/scala3-book/taste-summary.md b/_overviews/scala3-book/taste-summary.md index a80297b2d9..96c95089c3 100644 --- a/_overviews/scala3-book/taste-summary.md +++ b/_overviews/scala3-book/taste-summary.md @@ -2,6 +2,7 @@ title: Summary type: section description: This page provides a summary of the previous 'Taste of Scala' sections. +languages: [ru, zh-cn] num: 16 previous-page: taste-toplevel-definitions next-page: first-look-at-types @@ -20,7 +21,7 @@ In the previous sections you saw: - How to use objects for several purposes - An introduction to [contextual abstraction][contextual] -We also mentioned that if you prefer using a browser-based playground environment instead of the Scala REPL, you can also use [Scastie.scala-lang.org](https://scastie.scala-lang.org/?target=dotty) or [ScalaFiddle.io](https://scalafiddle.io). +We also mentioned that if you prefer using a browser-based playground environment instead of the Scala REPL, you can also use [Scastie](https://scastie.scala-lang.org/). Scala has even more features that aren’t covered in this whirlwind tour. See the remainder of this book and the [Reference documentation][reference] for many more details. diff --git a/_overviews/scala3-book/taste-toplevel-definitions.md b/_overviews/scala3-book/taste-toplevel-definitions.md index 3d3a1385ed..b56273945f 100644 --- a/_overviews/scala3-book/taste-toplevel-definitions.md +++ b/_overviews/scala3-book/taste-toplevel-definitions.md @@ -2,6 +2,7 @@ title: Toplevel Definitions type: section description: This page provides an introduction to top-level definitions in Scala 3 +languages: [ru, zh-cn] num: 15 previous-page: taste-contextual-abstractions next-page: taste-summary @@ -11,6 +12,8 @@ next-page: taste-summary In Scala 3, all kinds of definitions can be written at the “top level” of your source code files. For instance, you can create a file named _MyCoolApp.scala_ and put these contents into it: +{% tabs toplevel_1 %} +{% tab 'Scala 3 only' for=toplevel_1 %} ```scala import scala.collection.mutable.ArrayBuffer @@ -36,15 +39,18 @@ type Money = BigDecimal p.toppings += Cheese println("show me the code".capitalizeAllWords) ``` +{% endtab %} +{% endtabs %} As shown, there’s no need to put those definitions inside a `package`, `class`, or other construct. - ## Replaces package objects If you’re familiar with Scala 2, this approach replaces _package objects_. But while being much easier to use, they work similarly: When you place a definition in a package named _foo_, you can then access that definition under all other packages under _foo_, such as within the _foo.bar_ package in this example: +{% tabs toplevel_2 %} +{% tab 'Scala 3 only' for=toplevel_2 %} ```scala package foo { def double(i: Int) = i * 2 @@ -57,9 +63,9 @@ package foo { } } ``` +{% endtab %} +{% endtabs %} Curly braces are used in this example to put an emphasis on the package nesting. The benefit of this approach is that you can place definitions under a package named _com.acme.myapp_, and then those definitions can be referenced within _com.acme.myapp.model_, _com.acme.myapp.controller_, etc. - - diff --git a/_overviews/scala3-book/taste-vars-data-types.md b/_overviews/scala3-book/taste-vars-data-types.md index 36b7445a52..194e2d7f40 100644 --- a/_overviews/scala3-book/taste-vars-data-types.md +++ b/_overviews/scala3-book/taste-vars-data-types.md @@ -2,16 +2,15 @@ title: Variables and Data Types type: section description: This section demonstrates val and var variables, and some common Scala data types. +languages: [ru, zh-cn] num: 7 previous-page: taste-repl next-page: taste-control-structures --- - This section provides a look at Scala variables and data types. - ## Two types of variables When you create a new variable in Scala, you declare whether the variable is immutable or mutable: @@ -37,6 +36,9 @@ When you create a new variable in Scala, you declare whether the variable is imm These examples show how to create `val` and `var` variables: +{% tabs var-express-1 %} +{% tab 'Scala 2 and 3' %} + ```scala // immutable val a = 0 @@ -44,36 +46,54 @@ val a = 0 // mutable var b = 1 ``` +{% endtab %} +{% endtabs %} In an application, a `val` can’t be reassigned. You’ll cause a compiler error if you try to reassign one: +{% tabs var-express-2 %} +{% tab 'Scala 2 and 3' %} + ```scala val msg = "Hello, world" msg = "Aloha" // "reassignment to val" error; this won’t compile ``` +{% endtab %} +{% endtabs %} Conversely, a `var` can be reassigned: +{% tabs var-express-3 %} +{% tab 'Scala 2 and 3' %} + ```scala var msg = "Hello, world" msg = "Aloha" // this compiles because a var can be reassigned ``` - - +{% endtab %} +{% endtabs %} ## Declaring variable types When you create a variable you can explicitly declare its type, or let the compiler infer the type: +{% tabs var-express-4 %} +{% tab 'Scala 2 and 3' %} + ```scala val x: Int = 1 // explicit val x = 1 // implicit; the compiler infers the type ``` +{% endtab %} +{% endtabs %} The second form is known as _type inference_, and it’s a great way to help keep this type of code concise. The Scala compiler can usually infer the data type for you, as shown in the output of these REPL examples: +{% tabs var-express-5 %} +{% tab 'Scala 2 and 3' %} + ```scala scala> val x = 1 val x: Int = 1 @@ -84,19 +104,24 @@ val s: String = a string scala> val nums = List(1, 2, 3) val nums: List[Int] = List(1, 2, 3) ``` +{% endtab %} +{% endtabs %} You can always explicitly declare a variable’s type if you prefer, but in simple assignments like these it isn’t necessary: +{% tabs var-express-6 %} +{% tab 'Scala 2 and 3' %} + ```scala val x: Int = 1 val s: String = "a string" val p: Person = Person("Richard") ``` +{% endtab %} +{% endtabs %} Notice that with this approach, the code feels more verbose than necessary. - - {% comment %} TODO: Jonathan had an early comment on the text below: “While it might feel like this, I would be afraid that people automatically assume from this statement that everything is always boxed.” Suggestion on how to change this? {% endcomment %} @@ -108,6 +133,9 @@ In Scala, everything is an object. These examples show how to declare variables of the numeric types: +{% tabs var-express-7 %} +{% tab 'Scala 2 and 3' %} + ```scala val b: Byte = 1 val i: Int = 1 @@ -116,38 +144,59 @@ val s: Short = 1 val d: Double = 2.0 val f: Float = 3.0 ``` +{% endtab %} +{% endtabs %} Because `Int` and `Double` are the default numeric types, you typically create them without explicitly declaring the data type: +{% tabs var-express-8 %} +{% tab 'Scala 2 and 3' %} + ```scala val i = 123 // defaults to Int val j = 1.0 // defaults to Double ``` +{% endtab %} +{% endtabs %} In your code you can also append the characters `L`, `D`, and `F` (and their lowercase equivalents) to numbers to specify that they are `Long`, `Double`, or `Float` values: +{% tabs var-express-9 %} +{% tab 'Scala 2 and 3' %} + ```scala val x = 1_000L // val x: Long = 1000 val y = 2.2D // val y: Double = 2.2 val z = 3.3F // val z: Float = 3.3 ``` +{% endtab %} +{% endtabs %} When you need really large numbers, use the `BigInt` and `BigDecimal` types: +{% tabs var-express-10 %} +{% tab 'Scala 2 and 3' %} + ```scala var a = BigInt(1_234_567_890_987_654_321L) var b = BigDecimal(123_456.789) ``` +{% endtab %} +{% endtabs %} Where `Double` and `Float` are approximate decimal numbers, `BigDecimal` is used for precise arithmetic. Scala also has `String` and `Char` data types: +{% tabs var-express-11 %} +{% tab 'Scala 2 and 3' %} + ```scala val name = "Bill" // String val c = 'a' // Char ``` - +{% endtab %} +{% endtabs %} ### Strings @@ -161,28 +210,43 @@ Scala strings are similar to Java strings, but they have two great additional fe String interpolation provides a very readable way to use variables inside strings. For instance, given these three variables: +{% tabs var-express-12 %} +{% tab 'Scala 2 and 3' %} + ```scala val firstName = "John" val mi = 'C' val lastName = "Doe" ``` +{% endtab %} +{% endtabs %} You can combine those variables in a string like this: +{% tabs var-express-13 %} +{% tab 'Scala 2 and 3' %} + ```scala println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" ``` +{% endtab %} +{% endtabs %} Just precede the string with the letter `s`, and then put a `$` symbol before your variable names inside the string. To embed arbitrary expressions inside a string, enclose them in curly braces: +{% tabs var-express-14 %} +{% tab 'Scala 2 and 3' %} + ``` scala println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" val x = -1 println(s"x.abs = ${x.abs}") // prints "x.abs = 1" ``` +{% endtab %} +{% endtabs %} The `s` that you place before the string is just one possible interpolator. If you use an `f` instead of an `s`, you can use `printf`-style formatting syntax in the string. @@ -193,15 +257,17 @@ For instance, some database libraries define the very powerful `sql` interpolato Multiline strings are created by including the string inside three double-quotes: +{% tabs var-express-15 %} +{% tab 'Scala 2 and 3' %} + ```scala val quote = """The essence of Scala: Fusion of functional and object-oriented programming in a typed setting.""" ``` +{% endtab %} +{% endtabs %} > For more details on string interpolators and multiline strings, see the [“First Look at Types” chapter][first-look]. - - - [first-look]: {% link _overviews/scala3-book/first-look-at-types.md %} diff --git a/_overviews/scala3-book/tools-sbt.md b/_overviews/scala3-book/tools-sbt.md index eb1ed8f0e6..c17820ecf5 100644 --- a/_overviews/scala3-book/tools-sbt.md +++ b/_overviews/scala3-book/tools-sbt.md @@ -2,7 +2,8 @@ title: Building and Testing Scala Projects with sbt type: section description: This section looks at a commonly-used build tool, sbt, and a testing library, ScalaTest. -num: 70 +languages: [ru, zh-cn] +num: 71 previous-page: scala-tools next-page: tools-worksheets --- @@ -47,7 +48,7 @@ Create a file named _build.properties_ in the directory `project`, with the following content: ```text -sbt.version=1.6.1 +sbt.version=1.10.11 ``` Then create a file named _build.sbt_ in the project root directory that contains this line: @@ -357,7 +358,7 @@ Next, create a _build.properties_ file in the _project/_ subdirectory of your pr with this line: ```text -sbt.version=1.5.4 +sbt.version=1.10.11 ``` Next, create a _build.sbt_ file in the root directory of your project with these contents: @@ -368,7 +369,7 @@ version := "0.1" scalaVersion := "{{site.scala-3-version}}" libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.2.9" % Test + "org.scalatest" %% "scalatest" % "3.2.19" % Test ) ``` diff --git a/_overviews/scala3-book/tools-worksheets.md b/_overviews/scala3-book/tools-worksheets.md index 3231d4e654..cf14935e46 100644 --- a/_overviews/scala3-book/tools-worksheets.md +++ b/_overviews/scala3-book/tools-worksheets.md @@ -2,7 +2,8 @@ title: Worksheets type: section description: This section looks at worksheets, an alternative to Scala projects. -num: 71 +languages: [ru, zh-cn] +num: 72 previous-page: tools-sbt next-page: interacting-with-java --- diff --git a/_overviews/scala3-book/types-adts-gadts.md b/_overviews/scala3-book/types-adts-gadts.md index 19d9641b4a..356d01c16d 100644 --- a/_overviews/scala3-book/types-adts-gadts.md +++ b/_overviews/scala3-book/types-adts-gadts.md @@ -2,9 +2,12 @@ title: Algebraic Data Types type: section description: This section introduces and demonstrates algebraic data types (ADTs) in Scala 3. -num: 52 +languages: [ru, zh-cn] +num: 54 previous-page: types-union next-page: types-variance +scala3: true +versionSpecific: true --- diff --git a/_overviews/scala3-book/types-dependent-function.md b/_overviews/scala3-book/types-dependent-function.md index e70ed26045..cf86880fa6 100644 --- a/_overviews/scala3-book/types-dependent-function.md +++ b/_overviews/scala3-book/types-dependent-function.md @@ -2,13 +2,16 @@ title: Dependent Function Types type: section description: This section introduces and demonstrates dependent function types in Scala 3. -num: 56 +languages: [ru, zh-cn] +num: 58 previous-page: types-structural next-page: types-others +scala3: true +versionSpecific: true --- A *dependent function type* describes function types, where the result type may depend on the function’s parameter values. -The concept of dependent types, and of dependent function types is more advanced and you would typically only come across it when designing your own libraries or using advanced libraries. +The concept of dependent types, and of dependent function types, is more advanced and you would typically only come across it when designing your own libraries or using advanced libraries. ## Dependent Method Types Let's consider the following example of a heterogenous database that can store values of different types. diff --git a/_overviews/scala3-book/types-generics.md b/_overviews/scala3-book/types-generics.md index 004e82e8e5..84ddd4599e 100644 --- a/_overviews/scala3-book/types-generics.md +++ b/_overviews/scala3-book/types-generics.md @@ -2,7 +2,8 @@ title: Generics type: section description: This section introduces and demonstrates generics in Scala 3. -num: 49 +languages: [ru, zh-cn] +num: 51 previous-page: types-inferred next-page: types-intersection --- @@ -12,6 +13,30 @@ Generic classes (or traits) take a type as _a parameter_ within square brackets The Scala convention is to use a single letter (like `A`) to name those type parameters. The type can then be used inside the class as needed for method instance parameters, or on return types: +{% tabs stack class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +// here we declare the type parameter A +// v +class Stack[A] { + private var elements: List[A] = Nil + // ^ + // Here we refer to the type parameter + // v + def push(x: A): Unit = + elements = elements.prepended(x) + def peek: A = elements.head + def pop(): A = { + val currentTop = peek + elements = elements.tail + currentTop + } +} +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala // here we declare the type parameter A // v @@ -20,26 +45,43 @@ class Stack[A]: // ^ // Here we refer to the type parameter // v - def push(x: A): Unit = { elements = elements.prepended(x) } + def push(x: A): Unit = + elements = elements.prepended(x) def peek: A = elements.head def pop(): A = val currentTop = peek elements = elements.tail currentTop ``` +{% endtab %} +{% endtabs %} This implementation of a `Stack` class takes any type as a parameter. The beauty of generics is that you can now create a `Stack[Int]`, `Stack[String]`, and so on, allowing you to reuse your implementation of a `Stack` for arbitrary element types. This is how you create and use a `Stack[Int]`: +{% tabs stack-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val stack = new Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 ``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala val stack = Stack[Int] stack.push(1) stack.push(2) println(stack.pop()) // prints 2 println(stack.pop()) // prints 1 ``` +{% endtab %} +{% endtabs %} > See the [Variance section][variance] for details on how to express variance with generic types. diff --git a/_overviews/scala3-book/types-inferred.md b/_overviews/scala3-book/types-inferred.md index d56bafb629..92333b3735 100644 --- a/_overviews/scala3-book/types-inferred.md +++ b/_overviews/scala3-book/types-inferred.md @@ -2,7 +2,8 @@ title: Inferred Types type: section description: This section introduces and demonstrates inferred types in Scala 3 -num: 48 +languages: [ru, zh-cn] +num: 50 previous-page: types-introduction next-page: types-generics --- @@ -10,22 +11,32 @@ next-page: types-generics As with other statically typed programming languages, in Scala you can _declare_ a type when creating a new variable: +{% tabs xy %} +{% tab 'Scala 2 and 3' %} ```scala val x: Int = 1 val y: Double = 1 ``` +{% endtab %} +{% endtabs %} In those examples the types are _explicitly_ declared to be `Int` and `Double`, respectively. However, in Scala you generally don’t have to declare the type when defining value binders: +{% tabs abm %} +{% tab 'Scala 2 and 3' %} ```scala val a = 1 val b = List(1, 2, 3) val m = Map(1 -> "one", 2 -> "two") ``` +{% endtab %} +{% endtabs %} When you do this, Scala _infers_ the types, as shown in the following REPL interaction: +{% tabs abm2 %} +{% tab 'Scala 2 and 3' %} ```scala scala> val a = 1 val a: Int = 1 @@ -36,5 +47,7 @@ val b: List[Int] = List(1, 2, 3) scala> val m = Map(1 -> "one", 2 -> "two") val m: Map[Int, String] = Map(1 -> one, 2 -> two) ``` +{% endtab %} +{% endtabs %} Indeed, most variables are defined this way, and Scala’s ability to automatically infer types is one feature that makes it _feel_ like a dynamically typed language. diff --git a/_overviews/scala3-book/types-intersection.md b/_overviews/scala3-book/types-intersection.md index 625f2bfa20..2c533ffd09 100644 --- a/_overviews/scala3-book/types-intersection.md +++ b/_overviews/scala3-book/types-intersection.md @@ -2,16 +2,22 @@ title: Intersection Types type: section description: This section introduces and demonstrates intersection types in Scala 3. -num: 50 +languages: [ru, zh-cn] +num: 52 previous-page: types-generics next-page: types-union +scala3: true +versionSpecific: true --- - Used on types, the `&` operator creates a so called _intersection type_. The type `A & B` represents values that are **both** of the type `A` and of the type `B` at the same time. For instance, the following example uses the intersection type `Resettable & Growable[String]`: +{% tabs intersection-reset-grow %} + +{% tab 'Scala 3 Only' %} + ```scala trait Resettable: def reset(): Unit @@ -24,6 +30,10 @@ def f(x: Resettable & Growable[String]): Unit = x.add("first") ``` +{% endtab %} + +{% endtabs %} + In the method `f` in this example, the parameter `x` is required to be *both* a `Resettable` and a `Growable[String]`. The _members_ of an intersection type `A & B` are all the members of `A` and all the members of `B`. @@ -32,10 +42,23 @@ Therefore, as shown, `Resettable & Growable[String]` has member methods `reset` Intersection types can be useful to describe requirements _structurally_. That is, in our example `f`, we directly express that we are happy with any value for `x` as long as it’s a subtype of both `Resettable` and `Growable`. We **did not** have to create a _nominal_ helper trait like the following: + +{% tabs normal-trait class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait Both[A] extends Resettable with Growable[A] +def f(x: Both[String]): Unit +``` +{% endtab %} + +{% tab 'Scala 3' %} ```scala trait Both[A] extends Resettable, Growable[A] def f(x: Both[String]): Unit ``` +{% endtab %} +{% endtabs %} + There is an important difference between the two alternatives of defining `f`: While both allow `f` to be called with instances of `Both`, only the former allows passing instances that are subtypes of `Resettable` and `Growable[String]`, but _not of_ `Both[String]`. > Note that `&` is _commutative_: `A & B` is the same type as `B & A`. diff --git a/_overviews/scala3-book/types-introduction.md b/_overviews/scala3-book/types-introduction.md index 5719112cae..77a79a0844 100644 --- a/_overviews/scala3-book/types-introduction.md +++ b/_overviews/scala3-book/types-introduction.md @@ -2,7 +2,8 @@ title: Types and the Type System type: chapter description: This chapter provides an introduction to Scala 3 types and the type system. -num: 47 +languages: [ru, zh-cn] +num: 49 previous-page: fp-summary next-page: types-inferred --- @@ -11,19 +12,27 @@ next-page: types-inferred Scala is a unique language in that it’s statically typed, but often _feels_ flexible and dynamic. For instance, thanks to type inference you can write code like this without explicitly specifying the variable types: +{% tabs hi %} +{% tab 'Scala 2 and 3' %} ```scala val a = 1 val b = 2.0 val c = "Hi!" ``` +{% endtab %} +{% endtabs %} That makes the code feel dynamically typed. And thanks to new features, like [union types][union-types] in Scala 3, you can also write code like the following that expresses very concisely which values are expected as arguments and which types are returned: +{% tabs union-example %} +{% tab 'Scala 3 Only' %} ```scala def isTruthy(a: Boolean | Int | String): Boolean = ??? def dogCatOrWhatever(): Dog | Plant | Car | Sun = ??? ``` +{% endtab %} +{% endtabs %} As the example suggests, when using union types, the types don’t have to share a common hierarchy, and you can still accept them as arguments or return them from a method. diff --git a/_overviews/scala3-book/types-opaque-types.md b/_overviews/scala3-book/types-opaque-types.md index 4911446f1d..4076749050 100644 --- a/_overviews/scala3-book/types-opaque-types.md +++ b/_overviews/scala3-book/types-opaque-types.md @@ -2,12 +2,16 @@ title: Opaque Types type: section description: This section introduces and demonstrates opaque types in Scala 3. -num: 54 +languages: [ru, zh-cn] +num: 56 previous-page: types-variance next-page: types-structural +scala3: true +versionSpecific: true --- -Scala 3 _Opaque type aliases_ provide type abstractions without any **overhead**. +_Opaque type aliases_ provide type abstraction without any **overhead**. +In Scala 2, a similar result could be achieved with [value classes][value-classes]. ## Abstraction Overhead @@ -124,12 +128,12 @@ However, outside of the module the type `Logarithm` is completely encapsulated, ```scala import Logarithms.* -val l2 = Logarithm(2.0) -val l3 = Logarithm(3.0) -println((l2 * l3).toDouble) // prints 6.0 -println((l2 + l3).toDouble) // prints 4.999... +val log2 = Logarithm(2.0) +val log3 = Logarithm(3.0) +println((log2 * log3).toDouble) // prints 6.0 +println((log2 + log3).toDouble) // prints 4.999... -val d: Double = l2 // ERROR: Found Logarithm required Double +val d: Double = log2 // ERROR: Found Logarithm required Double ``` Even though we abstracted over `Logarithm`, the abstraction comes for free: @@ -141,3 +145,4 @@ As illustrated above, opaque types are convenient to use, and integrate very wel [extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[value-classes]: {% link _overviews/core/value-classes.md %} diff --git a/_overviews/scala3-book/types-others.md b/_overviews/scala3-book/types-others.md index cb87cccf2e..9419073f95 100644 --- a/_overviews/scala3-book/types-others.md +++ b/_overviews/scala3-book/types-others.md @@ -2,9 +2,12 @@ title: Other Types type: section description: This section mentions other advanced types in Scala 3. -num: 57 +languages: [ru, zh-cn] +num: 59 previous-page: types-dependent-function next-page: ca-contextual-abstractions-intro +scala3: true +versionSpecific: true --- @@ -18,7 +21,9 @@ Scala has several other advanced types that are not shown in this book, includin - Refinement types - Kind polymorphism -For more details on these types, see the [Reference documentation][reference]. +For more details on most of these types, refer to the [Scala 3 Reference documentation][reference]. +For singleton types see the [literal types](https://scala-lang.org/files/archive/spec/3.4/03-types.html#literal-types) section of the Scala 3 spec, +and for refinement types, see the [refined types](https://scala-lang.org/files/archive/spec/3.4/03-types.html) section. diff --git a/_overviews/scala3-book/types-structural.md b/_overviews/scala3-book/types-structural.md index 8ae1d57d93..afa74fe340 100644 --- a/_overviews/scala3-book/types-structural.md +++ b/_overviews/scala3-book/types-structural.md @@ -2,15 +2,22 @@ title: Structural Types type: section description: This section introduces and demonstrates structural types in Scala 3. -num: 55 +languages: [ru, zh-cn] +num: 57 previous-page: types-opaque-types next-page: types-dependent-function +scala3: true +versionSpecific: true --- {% comment %} NOTE: It would be nice to simplify this more. {% endcomment %} +_Scala 2 has a weaker form of structural types based on Java reflection, achieved with `import scala.language.reflectiveCalls`_. + +## Introduction + Some use cases, such as modeling database access, are more awkward in statically typed languages than in dynamically typed languages. With dynamically typed languages, it’s natural to model a row as a record or object, and to select entries with simple dot notation, e.g. `row.columnName`. diff --git a/_overviews/scala3-book/types-type-classes.md b/_overviews/scala3-book/types-type-classes.md deleted file mode 100644 index f0391f3806..0000000000 --- a/_overviews/scala3-book/types-type-classes.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Type Classes -type: section -description: This section introduces type classes in Scala 3. -num: 60 -previous-page: ca-given-using-clauses -next-page: ca-context-bounds ---- - -A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. -If you are coming from Java, you can think of type classes as something like [`java.util.Comparator[T]`][comparator]. - -> The paper [“Type Classes as Objects and Implicits”][typeclasses-paper] (2010) by Oliveira et al. discusses the basic ideas behind type classes in Scala. -> Even though the paper uses an older version of Scala, the ideas still hold to the current day. - -This style of programming is useful in multiple use-cases, for example: - -- Expressing how a type you don’t own---such as from the standard library or a third-party library---conforms to such behavior -- Adding behavior to multiple types without introducing sub-typing relationships between those types (i.e., one `extends` another) - -In Scala 3, _type classes_ are just _traits_ with one or more type parameters, like the following: -``` -trait Show[A]: - def show(a: A): String -``` -Instances of `Show` for a particular type `A` witness that we can show (i.e., produce a text representation of) an instance of type `A`. -For example, let’s look at the following `Show` instance for `Int` values: - -```scala -class ShowInt extends Show[Int]: - def show(a: Int) = s"The number is ${a}!" -``` -We can write methods that work on arbitrary types `A` _constrained_ by `Show` as follows: - -```scala -def toHtml[A](a: A)(showA: Show[A]): String = - "<p>" + showA.show(a) + "</p>" -``` -That is, `toHtml` can be called with arbitrary `A` _as long_ as you can also provide an instance of `Show[A]`. -For example, we can call it like: -```scala -toHtml(42)(ShowInt()) -// results in "<p>The number is 42!</p>" -``` - -#### Automatically passing type class instances -Since type classes are a very important way to structure software, Scala 3 offers additional features that make working with them very convenient. -We discuss these additional features (which fall into the category of *Contextual Abstractions*) in a [later chapter][typeclasses-chapter] of this book. - -[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf -[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} -[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html diff --git a/_overviews/scala3-book/types-union.md b/_overviews/scala3-book/types-union.md index 30df27d05c..e685646608 100644 --- a/_overviews/scala3-book/types-union.md +++ b/_overviews/scala3-book/types-union.md @@ -2,9 +2,12 @@ title: Union Types type: section description: This section introduces and demonstrates union types in Scala 3. -num: 51 +languages: [ru, zh-cn] +num: 53 previous-page: types-intersection next-page: types-adts-gadts +scala3: true +versionSpecific: true --- Used on types, the `|` operator creates a so-called _union type_. @@ -42,7 +45,7 @@ case 1.0 => ??? // ERROR: this line won’t compile As shown, union types can be used to represent alternatives of several different types, without requiring those types to be part of a custom-crafted class hierarchy, or requiring explicit wrapping. #### Pre-planning the Class Hierarchy -Other languages would require pre-planning of the class hierarchy, like the following example illustrates: +Without union types, it would require pre-planning of the class hierarchy, like the following example illustrates: ```scala trait UsernameOrPassword @@ -50,6 +53,7 @@ case class Username(name: String) extends UsernameOrPassword case class Password(hash: Hash) extends UsernameOrPassword def help(id: UsernameOrPassword) = ... ``` + Pre-planning does not scale very well since, for example, requirements of API users might not be foreseeable. Additionally, cluttering the type hierarchy with marker traits like `UsernameOrPassword` also makes the code more difficult to read. @@ -76,10 +80,10 @@ val password = Password(123) // password: Password = Password(123) This REPL example shows how a union type can be used when binding a variable to the result of an `if`/`else` expression: ```` -scala> val a = if (true) name else password +scala> val a = if true then name else password val a: Object = Username(Eve) -scala> val b: Password | Username = if (true) name else password +scala> val b: Password | Username = if true then name else password val b: Password | Username = Username(Eve) ```` diff --git a/_overviews/scala3-book/types-variance.md b/_overviews/scala3-book/types-variance.md index 84cba7d658..f2b8e3d931 100644 --- a/_overviews/scala3-book/types-variance.md +++ b/_overviews/scala3-book/types-variance.md @@ -2,7 +2,8 @@ title: Variance type: section description: This section introduces and demonstrates variance in Scala 3. -num: 53 +languages: [ru, zh-cn] +num: 55 previous-page: types-adts-gadts next-page: types-opaque-types --- @@ -10,13 +11,41 @@ next-page: types-opaque-types Type parameter _variance_ controls the subtyping of parameterized types (like classes or traits). To explain variance, let us assume the following type definitions: + +{% tabs types-variance-1 %} +{% tab 'Scala 2 and 3' %} ```scala trait Item { def productNumber: String } trait Buyable extends Item { def price: Int } trait Book extends Buyable { def isbn: String } + ``` +{% endtab %} +{% endtabs %} Let us also assume the following parameterized types: + +{% tabs types-variance-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-2 %} +```scala +// an example of an invariant type +trait Pipeline[T] { + def process(t: T): T +} + +// an example of a covariant type +trait Producer[+T] { + def make: T +} + +// an example of a contravariant type +trait Consumer[-T] { + def take(t: T): Unit +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-2 %} ```scala // an example of an invariant type trait Pipeline[T]: @@ -30,6 +59,9 @@ trait Producer[+T]: trait Consumer[-T]: def take(t: T): Unit ``` +{% endtab %} +{% endtabs %} + In general there are three modes of variance: - **invariant**---the default, written like `Pipeline[T]` @@ -44,6 +76,22 @@ This means that types like `Pipeline[Item]`, `Pipeline[Buyable]`, and `Pipeline[ And rightfully so! Assume the following method that consumes two values of type `Pipeline[Buyable]`, and passes its argument `b` to one of them, based on the price: +{% tabs types-variance-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-3 %} +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = { + val b1 = p1.process(b) + val b2 = p2.process(b) + if (b1.price < b2.price) b1 else b2 + } +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-3 %} ```scala def oneOf( p1: Pipeline[Buyable], @@ -54,10 +102,19 @@ def oneOf( val b2 = p2.process(b) if b1.price < b2.price then b1 else b2 ``` +{% endtab %} +{% endtabs %} + Now, recall that we have the following _subtyping relationship_ between our types: + +{% tabs types-variance-4 %} +{% tab 'Scala 2 and 3' %} ```scala Book <: Buyable <: Item ``` +{% endtab %} +{% endtabs %} + We cannot pass a `Pipeline[Book]` to the method `oneOf` because in its implementation, we call `p1` and `p2` with a value of type `Buyable`. A `Pipeline[Book]` expects a `Book`, which can potentially cause a runtime error. @@ -67,7 +124,6 @@ We cannot pass a `Pipeline[Item]` because calling `process` on it only promises In fact, type `Pipeline` needs to be invariant since it uses its type parameter `T` _both_ as an argument _and_ as a return type. For the same reason, some types in the Scala collection library---like `Array` or `Set`---are also _invariant_. - ### Covariant Types In contrast to `Pipeline`, which is invariant, the type `Producer` is marked as **covariant** by prefixing the type parameter with a `+`. This is valid, since the type parameter is only used in a _return position_. @@ -77,33 +133,47 @@ And in fact, this is sound. The type of `Producer[Buyable].make` only promises t As a caller of `make`, we will be happy to also accept a `Book`, which is a subtype of `Buyable`---that is, it is _at least_ a `Buyable`. This is illustrated by the following example, where the function `makeTwo` expects a `Producer[Buyable]`: + +{% tabs types-variance-5 %} +{% tab 'Scala 2 and 3' %} ```scala def makeTwo(p: Producer[Buyable]): Int = p.make.price + p.make.price ``` +{% endtab %} +{% endtabs %} + It is perfectly fine to pass a producer for books: -``` + +{% tabs types-variance-6 %} +{% tab 'Scala 2 and 3' %} +```scala val bookProducer: Producer[Book] = ??? makeTwo(bookProducer) ``` -The call to `price` within `makeTwo` is still valid also for books. +{% endtab %} +{% endtabs %} +The call to `price` within `makeTwo` is still valid also for books. #### Covariant Types for Immutable Containers You will encounter covariant types a lot when dealing with immutable containers, like those that can be found in the standard library (such as `List`, `Seq`, `Vector`, etc.). For example, `List` and `Vector` are approximately defined as: +{% tabs types-variance-7 %} +{% tab 'Scala 2 and 3' %} ```scala class List[+A] ... class Vector[+A] ... ``` +{% endtab %} +{% endtabs %} This way, you can use a `List[Book]` where a `List[Buyable]` is expected. This also intuitively makes sense: If you are expecting a collection of things that can be bought, it should be fine to give you a collection of books. They have an additional ISBN method in our example, but you are free to ignore these additional capabilities. - ### Contravariant Types In contrast to the type `Producer`, which is marked as covariant, the type `Consumer` is marked as **contravariant** by prefixing the type parameter with a `-`. This is valid, since the type parameter is only used in an _argument position_. @@ -115,20 +185,34 @@ Remember, for type `Producer`, it was the other way around, and we had `Producer And in fact, this is sound. The method `Consumer[Item].take` accepts an `Item`. As a caller of `take`, we can also supply a `Buyable`, which will be happily accepted by the `Consumer[Item]` since `Buyable` is a subtype of `Item`---that is, it is _at least_ an `Item`. - #### Contravariant Types for Consumers Contravariant types are much less common than covariant types. As in our example, you can think of them as “consumers.” The most important type that you might come across that is marked contravariant is the one of functions: +{% tabs types-variance-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-8 %} +```scala +trait Function[-A, +B] { + def apply(a: A): B +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-8 %} ```scala trait Function[-A, +B]: def apply(a: A): B ``` +{% endtab %} +{% endtabs %} + Its argument type `A` is marked as contravariant `A`---it consumes values of type `A`. In contrast, its result type `B` is marked as covariant---it produces values of type `B`. Here are some examples that illustrate the subtyping relationships induced by variance annotations on functions: +{% tabs types-variance-9 %} +{% tab 'Scala 2 and 3' %} ```scala val f: Function[Buyable, Buyable] = b => b @@ -138,6 +222,8 @@ val g: Function[Buyable, Item] = f // OK to provide a Book where a Buyable is expected val h: Function[Book, Buyable] = f ``` +{% endtab %} +{% endtabs %} ## Summary In this section, we have encountered three different kinds of variance: diff --git a/_overviews/scala3-book/where-next.md b/_overviews/scala3-book/where-next.md new file mode 100644 index 0000000000..8eed7a163f --- /dev/null +++ b/_overviews/scala3-book/where-next.md @@ -0,0 +1,16 @@ +--- +title: Where To Go Next +type: chapter +description: Where to go next after reading the Scala Book +languages: [zh-cn] +num: 77 +previous-page: scala-for-python-devs +next-page: +--- + +We hope you enjoyed this introduction to the Scala programming language, and we also hope we were able to share some of the beauty of the language. + +As you continue working with Scala, you can find many more details at the +[Guides and Overviews section][overviews] of our website. + +[overviews]: {% link _overviews/index.md %} diff --git a/_overviews/scala3-book/why-scala-3.md b/_overviews/scala3-book/why-scala-3.md index 53f0184685..639c04691e 100644 --- a/_overviews/scala3-book/why-scala-3.md +++ b/_overviews/scala3-book/why-scala-3.md @@ -2,6 +2,7 @@ title: Why Scala 3? type: chapter description: This page describes the benefits of the Scala 3 programming language. +languages: [ru, zh-cn] num: 3 previous-page: scala-features next-page: taste-intro @@ -12,7 +13,6 @@ TODO: Is “Scala 3 Benefits” a better title? NOTE: Could mention “grammar” as a way of showing that Scala isn’t a large language; see this slide: https://www.slideshare.net/Odersky/preparing-for-scala-3#13 {% endcomment %} - There are many benefits to using Scala, and Scala 3 in particular. It’s hard to list every benefit of Scala, but a “Top Ten” list might look like this: @@ -27,8 +27,6 @@ It’s hard to list every benefit of Scala, but a “Top Ten” list might look 9. The Scala ecosystem offers the most modern FP libraries in the world 10. Strong type system - - ## 1) FP/OOP fusion More than any other language, Scala supports a fusion of the FP and OOP paradigms. @@ -40,15 +38,21 @@ As Martin Odersky has stated, the essence of Scala is a fusion of functional and Possibly some of the best examples of modularity are the classes in the standard library. For instance, a `List` is defined as a class---technically it’s an abstract class---and a new instance is created like this: +{% tabs list %} +{% tab 'Scala 2 and 3' for=list %} ```scala val x = List(1, 2, 3) ``` +{% endtab %} +{% endtabs %} However, what appears to the programmer to be a simple `List` is actually built from a combination of several specialized types, including traits named `Iterable`, `Seq`, and `LinearSeq`. Those types are similarly composed of other small, modular units of code. In addition to building a type like `List` from a series of modular traits, the `List` API also consists of dozens of other methods, many of which are higher-order functions: +{% tabs list-methods %} +{% tab 'Scala 2 and 3' for=list-methods %} ```scala val xs = List(1, 2, 3, 4, 5) @@ -57,41 +61,55 @@ xs.filter(_ < 3) // List(1, 2) xs.find(_ > 3) // Some(4) xs.takeWhile(_ < 3) // List(1, 2) ``` +{% endtab %} +{% endtabs %} In those examples, the values in the list can’t be modified. The `List` class is immutable, so all of those methods return new values, as shown by the data in each comment. - - ## 2) A dynamic feel Scala’s _type inference_ often makes the language feel dynamically typed, even though it’s statically typed. This is true with variable declaration: +{% tabs dynamic %} +{% tab 'Scala 2 and 3' for=dynamic %} ```scala val a = 1 val b = "Hello, world" val c = List(1,2,3,4,5) val stuff = ("fish", 42, 1_234.5) ``` +{% endtab %} +{% endtabs %} It’s also true when passing anonymous functions to higher-order functions: +{% tabs dynamic-hof %} +{% tab 'Scala 2 and 3' for=dynamic-hof %} ```scala list.filter(_ < 4) list.map(_ * 2) list.filter(_ < 4) .map(_ * 2) ``` +{% endtab %} +{% endtabs %} and when defining methods: +{% tabs dynamic-method %} +{% tab 'Scala 2 and 3' for=dynamic-method %} ```scala def add(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} This is more true than ever in Scala 3, such as when using [union types][union-types]: +{% tabs union %} +{% tab 'Scala 3 Only' for=union %} ```scala // union type parameter def help(id: Username | Password) = @@ -103,21 +121,27 @@ def help(id: Username | Password) = // union type value val b: Password | Username = if (true) name else password ``` - - +{% endtab %} +{% endtabs %} ## 3) Concise syntax Scala is a low ceremony, “concise but still readable” language. For instance, variable declaration is concise: +{% tabs concise %} +{% tab 'Scala 2 and 3' for=concise %} ```scala val a = 1 val b = "Hello, world" val c = List(1,2,3) ``` +{% endtab %} +{% endtabs %} Creating types like traits, classes, and enumerations are concise: +{% tabs enum %} +{% tab 'Scala 3 Only' for=enum %} ```scala trait Tail: def wagTail(): Unit @@ -134,18 +158,23 @@ case class Person( age: Int ) ``` +{% endtab %} +{% endtabs %} Higher-order functions are concise: +{% tabs list-hof %} +{% tab 'Scala 2 and 3' for=list-hof %} + ```scala list.filter(_ < 4) list.map(_ * 2) ``` +{% endtab %} +{% endtabs %} All of these expressions and many more are concise, and still very readable: what we call _expressive_. - - ## 4) Implicits, simplified Implicits in Scala 2 were a major distinguishing design feature. @@ -174,8 +203,6 @@ Benefits of these changes include: These capabilities are described in detail in other sections, so see the [Contextual Abstraction introduction][contextual], and the section on [`given` and `using` clauses][given] for more details. - - ## 5) Seamless Java integration Scala/Java interaction is seamless in many ways. @@ -200,8 +227,6 @@ While almost every interaction is seamless, the [“Interacting with Java” cha See that chapter for more details on these features. - - ## 6) Client & server Scala can be used on the server side with terrific frameworks: @@ -214,8 +239,6 @@ The Scala.js ecosystem [has dozens of libraries](https://www.scala-js.org/librar In addition to those tools, the [Scala Native](https://github.com/scala-native/scala-native) project “is an optimizing ahead-of-time compiler and lightweight managed runtime designed specifically for Scala.” It lets you build “systems” style binary executable applications with plain Scala code, and also lets you use lower-level primitives. - - ## 7) Standard library methods You will rarely ever need to write a custom `for` loop again, because the dozens of pre-built functional methods in the Scala standard library will both save you time, and help make code more consistent across different applications. @@ -225,6 +248,8 @@ While these all use the `List` class, the same methods work with other collectio Here are some examples: +{% tabs list-more %} +{% tab 'Scala 2 and 3' for=list-more %} ```scala List.range(1, 3) // List(1, 2) List.range(start = 1, end = 6, step = 2) // List(1, 3, 5) @@ -263,36 +288,50 @@ nums.sorted // List(1, 5, 7, 8, 10) nums.sortWith(_ < _) // List(1, 5, 7, 8, 10) nums.sortWith(_ > _) // List(10, 8, 7, 5, 1) ``` - - +{% endtab %} +{% endtabs %} ## 8) Built-in best practices Scala idioms encourage best practices in many ways. For immutability, you’re encouraged to create immutable `val` declarations: +{% tabs val %} +{% tab 'Scala 2 and 3' for=val %} ```scala val a = 1 // immutable variable ``` +{% endtab %} +{% endtabs %} You’re also encouraged to use immutable collections classes like `List` and `Map`: +{% tabs list-map %} +{% tab 'Scala 2 and 3' for=list-map %} ```scala val b = List(1,2,3) // List is immutable val c = Map(1 -> "one") // Map is immutable ``` +{% endtab %} +{% endtabs %} Case classes are primarily intended for use in [domain modeling]({% link _overviews/scala3-book/domain-modeling-intro.md %}), and their parameters are immutable: +{% tabs case-class %} +{% tab 'Scala 2 and 3' for=case-class %} ```scala case class Person(name: String) val p = Person("Michael Scott") p.name // Michael Scott p.name = "Joe" // compiler error (reassignment to val name) ``` +{% endtab %} +{% endtabs %} As shown in the previous section, Scala collections classes support higher-order functions, and you can pass methods (not shown) and anonymous functions into them: +{% tabs higher-order %} +{% tab 'Scala 2 and 3' for=higher-order %} ```scala a.dropWhile(_ < 25) a.filter(_ < 25) @@ -301,25 +340,52 @@ a.filter(_ < 30).map(_ * 10) nums.sortWith(_ < _) nums.sortWith(_ > _) ``` +{% endtab %} +{% endtabs %} `match` expressions let you use pattern matching, and they truly are _expressions_ that return values: +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' for=match %} +```scala +val numAsString = i match { + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +} +``` +{% endtab %} + +{% tab 'Scala 3' for=match %} ```scala val numAsString = i match case 1 | 3 | 5 | 7 | 9 => "odd" case 2 | 4 | 6 | 8 | 10 => "even" case _ => "too big" ``` +{% endtab %} +{% endtabs %} Because they can return values, they’re often used as the body of a method: +{% tabs match-body class=tabs-scala-version %} +{% tab 'Scala 2' for=match-body %} ```scala -def isTruthy(a: Matchable) = a match +def isTruthy(a: Matchable) = a match { case 0 | "" => false case _ => true +} ``` +{% endtab %} - +{% tab 'Scala 3' for=match-body %} +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true +``` +{% endtab %} +{% endtabs %} ## 9) Ecosystem libraries @@ -328,8 +394,6 @@ All of the buzzwords like high-performance, type safe, concurrent, asynchronous, We could list hundreds of libraries here, but fortunately they’re all listed in another location: For those details, see the [“Awesome Scala” list](https://github.com/lauris/awesome-scala). - - ## 10) Strong type system Scala has a strong type system, and it’s been improved even more in Scala 3. @@ -380,7 +444,6 @@ A list of types from the Dotty documentation: - Bounds {% endcomment %} - _Safety_ is related to several new and changed features: - Multiversal equality @@ -390,6 +453,8 @@ _Safety_ is related to several new and changed features: Good examples of _ergonomics_ are enumerations and extension methods, which have been added to Scala 3 in a very readable manner: +{% tabs extension %} +{% tab 'Scala 3 Only' for=extension %} ```scala // enumeration enum Color: @@ -401,6 +466,8 @@ extension (c: Circle) def diameter: Double = c.radius * 2 def area: Double = math.Pi * c.radius * c.radius ``` +{% endtab %} +{% endtabs %} _Performance_ relates to several areas. One of those is [opaque types][opaque-types]. @@ -416,8 +483,6 @@ Conversely, the goal of opaque types, as described in that SIP, is that “opera For more type system details, see the [Reference documentation][reference]. - - ## Other great features Scala has many great features, and choosing a Top 10 list can be subjective. @@ -425,12 +490,12 @@ Several surveys have shown that different groups of developers love different fe Hopefully you’ll discover more great Scala features as you use the language. [java]: {% link _overviews/scala3-book/interacting-with-java.md %} -[given]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[given]: {% link _overviews/scala3-book/ca-context-parameters.md %} [contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} -[reference]: {{ site.scala3ref }}/overview.html -[dropped]: https://docs.scala-lang.org/scala3/reference/dropped-features.html -[changed]: https://docs.scala-lang.org/scala3/reference/changed-features.html -[added]: https://docs.scala-lang.org/scala3/reference/other-new-features.html +[reference]: {{ site.scala3ref }} +[dropped]: {{ site.scala3ref }}/dropped-features +[changed]: {{ site.scala3ref }}/changed-features +[added]:{{ site.scala3ref }}/other-new-features [union-types]: {% link _overviews/scala3-book/types-union.md %} [opaque-types]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_overviews/scala3-contribution/arch-context.md b/_overviews/scala3-contribution/arch-context.md index 3586366480..cbf342703f 100644 --- a/_overviews/scala3-contribution/arch-context.md +++ b/_overviews/scala3-contribution/arch-context.md @@ -1,58 +1,5 @@ --- title: Contexts -type: section description: This page describes symbols in the Scala 3 compiler. -num: 14 -previous-page: arch-lifecycle -next-page: arch-phases ---- - -`dotc` has almost no global state (with the exception of the name table, -which hashes strings into unique names). Instead, all -essential bits of information that can vary over a compiler [run] are collected -in a `Context` (defined in [Contexts]). - -Most methods in the compiler depend on an implicit anonymous `Context` parameter, -and a typical definition looks like the following: -```scala -import dotty.tools.dotc.Contexts.{Context, ctx} - -def doFoo(using Context): Unit = - val current = ctx.run // access the Context parameter with `ctx` -``` - -## Memory Leaks -> **Careful:** Contexts can be heavy so beware of memory leaks - -It is good practice to ensure that implicit contexts are not -captured in closures or other long-lived objects, in order to avoid space leaks -in the case where a closure can survive several compiler runs (e.g. a -lazy completer for a library class that is never required). In that case, the -convention is that the `Context` be an explicit parameter, to track its usage. - -## Context Properties - -| Context property | description | -|-------------------|----------------------------------------| -| `compilationUnit` | current compilation unit | -| `phase` | current phase | -| `run` | current run | -| `period` | current period | -| `settings` | the config passed to the compiler | -| `reporter` | operations for logging errors/warnings | -| `definitions` | the standard built in definitions | -| `platform` | operations for the underlying platform | -| `tree` | current tree | -| `scope` | current scope | -| `typer` | current typer | -| `owner` | current owner symbol | -| `outer` | outer Context | -| `mode` | type checking mode | -| `typerState` | | -| `searchHistory` | | -| `implicits` | | -| ... | and so on | - - -[Contexts]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Contexts.scala -[run]: {% link _overviews/scala3-contribution/arch-lifecycle.md %}#runs +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/context.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-intro.md b/_overviews/scala3-contribution/arch-intro.md index 91db84fc23..8b306a4e5c 100644 --- a/_overviews/scala3-contribution/arch-intro.md +++ b/_overviews/scala3-contribution/arch-intro.md @@ -1,18 +1,5 @@ --- title: High Level Architecture -type: chapter description: This page introduces the high level architecture of the Scala 3 compiler. -num: 12 -previous-page: procedures-checklist -next-page: arch-lifecycle ---- - -This chapter of the guide describes the architecture and concepts of `dotc`, -the Scala 3 compiler, including answers to questions such as: -- "What are the transformations that happen to my code?" -- "How do I run a compiler programatically?" -- "What are symbols, denotations, names and types?" -- "What is a compiler phase?" -- "What is the compiler Context?" - -and many more. +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/index.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-lifecycle.md b/_overviews/scala3-contribution/arch-lifecycle.md index 9fe58e7a46..917e5a7824 100644 --- a/_overviews/scala3-contribution/arch-lifecycle.md +++ b/_overviews/scala3-contribution/arch-lifecycle.md @@ -1,96 +1,5 @@ --- title: Compiler Overview -type: section description: This page describes the lifecycle for the Scala 3 compiler. -num: 13 -previous-page: arch-intro -next-page: arch-context ---- - -At a high level, `dotc` is an interactive compiler (see [what is a compiler?][whats-a-compiler]), -and can be invoked frequently, for example to answer questions for an IDE, provide REPL completions, -or to manage incremental builds and more. Each of these use cases requires a customised -workflow, but sharing a common core. - -## Introducing the Compiler's Lifecycle - -#### Core -Customisation is provided by extending the [Compiler] class, which maintains an ordered -list of [phases][Phases], and how to [run][Run] them. Each interaction with a compiler -creates a new run, which is a complete iteration of the compiler's phases over a list -of input sources. Each run has the capability to create new definitions or -invalidate older ones, and `dotc` can [track these changes over time][time]. - -#### Runs -During a run, the input sources are converted to [compilation units][CompilationUnit] (i.e. the abstraction of -compiler state associated with each input source); then iteratively: a single phase is applied to -every compilation unit before progressing to the next phase. - -#### Phases -A phase is an abstract transformation over a compilation unit, it is usually responsible -for transforming the trees and types representing the code of a source file. Some phases of -the compiler are: -- `parser`, which converts text that matches Scala's - [syntax] into abstract syntax trees, ASTs -- `typer`, which checks that trees conform to expected types -- `erasure`, which retypes a more simplified program into one that has the same types as the JVM. -- `genBCode`, the JVM backend, which converts erased compiler trees into Java bytecode format. - -[You can read more about phases here][phase-categories]. - -#### Drivers - -The core compiler also requires a lot of state to be initialised before use, such as [settings][ScalaSettings] -and the [Context][contexts]. For convenience, the [Driver] class contains high level functions for -configuring the compiler and invoking it programatically. The object [Main] inherits from `Driver` -and is invoked by the `scalac` script. - -## Code Structure - -The code of the compiler is found in the package [dotty.tools], -containing the following sub-packages: -```scala -tools // contains helpers and the `scala` generic runner -├── backend // Compiler backends (currently JVM and JS) -├── dotc // The main compiler, with subpackages: -│ ├── ast // Abstract syntax trees -│   ├── classpath -│   ├── config // Compiler configuration, settings, platform specific definitions. -│   ├── core // Core data structures and operations, with specific subpackages for: -│   │   ├── classfile // Reading of Java classfiles into core data structures -│   │   ├── tasty // Reading and writing of TASTY files to/from core data structures -│   │   └── unpickleScala2 // Reading of Scala2 symbol information into core data structures -│   ├── decompiler // pretty printing TASTY as code -│   ├── fromtasty // driver for recompilation from TASTY -│   ├── interactive // presentation compiler and code completions -│   ├── parsing // Scanner and parser -│   ├── plugins // compile plugin definitions -│   ├── printing // Pretty-printing trees, types and other data -│   ├── profile // internals for profiling the compiler -│   ├── quoted // internals for quoted reflection -│   ├── reporting // Reporting of error messages, warnings and other info. -│   ├── rewrites // Helpers for rewriting Scala 2's constructs into Scala 3's. -│   ├── sbt // Helpers for communicating with the Zinc compiler. -│   ├── semanticdb // Helpers for exporting semanticdb from trees. -│   ├── transform // Miniphases and helpers for tree transformations. -│   ├── typer // Type-checking -│   └── util // General purpose utility classes and modules. -├── io // Helper modules for file access and classpath handling. -├── repl // REPL driver and interaction with the terminal -├── runner // helpers for the `scala` generic runner script -└── scripting // scala runner for the -script argument -``` - -[whats-a-compiler]: {% link _overviews/scala3-contribution/contribution-intro.md %}#what-is-a-compiler -[Phases]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Phases.scala -[CompilationUnit]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/CompilationUnit.scala -[time]: {% link _overviews/scala3-contribution/arch-time.md %} -[dotty.tools]: https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools -[ScalaSettings]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala -[phase-categories]: {% link _overviews/scala3-contribution/arch-phases.md %}#phase-categories -[syntax]: {% link _scala3-reference/syntax.md %} -[Main]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Main.scala -[Driver]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Driver.scala -[Compiler]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Compiler.scala -[Run]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Run.scala -[contexts]: {% link _overviews/scala3-contribution/arch-context.md %} +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/lifecycle.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-phases.md b/_overviews/scala3-contribution/arch-phases.md index 235148fc5e..25db11e6a3 100644 --- a/_overviews/scala3-contribution/arch-phases.md +++ b/_overviews/scala3-contribution/arch-phases.md @@ -1,113 +1,5 @@ --- title: Compiler Phases -type: section description: This page describes the phases for the Scala 3 compiler. -num: 15 -previous-page: arch-context -next-page: arch-types ---- - -As described in the [compiler overview][lifecycle], `dotc` is divided into a list of [phases][Phase], -specified in the [Compiler] class. - -#### Printing the phases of the Compiler - -a flattened list of all the phases can be displayed by invoking -the compiler with the `-Xshow-phases` flag: -``` -$ scalac -Xshow-phases -``` - -## Phase Groups - -In class [Compiler] you can access the list of phases with the method `phases`: - -```scala -def phases: List[List[Phase]] = - frontendPhases ::: picklerPhases ::: transformPhases ::: backendPhases -``` - -You can see that phases are actually grouped into sublists, given by the signature -`List[List[Phase]]`; that is, each sublist forms a phase group that is then *fused* into a -single tree traversal when a [Run] is executed. - -Phase fusion allows each phase of a group to be small and modular, -(each performing a single function), while reducing the number of tree traversals -and increasing performance. - -Phases are able to be grouped together if they inherit from [MiniPhase]. - -## Phase Categories - -Phases fall into four categories, allowing customisation by sub-classes of [Compiler]: - -### `frontendPhases` -In the main compiler these include [parser], [typer], [posttyper], -[prepjsinterop] and phases for producing SemanticDB and communicating with the -incremental compiler Zinc. -The [parser] reads source programs and generates untyped abstract syntax trees, which -in [typer] are then typechecked and transformed into typed abstract syntax trees. -Following is [posttyper], performing checks and cleanups that require a fully typed program. -In particular, it -- creates super accessors representing `super` calls in traits -- creates implementations of compiler-implemented methods, -such as `equals` and `hashCode` for case classes. -- marks [compilation units][CompilationUnit] that require inline expansion, or quote pickling -- simplifies trees of erased definitions -- checks variance of type parameters -- mark parameters passed unchanged from subclass to superclass for later pruning. - -### `picklerPhases` -These phases start with [pickler], which serializes typed trees -produced by the `frontendPhases` into TASTy format. Following is [inlining], -which expand calls to inline methods, and [postInlining] providing implementations -of the [Mirror] framework for inlined calls. -Finally are [staging], which ensures that quotes conform to the -[Phase Consistency Principle (PCP)][PCP], and [pickleQuotes] which converts quoted -trees to embedded TASTy strings. - -### `transformPhases` -These phases are concerned with tranformation into lower-level forms -suitable for the runtime system, with two sub-groupings: -- High-level transformations: All phases from [firstTransform] to [erasure]. - Most of these phases transform syntax trees, expanding high-level constructs - to more primitive ones. - - An important transform phase is [patternMatcher], which converts match - trees and patterns into lower level forms, as well as checking the - exhaustivity of sealed types, and unreachability of pattern cases. - - Some phases perform further checks on more primitive trees, - e.g. [refchecks] verifies that no abstract methods exist in concrete classes, - and [initChecker] checks that fields are not used before initialisation. - - The last phase in the group, [erasure] translates all - types into types supported directly by the JVM. To do this, it performs - another type checking pass, but using the rules of the JVM's type system - instead of Scala's. -- Low-level transformations: All phases from `ElimErasedValueType` to - `CollectSuperCalls`. These further transform trees until they are essentially a - structured version of Java bytecode. - -### `backendPhases` -These map the transformed trees to Java classfiles or SJSIR files. - -[lifecycle]: {% link _overviews/scala3-contribution/arch-lifecycle.md %}#phases -[CompilationUnit]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/CompilationUnit.scala -[Compiler]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Compiler.scala -[Phase]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Phases.scala -[MiniPhase]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala -[Run]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Run.scala -[parser]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/parsing/ParserPhase.scala -[typer]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala -[posttyper]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/PostTyper.scala -[prepjsinterop]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala -[pickler]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Pickler.scala -[inlining]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Inlining.scala -[postInlining]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/PostInlining.scala -[staging]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Staging.scala -[pickleQuotes]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala -[refchecks]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/RefChecks.scala -[initChecker]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/init/Checker.scala -[firstTransform]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala -[patternMatcher]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala -[erasure]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Erasure.scala -[Mirror]: https://github.com/lampepfl/dotty/blob/master/library/src/scala/deriving/Mirror.scala -[PCP]: {% link _scala3-reference/metaprogramming/macros.md %}#the-phase-consistency-principle +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/phases.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-symbols.md b/_overviews/scala3-contribution/arch-symbols.md index f81599fbc1..5ec3408b51 100644 --- a/_overviews/scala3-contribution/arch-symbols.md +++ b/_overviews/scala3-contribution/arch-symbols.md @@ -1,76 +1,5 @@ --- title: Symbols -type: section description: This page describes symbols in the Scala 3 compiler. -num: 18 -previous-page: arch-time -next-page: ---- - -As discussed previously, `dotc` [maintains time-indexed views][arch-time] of various -compiler artifacts. The following sections discuss how they are managed in the compiler. - -## Symbols - -Defined in [Symbols], a `Symbol` is a unique identifier for a definition (e.g. a method, -type, or field). A `ClassSymbol` extends `Symbol` and represents either a -`class`, or a `trait`, or an `object`. A `Symbol` can even refer to non-Scala entities, -such as from the Java standard library. - -## Definitions are Dynamic - -Traditionally, compilers store context-dependent data in a _symbol table_. -Where a symbol then is the central reference to address context-dependent data. -`dotc` instead uses a phase-indexed function (known as -a [Denotation][Denotations]) to compute views of definitions across phases, -as many of attributes associated with definitions are phase-dependent. For example: -- types are gradually simplified by several phases, -- owners change in [lambdaLift] (local methods are lifted to an enclosing class) - and [flatten] (when inner classes are moved to the top level) -- Names are changed when private members need to be accessed from outside - their class (for instance from a nested class or a class implementing - a trait). - -Additionally, symbols are not suitable to be used as a reference to -a definition in another [compilation unit][CompilationUnit]. -In the context of incremental compilation, a symbol from -an external compilation unit may be deleted or changed, making the reference -stale. To counter this, `dotc` types trees of cross-module references with either -a `TermRef` or `TypeRef`. A reference type contains a prefix type and a name. -The denotation that the type refers to is established dynamically based on -these fields. - -## Denotations - -On its own a `Symbol` has no structure. Its semantic meaning is given by being associated -with a [Denotation][Denotations]. - -A denotation is the result of resolving a name during a given period, containing the information -describing some entity (either a term or type), indexed by phase. Denotations usually have a -reference to a selected symbol, but not always, for example if the denotation is overloaded, -i.e. a `MultiDenotation`. - -### SymDenotations -All definition symbols will contain a `SymDenotation`. The denotation, in turn, contains: -- a reverse link to the source symbol -- a reference to the enclosing symbol that defined the source symbol: - - for a local variable, the enclosing method - - for a field or class, the enclosing class -- a set of [flags], describing the definition (e.g. whether it's a trait or mutable). -- the type of the definition (through the `info` method) -- a [signature][Signature1], which uniquely identifies overloaded methods (or else `NotAMethod`). -- and more. - -A class symbol will instead be associated with a `ClassDenotation`, which extends `SymDenotation` -with some additional fields specific for classes. - -[Signature1]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/core/Signature.scala#L9-L33 -[Symbols]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Symbols.scala -[lifecycle]: {% link _overviews/scala3-contribution/arch-lifecycle.md %}#introducing-the-compilers-lifecycle -[arch-time]: {% link _overviews/scala3-contribution/arch-time.md %} -[flatten]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Flatten.scala -[lambdaLift]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala -[CompilationUnit]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/CompilationUnit.scala -[Denotations]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Denotations.scala -[SymDenotations]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/SymDenotations.scala -[flags]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Flags.scala +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/symbols.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-time.md b/_overviews/scala3-contribution/arch-time.md index 13e029e742..a56fed21a5 100644 --- a/_overviews/scala3-contribution/arch-time.md +++ b/_overviews/scala3-contribution/arch-time.md @@ -1,76 +1,5 @@ --- title: Time in the Compiler -type: section description: This page describes the concepts of time in the Scala 3 compiler. -num: 17 -previous-page: arch-types -next-page: arch-symbols ---- - -In the [compiler overview][lifecycle] section, we saw that `dotc` is an interactive compiler, -and so can answer questions about entities as they come into existance and change throughout time, -for example: -- which new definitions were added in a REPL session? -- which definitions were replaced in an incremental build? -- how are definitions simplified as they are adapted to the runtime system? - -## Hours, Minutes, and Periods - -For the compiler to be able to resolve the above temporal questions, and more, it maintains -a concept of time. Additionally, because interactions are frequent, it is important to -persist knowledge of entities between interactions, allowing the compiler to remain performant. -Knowing about time allows the compiler to efficiently mark entities as being outdated. - -Conceptually, `dotc` works like a clock, where its minutes are represented by [phases], -and its hours by [runs]. Like a clock, each run passes once each of its phases have completed -sequentially, and then a new run can begin. Phases are further grouped into [periods], where -during a period certain entities of the compiler remain stable. - -## Time Travel - -During a run, each phase can rewrite the world as the compiler sees it, for example: -- to transform trees, -- to gradually simplify type from Scala types to JVM types, -- to move definitions out of inner scopes to outer ones, fitting the JVM's model, -- and so on. - -Because definitions can [change over time][dynamic], various artifacts associated with them -are stored non-destructively, and views of the definition created earlier, or later -in the compiler can be accessed by using the `atPhase` method, defined in [Contexts]. - -As an example, assume the following definitions are available in a [Context]: -```scala -class Box { type X } - -def foo(b: Box)(x: b.X): List[b.X] = List(x) -``` - -You can compare the type of definition `foo` after the [typer] phase and after the [erasure] phase -by using `atPhase`: -```scala -import dotty.tools.dotc.core.Contexts.{Context, atPhase} -import dotty.tools.dotc.core.Phases.{typerPhase, erasurePhase} -import dotty.tools.dotc.core.Decorators.i - -given Context = … - -val fooDef: Symbol = … // `def foo(b: Box)(x: b.X): List[b.X]` - -println(i"$fooDef after typer => ${atPhase(typerPhase.next)(fooDef.info)}") -println(i"$fooDef after erasure => ${atPhase(erasurePhase.next)(fooDef.info)}") -``` -and see the following output: -``` -method foo after typer => (b: Box)(x: b.X): scala.collection.immutable.List[b.X] -method foo after erasure => (b: Box, x: Object): scala.collection.immutable.List -``` - -[runs]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/Run.scala -[periods]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/core/Periods.scala -[lifecycle]: {% link _overviews/scala3-contribution/arch-lifecycle.md %} -[phases]: {% link _overviews/scala3-contribution/arch-phases.md %} -[dynamic]: {% link _overviews/scala3-contribution/arch-symbols.md %}#definitions-are-dynamic -[Contexts]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Contexts.scala -[Context]: {% link _overviews/scala3-contribution/arch-context.md %} -[typer]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala -[erasure]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/Erasure.scala +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/time.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/arch-types.md b/_overviews/scala3-contribution/arch-types.md index 40ba8a5495..cadcee16f2 100644 --- a/_overviews/scala3-contribution/arch-types.md +++ b/_overviews/scala3-contribution/arch-types.md @@ -1,152 +1,5 @@ --- title: Compiler Types -type: section description: This page discusses the representation of types in the compiler -num: 16 -previous-page: arch-phases -next-page: arch-time ---- - -## Common Types and their Representation - -Type representations in `dotc` derive from the class `dotty.tools.dotc.core.Types.Type`, -defined in [Types.scala]. The `toString` method on `Type` will display types in a -format corresponding to the backing data structure, e.g. `ExprType(...)` -corresponds to `class ExprType`, defined in [Types.scala]. - -> You can inspect the representation of any type using the [dotty.tools.printTypes][DottyTypeStealer] -> script, its usage and integration into your debugging workflow is [described here][inspecting-types]. - -### Types of Definitions - -The following table describes definitions in Scala 3, followed by the `dotc` representation -of two types - a reference to the definition, and then its underlying type. - -**Note**: in the following types, `p` refers to the self-type of the enclosing scope of -the definition, or `NoPrefix` for local definitions and parameters. - -Definition | Reference | Underlying Type -------------------------|-----------------|------------------------- -`type Z >: A <: B` | `TypeRef(p, Z)` | `RealTypeBounds(A, B)` -`type Z = A` | `TypeRef(p, Z)` | `TypeAlias(A)` -`type F[T] = T match …` | `TypeRef(p, F)` | `MatchAlias([T] =>> T match …)` -`class C` | `TypeRef(p, C)` | `ClassInfo(p, C, …)` -`trait T` | `TypeRef(p, T)` | `ClassInfo(p, T, …)` -`object o` | `TermRef(p, o)` | `TypeRef(p, o$)` where `o$` is a class -`def f(x: A): x.type` | `TermRef(p, f)` | `MethodType(x, A, TermParamRef(x))` -`def f[T <: A]: T` | `TermRef(p, f)` | `PolyType(T, <: A, TypeParamRef(T))` -`def f: A` | `TermRef(p, f)` | `ExprType(A)` -`(x: => A)` | `TermRef(p, x)` | `ExprType(A)` where `x` is a parameter -`val x: A` | `TermRef(p, x)` | `A` - -### Types of Values - -The following types may appear in part of the type of an expression: - -Type | Representation ---------------------------|------------------------------ -`x.y.type` | `TermRef(x, y)` -`X#T` | `TypeRef(X, T)` -`x.y.T` and `x.y.type#T` | `TypeRef(TermRef(x, y), T)` -`this.type` | `ThisType(C)` where `C` is the enclosing class -`"hello"` | `ConstantType(Constant("hello"))` -`A & B` | `AndType(A, B)` -`A | B` | `OrType(A, B)` -`A @foo` | `AnnotatedType(A, @foo)` -`[T <: A] =>> T` | `HKTypeLambda(T, <: A, TypeParamRef(T))` -`x.C[A, B]` | `AppliedType(x.C, List(A, B))` -`C { type A = T }` | `RefinedType(C, A, T)`<br/>when `T` is not a member of `C` -`C { type X = Y }` | `RecType(RefinedType(C, X, z.Y))`<br/>when `X` and `Y` are members of `C`<br/>and `z` is a `RecThis` over the enclosing `RecType` -`super.x.type` | `TermRef(SuperType(…), x)` - -## Constructing Types - -### Method Definition Types - -You can see above that method definitions can have an underlying type of -either `PolyType`, `MethodType`, or `ExprType`. `PolyType` and `MethodType` -may be mixed recursively however, and either can appear as the result type of the other. - -Take this example as given: - -```scala -def f[A, B <: Seq[A]](x: A, y: B): Unit -``` -it can be constructed by the following code: - -```scala -import dotty.tools.dotc.core.Types.* -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.* - -given Context = … // contains the definitions of the compiler - -val f: Symbol = … // def f[A, B <: Seq[A]](x: A, y: B): Unit - -f.info = PolyType( - List("A".toTypeName, "B".toTypeName))( - pt => List( - TypeBounds(defn.NothingType, defn.AnyType), - TypeBounds(defn.NothingType, AppliedType(defn.SeqType, List(pt.newParamRef(0)))) - ), - pt => MethodType( - List("x".toTermName, "y".toTermName))( - mt => List(pt.newParamRef(0), pt.newParamRef(1)), - mt => defn.UnitType - ) -) -``` - -Note that `pt.newParamRef(0)` and `pt.newParamRef(1)` refers to the -type parameters `A` and `B` respectively. - -## Proxy Types and Ground Types -Types in `dotc` are divided into two semantic kinds: -- Ground Types (inheriting from either `CachedGroundType` or `UncachedGroundType`) -- Proxy Types (inheriting from `TypeProxy` via either `CachedProxyType` or `UncachedProxyType`) - -A Proxy Type is anything that can be considered to be an abstraction of another type, -which can be accessed by the `underlying` method of the `TypeProxy` class. It's dual, the -Ground Type has no meaningful underlying type, typically it is the type of method and class -definitions, but also union types and intersection types, along with utility types of the -compiler. - -Here's a diagram, serving as the mental model of the most important and distinct types available after the `typer` phase, derived from [dotty/tools/dotc/core/Types.scala][1]: - -``` -Type -+- proxy_type --+- NamedType --------+- TypeRef - | | \ - | +- SingletonType ----+- TermRef - | | +- ThisType - | | +- SuperType - | | +- ConstantType - | | +- TermParamRef - | | +- RecThis - | | +- SkolemType - | +- TypeParamRef - | +- RefinedOrRecType -+-- RefinedType - | | -+-- RecType - | +- AppliedType - | +- TypeBounds - | +- ExprType - | +- AnnotatedType - | +- TypeVar - | +- HKTypeLambda - | +- MatchType - | - +- ground_type -+- AndType - +- OrType - +- MethodOrPoly -----+-- PolyType - | +-- MethodType - +- ClassInfo - +- NoType - +- NoPrefix - +- ErrorType - +- WildcardType - -``` - -[Types.scala]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Types.scala -[DottyTypeStealer]: https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/DottyTypeStealer.scala -[inspecting-types]: {% link _overviews/scala3-contribution/procedures-inspection.md %}#inspecting-the-representation-of-types +redirect_to: https://dotty.epfl.ch/docs/contributing/architecture/types.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/contribution-intro.md b/_overviews/scala3-contribution/contribution-intro.md index 283f65cfb5..1708decf17 100644 --- a/_overviews/scala3-contribution/contribution-intro.md +++ b/_overviews/scala3-contribution/contribution-intro.md @@ -1,39 +1,5 @@ --- -title: About This Guide -type: chapter +title: Contribute to Scala 3 description: This page describes the format of the contribution guide for the Scala 3 compiler. -num: 1 -previous-page: -next-page: start-intro +redirect_to: https://dotty.epfl.ch/docs/contributing/index.html --- - -This guide is intended to give new contributors the knowledge they need to -become productive and fix issues or implement new features in Scala 3. It -also documents the inner workings of the Scala 3 compiler, `dotc`. - -### A Note on Stability - -Keep in mind that the code for `dotc` is subject to change, with no -guarantees of stability, so the ideas discussed in this guide may -fall out of date, please consider contributing to this guide -on [GitHub](https://github.com/scala/docs.scala-lang/tree/main/_overviews/scala3-contribution). - -### Get the Most from This Guide - -`dotc` is built with Scala 3, fully utilising its [new features](/scala3/new-in-scala3.html). -It is recommended that you first have some familiarity with Scala 3 -to get the most out of this guide. You can learn more in the [language reference](/scala3/reference/overview.html). - -Many code snippets in this guide make use of shell commands (a line beginning with `$`), and in this case -a `bash` compatible shell is assumed. You may have to look up how to translate commands to your shell. - -### What is a Compiler? - -A compiler is a program that takes as input text, representing a program in one language -and produces as output the same program, written in another programming language. - -#### The Scala Compiler - -As an example, `dotc` takes text input, verifies that it is a valid Scala program -and then produces as output the same program, but written in Java bytecode, and optionally -in SJSIR when producing Scala.js output. diff --git a/_overviews/scala3-contribution/procedures-areas.md b/_overviews/scala3-contribution/procedures-areas.md index 3d15216da4..74d593b4ac 100644 --- a/_overviews/scala3-contribution/procedures-areas.md +++ b/_overviews/scala3-contribution/procedures-areas.md @@ -1,74 +1,5 @@ --- title: Common Issue Locations -type: section description: This page describes common areas of issues around the Scala 3 compiler. -num: 7 -previous-page: procedures-navigation -next-page: procedures-inspection ---- - -Many issues are localised to small domains of the compiler and are self-contained, -here is a non-exhaustive list of such domains, and the files associated with them: - -### Pretty Printing of Types and Trees - -Objects in the compiler that inherit from [Showable] can be pretty printed. -The pretty-printing of objects is used in many places, from debug output, -to user-facing error messages and printing of trees after each phase. - -Look in [RefinedPrinter] (or its parent class [PlainPrinter]) for the implementation of pretty printing. - -### Content of Error Messages - -You can find the definitions of most error messages in [messages] (with IDs -defined in [ErrorMessageID]). If the message is not defined there, try the -`-Ydebug-error` compiler flag, which will print a stack trace leading to the -production of the error, and the contents of the message. - -### Compiler Generated Given Instances - -If the issue lies in given instances provided by the compiler, such as `scala.reflect.ClassTag`, -`scala.deriving.Mirror`, `scala.reflect.TypeTest`, `scala.CanEqual`, `scala.ValueOf`, -`scala.reflect.Manifest`, etc, look in [Synthesizer], which provides factories for -given instances. - -### Compiler Generated Methods - -Members can be generated for many classes, such as `equals` and `hashCode` -for case classes and value classes, and `ordinal` and `fromProduct` for Mirrors. -To change the implementation, see [SyntheticMembers]. - -### Code Completions -For suggestions to auto-complete method selections, see [Completion]. - -### Enum Desugaring -See [Desugar] and [DesugarEnums]. - -### Pattern Match Exhaustivity -See [Space]. - -### Metaprogramming - -#### Quotes Reflection -See the [quoted runtime package][quotes-impl]. - -#### Inline match -See [Inliner]. - -#### Compiletime Ops Types -See `tryCompiletimeConstantFold` in [Types]. - -[Showable]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/printing/Showable.scala -[PlainPrinter]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala -[RefinedPrinter]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala -[ErrorMessageID]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala -[messages]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/reporting/messages.scala -[Synthesizer]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala -[SyntheticMembers]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala -[quotes-impl]: https://github.com/lampepfl/dotty/tree/master/compiler/src/scala/quoted/runtime/impl -[Inliner]: https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools/dotc/typer/Inliner.scala -[Types]: https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools/dotc/core/Types.scala -[Completion]: https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools/dotc/interactive/Completion.scala -[DesugarEnums]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala -[Desugar]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/ast/Desugar.scala -[Space]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/areas.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-cheatsheet.md b/_overviews/scala3-contribution/procedures-cheatsheet.md index c3c114e97b..fdbf2a2435 100644 --- a/_overviews/scala3-contribution/procedures-cheatsheet.md +++ b/_overviews/scala3-contribution/procedures-cheatsheet.md @@ -1,35 +1,5 @@ --- title: Cheatsheets -type: section description: This page describes a cheatsheet for working with the Scala 3 compiler. -num: 4 -previous-page: procedures-intro -next-page: procedures-reproduce ---- - -This page is a quick-reference guide for common tasks while working with the compiler. -For more in-depth explanations, see the rest of this chapter. - -## sbt Commands - -The following commands can be run within `sbt` in the dotty directory: - -| Commands | -|-----------------------------------------------------------------------------------------------| -| `testCompilation` Run compilation tests on files that match the first argument. | -| `scala3/scalac` Run the compiler directly, with any current changes. | -| `scala3/scala` Run the main method of a given class name. | -| `repl` Start a REPL with the bootstrapped compiler. | -| `testOnly *CompilationTests -- *pos` Run test `pos` from the compilation test suite. | -| `scala3-compiler/Test/runMain dotty.tools.printTypes` Print types underlying representation | -| `scala3/scalac -print-tasty Foo.tasty` Print the TASTy of top-level class `Foo` | -| `scala3-bootstrapped/test` Run all tests for Scala 3. (Slow, recommended for CI only) | -| `scala3-bootstrapped/publishLocal` Build Scala 3 locally. (Use to debug a specific project) | - -## Shell Commands - -| Command | Description | -|--------------------------------------|------------------------------------------------------------------| -| `rm -rv *.tasty *.class out || true` | clean all compiled artifacts, from root dotty directory | - -<!-- Todo: add cheatsheet for compiler flags, and places to go in code for certain issues --> +redirect_to: https://dotty.epfl.ch/docs/contributing/cheatsheet.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-checklist.md b/_overviews/scala3-contribution/procedures-checklist.md index df194f99e9..6908332d2d 100644 --- a/_overviews/scala3-contribution/procedures-checklist.md +++ b/_overviews/scala3-contribution/procedures-checklist.md @@ -1,41 +1,5 @@ --- title: Pull Request Checklist -type: section description: This page describes a checklist before opening a Pull Request to the Scala 3 compiler. -num: 11 -previous-page: procedures-testing -next-page: arch-intro ---- - -Once you solved an issue, you likely want to see your change added to the [Scala 3 repo][lampepfl/dotty]. -To do that, you need to prepare a [pull request][pull-request] with your changes. We recommend you -follow these guidelines, [also consult the full requirements][full-list]: - -### 1. Sign the CLA - -Make sure you have signed the [Scala CLA][cla], if not, sign it. - -### 2: Is It Relevant? - -Before starting to work on a feature or a fix, it's good practice to ensure that: -1. There is a ticket for your work in the project's [issue tracker][issues]; -2. The ticket has been discussed and there is desire for it to be implemented by the -Scala 3 core maintainers. - -### 3: Add Tests -Add at least one test that replicates the problem in the issue, and that shows it is now resolved. - -You may of course add variations of the test code to try and eliminate edge cases. -[Become familiar with testing in Scala 3][testing]. - -### 4: Add Documentation -Please ensure that all code is documented to explain its use, even if only internal -changes are made. - - -[pull-request]: https://docs.github.com/en?query=pull+requests -[lampepfl/dotty]: https://github.com/lampepfl/dotty -[cla]: http://typesafe.com/contribute/cla/scala -[issues]: https://github.com/lampepfl/dotty/issues -[full-list]: https://github.com/lampepfl/dotty/blob/master/CONTRIBUTING.md -[testing]: {% link _overviews/scala3-contribution/procedures-testing.md %} +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/checklist.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-debugging.md b/_overviews/scala3-contribution/procedures-debugging.md new file mode 100644 index 0000000000..6fe158614d --- /dev/null +++ b/_overviews/scala3-contribution/procedures-debugging.md @@ -0,0 +1,5 @@ +--- +title: Debugging the Compiler +description: This page describes navigating around the Scala 3 compiler. +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/debugging.html +--- diff --git a/_overviews/scala3-contribution/procedures-efficiency.md b/_overviews/scala3-contribution/procedures-efficiency.md deleted file mode 100644 index 550a81926e..0000000000 --- a/_overviews/scala3-contribution/procedures-efficiency.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Improving Your Workflow -type: section -description: This page describes improving efficiency of debugging the Scala 3 compiler. -num: 9 -previous-page: procedures-inspection -next-page: procedures-testing ---- - -In the previous sections of this chapter, you saw some techniques for -working with the compiler. Some of these techniques can be used -repetitively, e.g.: - -- Navigating stack frames -- Printing variables in certain ways -- Instrumenting variable definitions with tracers - -The above procedures often take a lot of time when done manually, reducing productivity: -as the cost (in terms of time and effort) is high, you may avoid attempting to do so, -and possibly miss valuable information. - -If you're doing those things really frequently, it is recommended to script your editor -to reduce the number of steps. E.g. navigating to the definition of a stack frame -part when you click it, or instrumenting variables for printing. - -An example of how it is done for Sublime Text 3 is [here](https://github.com/anatoliykmetyuk/scala-debug-sublime). - -True, it takes some time to script your editor, but if you spend a lot of time with issues, it pays off. diff --git a/_overviews/scala3-contribution/procedures-inspection.md b/_overviews/scala3-contribution/procedures-inspection.md index 8121065796..40ec4e2f92 100644 --- a/_overviews/scala3-contribution/procedures-inspection.md +++ b/_overviews/scala3-contribution/procedures-inspection.md @@ -1,186 +1,5 @@ --- title: How to Inspect Values -type: section description: This page describes inspecting semantic values in the Scala 3 compiler. -num: 8 -previous-page: procedures-areas -next-page: procedures-efficiency ---- - -In this section, you will find out how to debug the contents of certain objects -while the compiler is running, and inspect produced artifacts of the compiler. - -## Inspecting variables in-place - -Frequently you will need to inspect the content of a particular variable. -Often, it is sufficient to use `println`. - -When printing a variable, it's always a good idea to call `show` on that variable: `println(x.show)`. -Many objects of the compiler define `show`, returning a human-readable string. -e.g. if called on a tree, the output will be the tree's representation as source code, rather than -the underlying raw data. - -Sometimes you need to print flags. Flags are metadata attached to [symbols] containing information such as whether a -class is abstract, comes from Java, what modifiers a variable has (private, protected etc) and so on. -Flags are stored in a single `Long` value, each bit of which represents whether a particular flag is set. - -To print flags, you can use the `flagsString` method, e.g. `println(x.flagsString)`. - -## Pretty Printing with a String Interpolator - -You can also pretty print objects with string interpolators, -these default to call `.show` when possible, avoiding boilerplate -and also helping format error messages. - -Import them with the following: - -```scala -import dotty.tools.dotc.core.Decorators.* -``` - -Here is a table of explanations for their use: - -| Usage | Description | -|--------|-----------------------------------| -|`i""` | General purpose string formatting. It calls `.show` on objects <br/> mixing in Showable, `String.valueOf` otherwise | -|`em""` | Formatting for error messages: Like `i` but suppress <br/>follow-on, error messages after the first one if some <br/>of their arguments are "non-sensical". | -|`ex""` | Formatting with added explanations: Like `em`, but add <br/>explanations to give more info about type variables<br/>and to disambiguate where needed. | - - -## Obtaining debug output from the compiler - -As explained in [navigation], we can debug the code being generated as it is transformed -through the compiler. As well as plain tree output, there are many compiler options that -add extra debug information to trees when compiling a file; you can find the full list -in [ScalaSettings]. - -## Stopping the compiler early -Sometimes you may want to stop the compiler after a certain phase, for example to prevent -knock-on errors from occurring from a bug in an earlier phase. Use the flag -`-Ystop-after:<phase-name>` to prevent any phases executing afterwards. - -> e.g. `-Xprint:<phase>` where `phase` is a miniphase, will print after -> the whole phase group is complete, which may be several miniphases after `phase`. -> Instead you can use `-Ystop-after:<phase> -Xprint:<phase>` to stop -> immediately after the miniphase and see the trees that you intended. - -## Printing TASTy of a Class - -If you are working on an issue related to TASTy, it is good to know how to inspect -the contents of a TASTy file, produced from compilation of Scala files. - -The next example uses an [issue directory][reproduce] to compile a class and print its TASTy. -In the directory, you should create a file `tasty/Foo.scala` (with contents of `class Foo`), -and create a file `tasty/launch.iss` with the following contents: - -``` -$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir. - -scala3/scalac -d $here/out $here/Foo.scala - -scala3/scalac -print-tasty $here/out/Foo.tasty -``` - -With sbt command `issue tasty` you will see output such as the following: - -``` --------------------------------------------------------------------------------- -local/foo/out/Foo.tasty --------------------------------------------------------------------------------- -Names: - 0: ASTs - 1: <empty> - 2: Foo - 3: <init> -... -``` -and so on. - -## Inspecting The Representation of Types - -> [learn more about types][types] in `dotc`. - -If you are curious about the representation of a type, say `[T] =>> List[T]`, -you can use a helper program [dotty.tools.printTypes][DottyTypeStealer], -it prints the internal representation of types, along with their class. It can be -invoked from the sbt shell with three arguments as follows: -```bash -sbt:scala3> scala3-compiler/Test/runMain - dotty.tools.printTypes - <source> - <kind> - <typeStrings*> -``` - -- The first argument, `source`, is an arbitrary string that introduces some Scala definitions. -It may be the empty string `""`. -- The second argument, `kind`, determines the format of the following arguments, -accepting one of the following options: - - `rhs` - accept return types of definitions - - `class` - accept signatures for classes - - `method` - accept signatures for methods - - `type` - accept signatures for type definitions - - The empty string `""`, in which case `rhs` will be assumed. -- The remaining arguments are type signature strings, accepted in the format determined by -`kind`, and collected into a sequence `typeStrings`. Signatures are the part of a definition -that comes after its name, (or a simple type in the case of `rhs`) and may reference -definitions introduced by the `source` argument. - -Each one of `typeStrings` is then printed, displaying their internal structure, alongside their class. - -### Examples - -Here, given a previously defined `class Box { type X }`, you can inspect the return type `Box#X`: -```bash -sbt:scala3> scala3-compiler/Test/runMain -> dotty.tools.printTypes -> "class Box { type X }" -> "rhs" -> "Box#X" -[info] running (fork) dotty.tools.printTypes "class Box { type X }" rhs Box#X -TypeRef(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),class Box),type X) [class dotty.tools.dotc.core.Types$CachedTypeRef] -``` - -Here are some other examples you can try: -- `...printTypes "" "class" "[T] extends Seq[T] {}"` -- `...printTypes "" "method" "(x: Int): x.type"` -- `...printTypes "" "type" "<: Int" "= [T] =>> List[T]"` - -### Don't just print: extracting further information - -`dotty.tools.printTypes` is useful to to see the representation -of a type at a glance, but sometimes you want to extract more. Instead, you can use the -method `dotty.tools.DottyTypeStealer.stealType`. With the same inputs as `printTypes`, -it returns both a `Context` containing the definitions passed, along with the list of types. - -As a worked example let's create a test case to verify the structure of `Box#X` that you saw earlier: -```scala -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Types.* - -import org.junit.Test - -import dotty.tools.DottyTypeStealer, DottyTypeStealer.Kind - -class StealBox: - - @Test - def stealBox: Unit = - val (ictx, List(rhs)) = - DottyTypeStealer.stealType("class Box { type X }", Kind.rhs, "Box#X") - - given Context = ictx - - rhs match - case X @ TypeRef(Box @ TypeRef(ThisType(empty), _), _) => - assert(Box.name.toString == "Box") - assert(X.name.toString == "X") - assert(empty.name.toString == "<empty>") -``` - -[DottyTypeStealer]: https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/DottyTypeStealer.scala -[types]: {% link _overviews/scala3-contribution/arch-types.md %} -[ScalaSettings]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala -[symbols]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/SymDenotations.scala -[reproduce]: {% link _overviews/scala3-contribution/procedures-reproduce.md %}#dotty-issue-workspace -[navigation]: {% link _overviews/scala3-contribution/procedures-navigation.md %}#what-phase-generated-a-particular-tree +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/inspection.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-intro.md b/_overviews/scala3-contribution/procedures-intro.md index 8e23a97d4b..2cb292caf4 100644 --- a/_overviews/scala3-contribution/procedures-intro.md +++ b/_overviews/scala3-contribution/procedures-intro.md @@ -1,26 +1,5 @@ --- title: Contributing to Scala 3 -type: chapter description: This page introduces the compiler procedures for the Scala 3 compiler. -num: 3 -previous-page: start-intro -next-page: procedures-cheatsheet ---- - -Thank you for wanting to contribute to Scala 3! - -This chapter introduces instructions -on how to do basic tasks when fixing compiler issues. The tasks include: -- how to [reproduce an issue][reproduce] -- how to [navigate to where the issue manifests][navigation] itself in the compiler -- find shortcuts to [common issue areas][areas] of the compiler -- how to [inspect various values][inspection] encountered in the compiler -- how to [create a test][testing] for your fix. - -You can find the instructions of how to do the above in the following sections of this guide. - -[reproduce]: {% link _overviews/scala3-contribution/procedures-reproduce.md %} -[navigation]: {% link _overviews/scala3-contribution/procedures-navigation.md %} -[areas]: {% link _overviews/scala3-contribution/procedures-areas.md %} -[inspection]: {% link _overviews/scala3-contribution/procedures-inspection.md %} -[testing]: {% link _overviews/scala3-contribution/procedures-testing.md %} +redirect_to: https://dotty.epfl.ch/docs/contributing/procedures/index.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-navigation.md b/_overviews/scala3-contribution/procedures-navigation.md index 3a7cd43b63..a0e869970c 100644 --- a/_overviews/scala3-contribution/procedures-navigation.md +++ b/_overviews/scala3-contribution/procedures-navigation.md @@ -1,123 +1,5 @@ --- title: Finding the Cause of an Issue -type: section description: This page describes navigating around the Scala 3 compiler. -num: 6 -previous-page: procedures-reproduce -next-page: procedures-areas +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/cause.html --- - -In this section, you will be able to answer questions such as: -- where does an error happen in a codebase? -- when during compilation was a particular tree introduced? -- where is a particular object created? -- where is a particular value assigned to a variable? - -> You may be able to quickly find the source responsible for an issue by consulting [common issue locations][areas] - -## What phase generated a particular tree? - -As described in the [compiler lifecycle][lifecycle], each phase transforms the trees -and types that represent your code in a certain way. - -To print the code as it is transformed through the compiler, use the compiler flag `-Xprint:all`. -After each phase group is completed, you will see the resulting trees representing the code. - -> It is recommended to test `-Xprint:all` on a single, small file, otherwise a lot of unnecessary -> output will be generated. - -### Trace a Tree Creation Site - -When you see a problematic tree appear after a certain phase group, you know to isolate the rest of -your search to the code of that phase. For example if you found a problematic tree after phase -`posttyper`, the problem most likely appears in the code of [PostTyper]. We can trace the exact point -the tree was generated by looking for its unique ID, and then generating a stack trace at its creation: - -1. Run the compiler with `-Xprint:posttyper` and `-Yshow-tree-ids` flags. - This will only print the trees of the `posttyper` phase. This time you should see the tree - in question be printed alongside its ID. You'll see something like `println#223("Hello World"#37)`. -2. Copy the ID of the desired tree. -3. Run the compiler with `-Ydebug-tree-with-id <tree-id>` flag. The compiler will print a stack trace - pointing to the creation site of the tree with the provided ID. - -### Enhanced Tree Printing - -As seen above `-Xprint:<phase>` can be enhanced with further configuration flags, found in -[ScalaSettings]. For example, you can additionally print the type of a tree with `-Xprint-types`. - -## Increasing Logging Output -Once you have identified the phase that generated a certain tree, you can then increase -logging in that phase, to try and detect erroneous states: - -- general logging within a phase can be enabled with the `-Ylog` compiler flag, such as - - `-Ylog:<phase1>,<phase2>,...` for individual phases - - `-Ylog:all` for all phases. -- Additionally, various parts of the compiler have specialised logging objects, defined in [Printers]. - Change any of the printers of interest from `noPrinter` to `default` and increase output specialised - to that domain. - -## Navigating to Where an Error is Generated - -The compiler issues user facing errors for code that is not valid, such as the type mismatch -of assigning an `Int` to a `Boolean` value. Sometimes these errors do not match what is expected, which could be a bug. - -To discover why such a *spurious* error is generated, you can trace the code that generated the error by -adding the `-Ydebug-error` compiler flag, e.g. `scala3/scalac -Ydebug-error Test.scala`. -This flag forces a stack trace to be printed each time an error happens, from the site where it occurred. - -Analysing the trace will give you a clue about the objects involved in producing the error. -For example, you can add some debug statements before the error is issued to discover -the state of the compiler. [See some useful ways to debug values.][inspect] - -### Where was a particular object created? - -If you navigate to the site of the error, and discover a problematic object, you will want to know -why it exists in such a state, as it could be the cause of the error. You can discover the -creation site of that object to understand the logic that created it. - -You can do this by injecting a *tracer* into the class of an instance in question. -A tracer is the following variable: -```scala -val tracer = Thread.currentThread.getStackTrace.mkString("\n") -``` -When placed as a member definition at a class, it will contain a stack trace pointing at where exactly -its particular instance was created. - -Once you've injected a tracer into a class, you can `println` that tracer from the error site or -other site you've found the object in question. - -#### Procedure - -1. Determine the type of the object in question. You can use one of the following techniques to do so: - - Use an IDE to get the type of an expression, or save the expression to a `val` - and see its inferred type. - - Use `println` to print the object or use `getClass` on that object. -2. Locate the type definition for the type of that object. -3. Add a field `val tracer = Thread.currentThread.getStackTrace.mkString("\n")` to that type definition. -4. `println(x.tracer)` (where `x` is the name of the object in question) from the original site where you - encountered the object. This will give you the stack trace pointing to the place where the - constructor of that object was invoked. - -### Where was a particular value assigned to a variable? - -Say you have a certain [type][types] assigned to a [Denotation] and you would like to know why it has that -specific type. The type of a denotation is defined by `var myInfo: Type`, and can be assigned multiple times. -In this case, knowing the creation site of that `Type`, as described above, is not useful; instead, you need to -know the *assignment* (not *creation*) site. - -This is done similarly to how you trace the creation site. Conceptually, you need to create a proxy for that variable that will log every write operation to it. Practically, if you are trying to trace the assignments to a variable `myInfo` of type `Type`, first, rename it to `myInfo_debug`. Then, insert the following at the same level as that variable: - -```scala -var tracer = "", -def myInfo: Type = myInfo_debug, -def myInfo_=(x: Type) = { tracer = Thread.currentThread.getStackTrace.mkString("\n"); myInfo_debug = x } -``` - -[Printers]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/config/Printers.scala -[areas]: {% link _overviews/scala3-contribution/procedures-areas.md %} -[Denotation]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Denotations.scala -[types]: {% link _overviews/scala3-contribution/arch-types.md %} -[lifecycle]: {% link _overviews/scala3-contribution/arch-lifecycle.md %}#phases -[PostTyper]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/PostTyper.scala -[ScalaSettings]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala -[inspect]: {% link _overviews/scala3-contribution/procedures-inspection.md %} diff --git a/_overviews/scala3-contribution/procedures-reproduce.md b/_overviews/scala3-contribution/procedures-reproduce.md index d567adac23..aa31ecedde 100644 --- a/_overviews/scala3-contribution/procedures-reproduce.md +++ b/_overviews/scala3-contribution/procedures-reproduce.md @@ -1,136 +1,5 @@ --- title: Reproducing an Issue -type: section description: This page describes reproducing an issue in the Scala 3 compiler. -num: 5 -previous-page: procedures-cheatsheet -next-page: procedures-navigation ---- - -An issue found in the [GitHub repo][lampepfl/dotty] usually describes some code that -manifests undesired behaviour. - -To try fixing it, you will first need to reproduce the issue, so that -- you can understand its cause -- you can verify that any changes made to the codebase have a positive impact on the issue. - -Say you want to reproduce locally issue [#7710], you would first copy the code from the *"Minimised Code"* -section of the issue to a file named e.g. `local/i7710.scala`, -and then try to compile it from the sbt console opened in the dotty root directory: -```bash -$ sbt -sbt:scala3> scala3/scalac -d local/out local/i7710.scala -``` -> Here, the `-d` flag specifies a directory `local/out` where generated code will be output. - -You can then verify that the local reproduction has the same behaviour as originally reported in the issue. -If so, then you can start to try and fix it. Otherwise, perhaps the issue is out of date, or -is missing information about how to accurately reproduce the issue. - -## Dotty Issue Workspace - -Sometimes you will need more complex commands to reproduce an issue, and it is useful to script these, which -can be done with [dotty-issue-workspace]. It allows to bundle sbt commands for issue reproduction in one -file and then run them from the Dotty project's sbt console. - -### Try an Example Issue - -Let's use [dotty-issue-workspace] to reproduce issue [#7710]: -1. Follow [the steps in the README][workspace-readme] to install the plugin. -2. In your Issue Workspace directory (as defined in the plugin's README file, - "Getting Started" section, step 2), create a subdirectory for the - issue: `mkdir i7710`. -3. Create a file for the reproduction: `cd i7710; touch Test.scala`. In that file, - insert the code from the issue. -4. In the same directory, create a file `launch.iss` with the following content: - ```bash - $ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir. - - scala3/scalac -d $here/out $here/Test.scala - ``` - - - The first line, `$ (rm -rv out || true) && mkdir out` specifies a shell command - (it starts with `$`), in this case to ensure that there is a fresh `out` - directory to hold compiler output. - - The next line, `scala3/scalac -d $here/out $here/Test.scala` specifies an sbt - command, which will compile `Test.scala` and place any output into `out`. - `$here` is a special variable that will be replaced by the path of the parent - directory of `launch.iss` when executing the commands. -5. Now, from a terminal you can run the issue from sbt in the dotty directory - ([See here][clone] for a reminder if you have not cloned the repo.): - ```bash - $ sbt - sbt:scala3> issue i7710 - ``` - This will execute all the commands in the `i7710/launch.iss` file one by one. - If you've set up `dotty-issue-workspace` as described in its README, - the `issue` task will know where to find the folder by its name. - -### Using Script Arguments - -You can use script arguments inside `launch.iss` to reduce the number of steps when -working with issues. - -Say you have an issue `foo`, with two alternative files that are very similar: -`original.scala`, which reproduces the issue, and `alt.scala`, which does not, -and you want to compile them selectively? - -You can achieve this via the following `launch.iss`: - -```bash -$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir. - -scala3/scalac -d $here/out $here/$1.scala # compile the first argument following `issue foo <arg>` -``` - -It is similar to the previous example, except now you will compile a file `$1.scala`, referring -to the first argument passed after the issue name. The command invoked would look like -`issue foo original` to compile `original.scala`, and `issue foo alt` for `alt.scala`. - -In general, you can refer to arguments passed to the `issue <issue_name>` command using -the dollar notation: `$1` for the first argument, `$2` for the second and so on. - -### Multiline Commands - -Inside a `launch.iss` file, one command can be spread accross multiple lines. For example, -if your command has multiple arguments, you can put each argument on a new line. - -Multiline commands can even have comments inbetween lines. This is useful -if you want to try variants of a command with optional arguments (such as configuration). -You can put the optional arguments on separate lines, and then decide when they are passed to -the command by placing `#` in front to convert it to a comment (i.e. the argument will -not be passed). This saves typing the same arguments each time you want to use them. - -The following `launch.iss` file is an example of how you can use multiline commands as a -template for solving issues that [run compiled code][run]. It demonstrates configuring the -`scala3/scalac` command using compiler flags, which are commented out. -Put your favourite flags there for quick usage. - -```bash -$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir. - -scala3/scalac # Invoke the compiler task defined by the Dotty sbt project - -d $here/out # All the artefacts go to the `out` folder created earlier - # -Xprint:typer # Useful debug flags, commented out and ready for quick usage. Should you need one, you can quickly access it by uncommenting it. - # -Ydebug-error - # -Yprint-debug - # -Yprint-debug-owners - # -Yshow-tree-ids - # -Ydebug-tree-with-id 340 - # -Ycheck:all - $here/$1.scala # Invoke the compiler on the file passed as the second argument to the `issue` command. E.g. `issue foo Hello` will compile `Hello.scala` assuming the issue folder name is `foo`. - -scala3/scala -classpath $here/out Test # Run main method of `Test` generated by the compiler run. -``` - -## Conclusion - -In this section, you have seen how to reproduce an issue locally, and next you will see -how to try and detect its root cause. - -[lampepfl/dotty]: https://github.com/lampepfl/dotty/issues -[#7710]: https://github.com/lampepfl/dotty/issues/7710 -[dotty-issue-workspace]: https://github.com/anatoliykmetyuk/dotty-issue-workspace -[workspace-readme]: https://github.com/anatoliykmetyuk/dotty-issue-workspace#getting-started -[clone]: {% link _overviews/scala3-contribution/start-intro.md %}#clone-the-code -[run]: {% link _overviews/scala3-contribution/procedures-testing.md %}#checking-program-output +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/reproduce.html +--- \ No newline at end of file diff --git a/_overviews/scala3-contribution/procedures-testing.md b/_overviews/scala3-contribution/procedures-testing.md index 0e33c4452c..7c68dc18af 100644 --- a/_overviews/scala3-contribution/procedures-testing.md +++ b/_overviews/scala3-contribution/procedures-testing.md @@ -1,201 +1,5 @@ --- title: Testing Your Changes -type: section description: This page describes test procedures in the Scala 3 compiler. -num: 10 -previous-page: procedures-efficiency -next-page: procedures-checklist +redirect_to: https://dotty.epfl.ch/docs/contributing/workflow/testing.html --- - -It is important to add tests before a pull request, to verify that everything is working as expected, -and act as proof of what is valid/invalid Scala code (in case it is broken in the future). -In this section you will see the testing procedures in Scala 3. - -## Running all Tests - -To run all tests of Scala 3, including for compiler, REPL, libraries and more, run the following in sbt: - -```bash -$ sbt -sbt:scala3> scala3-bootstrapped/test -``` - -Often however it is not necessary to test everything if your changes are localised to one area, -you will see in the following sections the different kinds of tests, and how -to run individual tests. - -## Compilation Tests - -Compilation tests run the compiler over input files, using various settings. Input files -are found within the `tests/` directory at the root of the compiler repo. - -Test input files are categorised further by placing them in the subdirectories -of the `tests/` directory. A small selection of test categories include: - -- `tests/pos` – tests that should compile: pass if compiles successfully. -- `tests/neg` – should not compile: pass if fails compilation. Useful, e.g., to test an expected compiler error. -- `tests/run` – these tests not only compile but are also run. - -### Naming and Running a Test Case - -Tests are, by convention, named after the number of the issue they are fixing. -e.g. if you are fixing issue 101, then the test should be named `i101.scala`, for a single-file test, -or be within a directory called `i101/` for a multi-file test. - -To run the test, invoke the sbt command `testCompilation i101` (this will match all tests with `"i101"` in -the name, so it is useful to use a unique name) - -The test groups – `pos`, `neg`, etc. – are defined in [CompilationTests]. If you want to run a group -of tests, e.g. `pos`, you can do so via `testOnly *CompilationTests -- *pos` command. - -### Testing a Single Input File - -If your issue is reproducible by only one file, put that file under an appropriate category. -For example, if your issue is about getting rid of a spurious compiler error (that is a code that doesn't compile should, in fact, compile), you can create a file `tests/pos/i101.scala`. - -### Testing Multiple Input Files - -If you need more than one file to reproduce an issue, create a directory instead of a file -e.g. `tests/pos/i101/`, and put all the Scala files that are needed to reproduce the issue there. -There are two ways to organise the input files within: - -**1: Requiring classpath dependency:** Sometimes issues require one file to be compiled after the other, -(e.g. if the issue only happens with a library dependency, like with Java interop). In this case, -the outputs of the first file compiled will be available to the next file compiled, available via the classpath. -This is called *separate compilation*. - -To achieve this, within `tests/pos/i101/`, add a suffix `_n` to each file name, where `n` is an integer defining the -order in which the file will compile. E.g. if you have two files, `Lib.scala` and `Main.scala`, and you need them -compiled separately – Lib first, Main second, then name them `Lib_1.scala` and `Main_2.scala`. - -**2: Without classpath dependency:** If your issue does not require a classpath dependency, your files can be compiled -in a single run, this is called *joint compilation*. In this case use file names without the `_n` suffix. - -### Checking Program Output - -`tests/run` tests verify the run-time behaviour of a test case. The output is checked by invoking a main method -on a class `Test`, this can be done with either -```scala -@main def Test: Unit = assert(1 > 0) -``` -or -```scala -object Test extends scala.App: - assert(1 > 0) -``` - -If your program also prints output, this can be compared against `*.check` files. -These contain the expected output of a program. Checkfiles are named after the issue they are checking, -e.g. `tests/run/i101.check` will check either `tests/run/i101.scala` or `tests/run/i101/`. - -### Checking Compilation Errors - -`tests/neg` tests verify that a file does not compile, and user-facing errors are produced. There are other neg -categories such as `neg-custom-args`, i.e. with `neg` prefixing the directory name. Test files in the `neg*` -categories require annotations for the lines where errors are expected. To do this add one `// error` token to the -end of a line for each expected error. For example, if there are three expected errors, the end of the line should contain -`// error // error // error`. - -You can verify the content of the error messages with a `*.check` file. These contain the expected output of the -compiler. Checkfiles are named after the issue they are checking, -e.g. `i101.check` will check either `tests/neg/i101.scala` or `tests/neg/i101/`. -*Note:* checkfiles are not required for the test to pass, however they do add stronger constraints that the errors -are as expected. - -### If Checkfiles do not Match Output - -If the actual output mismatches the expected output, the test framework will dump the actual output in the file -`*.check.out` and fail the test suite. It will also output the instructions to quickly replace the expected output -with the actual output, in the following format: - -``` -Test output dumped in: tests/neg/Sample.check.out - See diff of the checkfile - > diff tests/neg/Sample.check tests/neg/Sample.check.out - Replace checkfile with current output - > mv tests/neg/Sample.check.out tests/neg/Sample.check -``` - -### Tips for creating Checkfiles - -To create a checkfile for a test, you can do one of the following: - -1. Create an empty checkfile - - then add arbitrary content - - run the test - - when it fails, use the `mv` command reported by the test to replace the initial checkfile with the actual output. -2. Manually compile the file you are testing with `scala3/scalac` - - copy-paste whatever console output the compiler produces to the checkfile. - -### Automatically Updating Checkfiles - -When complex or many checkfiles must be updated, `testCompilation` can run in a mode where it overrides the -checkfiles with the test outputs. -```bash -$ sbt -> testCompilation --update-checkfiles -``` - -Use `--help` to see all the options -```bash -$ sbt -> testCompilation --help -``` - -### Bootstrapped-only tests - -To run `testCompilation` on a bootstrapped Dotty compiler, use -`scala3-compiler-bootstrapped/testCompilation` (with the same syntax as above). -Some tests can only be run in bootstrapped compilers; that includes all tests -with `with-compiler` in their name. - -### From TASTy tests - -`testCompilation` has an additional mode to run tests that compile code from a `.tasty` file. -Modify the lists in [compiler/test/dotc] to enable or disable tests from `.tasty` files. - -```bash -$ sbt -> testCompilation --from-tasty -``` - -## Unit Tests - -Unit tests cover the other areas of the compiler, such as interactions with the REPL, scripting tools and more. -They are defined in [compiler/test], so if your use case isn't covered by this guide, -you may need to consult the codebase. Some common areas are highlighted below: - -### SemanticDB tests - -To test the SemanticDB output from the `extractSemanticDB` phase (enabled with the `-Xsemanticdb` flag), run the following sbt command: -```bash -$ sbt -sbt:scala3> scala3-compiler-bootstrapped/testOnly - dotty.tools.dotc.semanticdb.SemanticdbTests -``` - -[SemanticdbTests] uses source files in `tests/semanticdb/expect` to generate "expect files": -these verify both -- SemanticDB symbol occurrences inline in sourcecode (`*.expect.scala`) -- complete output of all SemanticDB information (`metac.expect`). - -Expect files are used as regression tests to detect changes in the compiler. -Their correctness is determined by human inspection. - -If expect files change then [SemanticdbTests] will fail, and generate new expect files, providing instructions for -comparing the differences and replacing the outdated expect files. - -If you are planning to update the SemanticDB output, you can do it in bulk by running the command -```bash -$ sbt -sbt:scala3> scala3-compiler/Test/runMain - dotty.tools.dotc.semanticdb.updateExpect -``` - -then compare the changes via version control. - - -[CompilationTests]: https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/dotc/CompilationTests.scala -[compiler/test]: https://github.com/lampepfl/dotty/blob/master/compiler/test/ -[compiler/test/dotc]: https://github.com/lampepfl/dotty/tree/master/compiler/test/dotc -[SemanticdbTests]: https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala diff --git a/_overviews/scala3-contribution/start-intro.md b/_overviews/scala3-contribution/start-intro.md index 815e9a1a8a..48e6100fbd 100644 --- a/_overviews/scala3-contribution/start-intro.md +++ b/_overviews/scala3-contribution/start-intro.md @@ -1,60 +1,5 @@ --- title: Getting Started -type: Chapter description: This page describes the high level architecture for the Scala 3 compiler. -num: 2 -previous-page: contribution-intro -next-page: procedures-intro +redirect_to: https://dotty.epfl.ch/docs/contributing/getting-started.html --- - -## First Steps - -### Required Tools - -#### Essential - -- [git] is essential for managing the Scala 3 code, and contributing to GitHub, where the code is hosted. -- A Java Virtual Machine (JDK 8 or higher), required for running the build tool. - - download Java from [Oracle Java 8][java8], [Oracle Java 11][java11], - or [AdoptOpenJDK 8/11][adopt]. Refer to [JDK Compatibility][compat] for Scala/Java compatibility detail. - - Verify that the JVM is installed by running the following command in a terminal: `java -version`. -- [sbt][sbt-download], the build tool required to build the Scala 3 compiler and libraries. - -#### Nice To Have - -An IDE, such as [Metals] will help you develop in Scala 3 with features such as goto-definition, -and with the [VS Code][vs-code] text editor you can even create interactive worksheets for an -iterative workflow. - -### Clone the Code -The code of Scala 3 is hosted on GitHub at [lampepfl/dotty]. - -Download the code with the following commands (shown using a `bash` compatible shell): - -```bash -$ cd workspace # or, replace `workspace` with any other directory you prefer -$ git clone https://github.com/lampepfl/dotty.git -$ cd dotty -``` - -## Verify your installation - -To verify that you can build the code, you can use `scala3/scalac` and `scala3/scala` to build -and run a test case, as shown in the next snippet: -```bash -$ sbt -sbt:scala3> scala3/scalac tests/pos/HelloWorld.scala -sbt:scala3> scala3/scala HelloWorld -hello world -``` - - -[git]: https://git-scm.com -[Metals]: https://scalameta.org/metals/ -[vs-code]: https://code.visualstudio.com -[lampepfl/dotty]: https://github.com/lampepfl/dotty -[sbt-download]: https://www.scala-sbt.org/download.html -[java8]: https://www.oracle.com/java/technologies/javase-jdk8-downloads.html -[java11]: https://www.oracle.com/java/technologies/javase-jdk11-downloads.html -[adopt]: https://adoptopenjdk.net/ -[compat]: /overviews/jdk-compatibility/overview.html diff --git a/_overviews/scala3-macros/faq.md b/_overviews/scala3-macros/faq.md index fc8ccb8972..7a809cdd60 100644 --- a/_overviews/scala3-macros/faq.md +++ b/_overviews/scala3-macros/faq.md @@ -13,7 +13,7 @@ All quotes containing a value of a primitive type is optimised to an `Expr.apply Choose one in your project and stick with a single notation to avoid confusion. ## How do I get a value out of an `Expr`? -If the expression represents a value, you can use `.value`, `.valueOrError` or `Expr.unapply` +If the expression represents a value, you can use `.value`, `.valueOrAbort` or `Expr.unapply` ## How can I get the precise type of an `Expr`? We can get the precise type (`Type`) of an `Expr` using the following pattern match: diff --git a/_overviews/scala3-macros/other-resources.md b/_overviews/scala3-macros/other-resources.md index 476f901b08..a50aefa23e 100644 --- a/_overviews/scala3-macros/other-resources.md +++ b/_overviews/scala3-macros/other-resources.md @@ -9,7 +9,7 @@ num: 9 * [Migration status][migration-status] ## Dotty documentation -- [Dotty Documentation](/scala3/reference/metaprogramming.html) +- [Dotty Documentation]({{ site.scala3ref }}/metaprogramming) - [Macros: The Plan For Scala 3](https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html) - [Examples](https://github.com/lampepfl/dotty-macro-examples) - a repository with small, self-contained examples of various tasks done with Dotty macros. diff --git a/_overviews/scala3-macros/tutorial/inline.md b/_overviews/scala3-macros/tutorial/inline.md index c081b8d61c..0fe620f162 100644 --- a/_overviews/scala3-macros/tutorial/inline.md +++ b/_overviews/scala3-macros/tutorial/inline.md @@ -254,8 +254,11 @@ Calling `power` with statically known constants results in the following code: val x = 2 power(x * x, 1) ``` + +{::options parse_block_html="true" /} <details> - <summary> See rest of inlining steps</summary> + <summary>See rest of inlining steps + </summary> ```scala // then inlined as @@ -273,8 +276,8 @@ val x = 2 val x2 = x * x x2 * { if (0 == 0) 1.0 - else if (0 % 2 == 1) x * power(x, 0 - 1) // dead branch - else power(x * x, 0 / 2) // dead branch + else if (0 % 2 == 1) x2 * power(x2, 0 - 1) // dead branch + else power(x2 * x2, 0 / 2) // dead branch } // partially evaluated to val x = 2 @@ -283,6 +286,8 @@ x2 * 1.0 ``` </details> +{::options parse_block_html="false" /} + In contrast, let us imagine we do not know the value of `n`: ```scala @@ -291,8 +296,10 @@ power(2, unknownNumber) Driven by the inline annotation on the parameter, the compiler will try to unroll the recursion. But without any success, since the parameter is not statically known. +{::options parse_block_html="true" /} <details> - <summary>See inlining steps</summary> + <summary>See inlining steps + </summary> ```scala // first inlines as @@ -318,6 +325,7 @@ else { ... ``` </details> +{::options parse_block_html="false" /} To guarantee that the branching can indeed be performed at compile-time, we can use the `inline if` variant of `if`. Annotating a conditional with `inline` will guarantee that the conditional can be reduced at compile-time and emits an error if the condition is not a statically known constant. @@ -396,7 +404,8 @@ Let us assume a call to `logged` on a concrete instance of `PrintLogger`: ```scala logged(new PrintLogger, "🥧") // inlined as -val logger: PrintLogger = new PrintLogger +val logger = new PrintLogger +val x = "🥧" logger.log(x) ``` After inlining, the call to `log` is de-virtualized and known to be on `PrintLogger`. @@ -491,7 +500,7 @@ def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = ... ``` -[soft-modifier]: {% link _scala3-reference/soft-modifier.md %} +[soft-modifier]: {{ site.scala3ref }}/soft-modifier.html [contributing]: {% link scala3/contribute-to-docs.md %} [best-practices]: {% link _overviews/scala3-macros/best-practices.md %} diff --git a/_overviews/scala3-macros/tutorial/macros.md b/_overviews/scala3-macros/tutorial/macros.md index 81cbc920d1..1c90c15928 100644 --- a/_overviews/scala3-macros/tutorial/macros.md +++ b/_overviews/scala3-macros/tutorial/macros.md @@ -14,13 +14,16 @@ Macros enable us to do exactly this: treat **programs as data** and manipulate t ## Macros Treat Programs as Values With a macro, we can treat programs as values, which allows us to analyze and generate them at compile time. + A Scala expression with type `T` is represented by an instance of the type `scala.quoted.Expr[T]`. We will dig into the details of the type `Expr[T]`, as well as the different ways of analyzing and constructing instances, when talking about [Quoted Code][quotes] and [Reflection][tasty]. For now, it suffices to know that macros are metaprograms that manipulate expressions of type `Expr[T]`. -The following macro implementation simply prints the expression of the provided argument: +The following macro implementation prints the expression of the provided argument at compile-time in the standard output of the compiler process: ```scala +import scala.quoted.* // imports Quotes, Expr + def inspectCode(x: Expr[Any])(using Quotes): Expr[Any] = println(x.show) x @@ -69,7 +72,7 @@ On the other hand, a macro executes user-written code that generates the code th Technically, compiling the inlined code `${ inspectCode('x) }` calls the method `inspectCode` _at compile time_ (through Java reflection), and the method `inspectCode` then executes as normal code. To be able to execute `inspectCode`, we need to compile its source code first. -As a technicaly consequence, we cannot define and use a macro in the **same class/file**. +As a technical consequence, we cannot define and use a macro in the **same class/file**. However, it is possible to have the macro definition and its call in the **same project** as long as the implementation of the macro can be compiled first. > ##### Suspended Files @@ -113,7 +116,7 @@ def powerCode( x: Expr[Double], n: Expr[Int] )(using Quotes): Expr[Double] = - val value: Double = pow(x.valueOrError, n.valueOrError) + val value: Double = pow(x.valueOrAbort, n.valueOrAbort) Expr(value) ``` Here, the `pow` operation is a simple Scala function that computes the value of `xⁿ`. @@ -131,22 +134,36 @@ Other types can also work if a `ToExpr` is implemented for it, we will [see this ### Extracting Values from Expressions -The second method we use in the implementation of `powerCode` is `Expr[T].valueOrError`, which has an effect opposite to `Expr.apply`. +The second method we use in the implementation of `powerCode` is `Expr[T].valueOrAbort`, which has an effect opposite to `Expr.apply`. It attempts to extract a value of type `T` from an expression of type `Expr[T]`. This can only succeed, if the expression directly contains the code of a value, otherwise, it will throw an exception that stops the macro expansion and reports that the expression did not correspond to a value. -Instead of `valueOrError`, we could also use the `value` operation, which will return an `Option`. +Instead of `valueOrAbort`, we could also use the `value` operation, which will return an `Option`. This way we can report the error with a custom error message. +#### Reporting Custom Error Messages + +The contextual `Quotes` parameter provides a `report` object that we can use to report a custom error message. +Within a macro implementation method, you can access the contextual `Quotes` parameter with the `quotes` method +(imported with `import scala.quoted.*`), then import the `report` object by `import quotes.reflect.report`. + +#### Providing the Custom Error + +We will provide the custom error message by calling `errorAndAbort` on the `report` object as follows: ```scala - ... +def powerCode( + x: Expr[Double], + n: Expr[Int] +)(using Quotes): Expr[Double] = + import quotes.reflect.report (x.value, n.value) match case (Some(base), Some(exponent)) => - pow(base, exponent) + val value: Double = pow(base, exponent) + Expr(value) case (Some(_), _) => - report.error("Expected a known value for the exponent, but was " + n.show, n) + report.errorAndAbort("Expected a known value for the exponent, but was " + n.show, n) case _ => - report.error("Expected a known value for the base, but was " + x.show, x) + report.errorAndAbort("Expected a known value for the base, but was " + x.show, x) ``` Alternatively, we can also use the `Expr.unapply` extractor @@ -155,11 +172,12 @@ Alternatively, we can also use the `Expr.unapply` extractor ... (x, n) match case (Expr(base), Expr(exponent)) => - pow(base, exponent) + val value: Double = pow(base, exponent) + Expr(value) case (Expr(_), _) => ... case _ => ... ``` -The operations `value`, `valueOrError`, and `Expr.unapply` will work for all _primitive types_, _tuples_ of any arity, `Option`, `Seq`, `Set`, `Map`, `Either` and `StringContext`. +The operations `value`, `valueOrAbort`, and `Expr.unapply` will work for all _primitive types_, _tuples_ of any arity, `Option`, `Seq`, `Set`, `Map`, `Either` and `StringContext`. Other types can also work if an `FromExpr` is implemented for it, we will [see this later][quotes]. @@ -167,15 +185,17 @@ Other types can also work if an `FromExpr` is implemented for it, we will [see t In the implementation of `inspectCode`, we have already seen how to convert expressions to the string representation of their _source code_ using the `.show` method. This can be useful to perform debugging on macro implementations: + +<!-- The below code example does not use multi-line string because it causes syntax highlighting to break --> ```scala def debugPowerCode( x: Expr[Double], n: Expr[Int] )(using Quotes): Expr[Double] = println( - s"""powerCode - | x := ${x.show} - | n := ${n.show}""".stripMargin) + s"powerCode \n" + + s" x := ${x.show}\n" + + s" n := ${n.show}") val code = powerCode(x, n) println(s" code := ${code.show}") code @@ -188,23 +208,24 @@ Varargs in Scala are represented with `Seq`, hence when we write a macro with a It is possible to recover each individual argument (of type `Expr[T]`) using the `scala.quoted.Varargs` extractor. ```scala -import scala.quoted.Varargs +import scala.quoted.* // imports `Varargs`, `Quotes`, etc. inline def sumNow(inline nums: Int*): Int = ${ sumCode('nums) } def sumCode(nums: Expr[Seq[Int]])(using Quotes): Expr[Int] = + import quotes.reflect.report nums match - case Varargs(numberExprs) => // numberExprs: Seq[Expr[Int]] - val numbers: Seq[Int] = numberExprs.map(_.valueOrError) + case Varargs(numberExprs) => // numberExprs: Seq[Expr[Int]] + val numbers: Seq[Int] = numberExprs.map(_.valueOrAbort) Expr(numbers.sum) - case _ => report.error( - "Expected explicit argument" + - "Notation `args: _*` is not supported.", numbersExpr) + case _ => report.errorAndAbort( + "Expected explicit varargs sequence. " + + "Notation `args*` is not supported.", nums) ``` The extractor will match a call to `sumNow(1, 2, 3)` and extract a `Seq[Expr[Int]]` containing the code of each parameter. -But, if we try to match the argument of the call `sumNow(nums: _*)`, the extractor will not match. +But, if we try to match the argument of the call `sumNow(nums*)`, the extractor will not match. `Varargs` can also be used as a constructor. `Varargs(Expr(1), Expr(2), Expr(3))` will return an `Expr[Seq[Int]]`. We will see how this can be useful later. @@ -244,7 +265,7 @@ inline def test(inline ignore: Boolean, computation: => Unit): Boolean = ${ testCode('ignore, 'computation) } def testCode(ignore: Expr[Boolean], computation: Expr[Unit])(using Quotes) = - if ignore.valueOrError then Expr(false) + if ignore.valueOrAbort then Expr(false) else Expr.block(List(computation), Expr(true)) ``` @@ -269,7 +290,7 @@ Note, that `matches` only performs a limited amount of normalization and while f ### Arbitrary Expressions Last but not least, it is possible to create an `Expr[T]` from arbitary Scala code by enclosing it in [quotes][quotes]. -For example, `'{ ${expr}; true }` will generate an `Expr[Int]` equivalent to `Expr.block(List(expr), Expr(true))`. +For example, `'{ ${expr}; true }` will generate an `Expr[Boolean]` equivalent to `Expr.block(List(expr), Expr(true))`. The subsequent section on [Quoted Code][quotes] presents quotes in more detail. [contributing]: {% link scala3/contribute-to-docs.md %} diff --git a/_overviews/scala3-macros/tutorial/quotes.md b/_overviews/scala3-macros/tutorial/quotes.md index 109e509e59..b94d4bb6ab 100644 --- a/_overviews/scala3-macros/tutorial/quotes.md +++ b/_overviews/scala3-macros/tutorial/quotes.md @@ -154,13 +154,13 @@ given ToExpr[Boolean] with { } given ToExpr[StringContext] with { - def apply(x: StringContext)(using Quotes) = + def apply(stringContext: StringContext)(using Quotes) = val parts = Varargs(stringContext.parts.map(Expr(_))) - '{ StringContext($parts: _*) } + '{ StringContext($parts*) } } ``` The `Varargs` constructor just creates an `Expr[Seq[T]]` which we can efficiently splice as a varargs. -In general, any sequence can be spliced with `$mySeq: _*` to splice it as a varargs. +In general, any sequence can be spliced with `$mySeq*` to splice it as a varargs. ## Quoted patterns Quotes can also be used to check if an expression is equivalent to another or to deconstruct an expression into its parts. @@ -245,26 +245,86 @@ case ... ### Matching function expressions -*Coming soon* +Let's start with the most straightforward example, matching an identity function expression: + +```scala +def matchIdentityFunction[A: Type](func: Expr[A => A])(using Quotes): Unit = + func match + case '{ (arg: A) => arg } => +``` +The above matches function expressions that just return their arguments, like: + +```scala +(value: Int) => value +``` + +We can also match more complex expressions, like method call chains: + +```scala +def matchMethodCallChain(func: Expr[String => String])(using Quotes) = + func match + case '{ (arg: String) => arg.toLowerCase.strip.trim } => +``` + +But what about the cases where we want more flexibility (eg. we know the subset of methods that will be called but not neccessarily their order)? + +#### Iterative deconstruction of a function expression + +Let's imagine we need a macro that collects names of methods used in an expression of type `FieldName => FieldName`, for a definition of `FieldName`: + +```scala +trait FieldName: + def uppercase: FieldName + def lowercase: FieldName +``` + +The implementation itself would look like this: + +```scala +def collectUsedMethods(func: Expr[FieldName => FieldName])(using Quotes): List[String] = + def recurse(current: Expr[FieldName => FieldName], acc: List[String])(using Quotes): List[String] = + current match + // $body is the next tree with the '.lowercase' call stripped away + case '{ (arg: FieldName) => ($body(arg): FieldName).lowercase } => + recurse(body, "lowercase" :: acc) // body: Expr[FieldName => FieldName] + + // $body is the next tree with the '.uppercase' call stripped away + case '{ (arg: FieldName) => ($body(arg): FieldName).uppercase } => + recurse(body, "uppercase" :: acc) // body: Expr[FieldName => FieldName] + + // this matches an identity function, i.e. the end of our loop + case '{ (arg: FieldName) => arg } => acc + end recurse + + recurse(func, Nil) +``` + +For more details on how patterns like `$body(arg)` work please refer to a docs section on [the HOAS pattern](https://dotty.epfl.ch/docs/reference/metaprogramming/macros.html#hoas-patterns-1). + +If we were to use this on an expression like this one: +```scala +(name: FieldName) => name.lowercase.uppercase.lowercase +``` +the result would evaluate to `List("lowercase", "uppercase", "lowercase")`. ### Matching types So far, we assumed that the types within quote patterns would be statically known. -Quote patterns also allow for generic types and existential types, which we will see in this section. +Quote patterns also allow for type parameters, which we will see in this section. -#### Generic types in patterns +#### Type parameters in patterns Consider the function `exprOfOption` that we have already seen: ```scala def exprOfOption[T: Type](x: Expr[Option[T]])(using Quotes): Option[Expr[T]] = x match case '{ Some($x: T) } => Some(x) // x: Expr[T] - // ^^^ type ascription with generic type T + // ^^^ type ascription with type T ... ``` Note that this time we have added the `T` explicitly in the pattern, even though it could be inferred. -By referring to the generic type `T` in the pattern, we are required to have a given `Type[T]` in scope. +By referring to the type parameter `T` in the pattern, we are required to have a given `Type[T]` in scope. This implies that `$x: T` will only match if `x` is of type `Expr[T]`. In this particular case, this condition will always be true. @@ -349,17 +409,23 @@ Note that the previous case could also be written as `case '{ ($ls: List[u]).map Types represented with `Type[T]` can be matched on using the patten `case '[...] =>`. ```scala -def mirrorFields[T: Type](using Quotes): List[String] = - Type.of[T] match +inline def mirrorFields[T]: List[String] = ${mirrorFieldsImpl[T]} + +def mirrorFieldsImpl[T: Type](using Quotes): Expr[List[String]] = + + def rec[A : Type]: List[String] = Type.of[A] match case '[field *: fields] => - Type.show[field] :: mirrorFields[fields] + Type.show[field] :: rec[fields] case '[EmptyTuple] => Nil case _ => - compiletime.error("Expected known tuple but got: " + Type.show[T]) + quotes.reflect.report.errorAndAbort("Expected known tuple but got: " + Type.show[A]) + Expr(rec) +``` +```scala mirrorFields[EmptyTuple] // Nil -mirrorFields[(Int, String, Int)] // List("Int", "String", "Int") +mirrorFields[(Int, String, Int)] // List("scala.Int", "java.lang.String", "scala.Int") mirrorFields[Tuple] // error: Expected known tuple but got: Tuple ``` @@ -367,7 +433,7 @@ As with expression quote patterns, type variables are represented using lower ca ## FromExpr -The `Expr.value`, `Expr.valueOrError`, and `Expr.unapply` methods uses intances of `FromExpr` to extract the value if possible. +The `Expr.value`, `Expr.valueOrAbort`, and `Expr.unapply` methods use instances of `FromExpr` to extract the value if possible. ```scala extension [T](expr: Expr[T]): def value(using Quotes)(using fromExpr: FromExpr[T]): Option[T] = @@ -402,8 +468,8 @@ given FromExpr[Boolean] with { given FromExpr[StringContext] with { def unapply(x: Expr[StringContext])(using Quotes): Option[StringContext] = x match { - case '{ new StringContext(${Varargs(Exprs(args))}: _*) } => Some(StringContext(args: _*)) - case '{ StringContext(${Varargs(Exprs(args))}: _*) } => Some(StringContext(args: _*)) + case '{ new StringContext(${Varargs(Exprs(args))}*) } => Some(StringContext(args*)) + case '{ StringContext(${Varargs(Exprs(args))}*) } => Some(StringContext(args*)) case _ => None } } diff --git a/_overviews/scala3-macros/tutorial/reflection.md b/_overviews/scala3-macros/tutorial/reflection.md index b9bca2e1c9..46618a1d4f 100644 --- a/_overviews/scala3-macros/tutorial/reflection.md +++ b/_overviews/scala3-macros/tutorial/reflection.md @@ -117,7 +117,7 @@ In addition, `Symbol` exposes and is used by many useful methods. For example: - `declaredFields` and `declaredMethods` allow you to iterate on the fields and members defined inside a symbol - `flags` allows you to check multiple properties of a symbol - - `companionObject` and `companionModule` provide a way to jump to and from the companion object/class + - `companionClass` and `companionModule` provide a way to jump to and from the companion object/class - `TypeRepr.baseClasses` returns the list of symbols of classes extended by a type - `Symbol.pos` gives you access to the position where the symbol is defined, the source code of the definition, and even the filename where the symbol is defined - many others that you can find in [`SymbolMethods`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule$SymbolMethods.html) diff --git a/_overviews/scala3-migration/compatibility-classpath.md b/_overviews/scala3-migration/compatibility-classpath.md index b1374f6474..6b25280994 100644 --- a/_overviews/scala3-migration/compatibility-classpath.md +++ b/_overviews/scala3-migration/compatibility-classpath.md @@ -24,15 +24,16 @@ The Scala 3 unpickler has been extensively tested in the community build for man ![Scala 3 module depending on a Scala 2.13 artifact](/resources/images/scala3-migration/compatibility-3-to-213.svg) -As an sbt build it can be illustrated by (sbt 1.5.0 or higher is required): +As an sbt build, it looks like this: ```scala +// build.sbt (sbt 1.5 or higher) lazy val foo = project.in(file("foo")) - .settings(scalaVersion := "3.0.0") + .settings(scalaVersion := "3.3.1") .dependsOn(bar) lazy val bar = project.in(file("bar")) - .settings(scalaVersion := "2.13.6") + .settings(scalaVersion := "2.13.11") ``` Or, in case bar is a published Scala 2.13 library, we can have: @@ -40,7 +41,7 @@ Or, in case bar is a published Scala 2.13 library, we can have: ```scala lazy val foo = project.in(file("foo")) .settings( - scalaVersion := "3.0.0", + scalaVersion := "3.3.1", libraryDependencies += ("org.bar" %% "bar" % "1.0.0").cross(CrossVersion.for3Use2_13) ) ``` @@ -56,37 +57,35 @@ Let's note that the standard library is automatically provided by the build tool ## The Scala 2.13 TASTy Reader -The second piece of good news is that the Scala 2.13 TASTy reader, which enables consuming Scala 3 libraries has been shipped since Scala 2.13.4. - -> The TASTy reader is very new. That's why it is only available under the `-Ytasty-reader` flag. +The second piece of good news is that Scala 2.13 can consume Scala 3 libraries with `-Ytasty-reader`. ### Supported Features -The TASTy reader supports all the traditional language features as well as the following brand-new features: -- [Enumerations]({% link _scala3-reference/enums/enums.md %}) -- [Intersection Types]({% link _scala3-reference/new-types/intersection-types.md %}) -- [Opaque Type Aliases]({% link _scala3-reference/other-new-features/opaques.md %}) -- [Type Lambdas]({% link _scala3-reference/new-types/type-lambdas.md %}) -- [Contextual Abstractions]({% link _scala3-reference/contextual.md %}) (new syntax) -- [Open Classes]({% link _scala3-reference/other-new-features/open-classes.md %}) (and inheritance of super traits) -- [Export Clauses]({% link _scala3-reference/other-new-features/export.md %}) - -We have limited support on: -- [Top-Level Definitions]({% link _scala3-reference/dropped-features/package-objects.md %}) -- [Extension Methods]({% link _scala3-reference/contextual/extension-methods.md %}) - -More exotic features are not supported: -- [Context Functions]({% link _scala3-reference/contextual/context-functions.md %}) -- [Polymorphic Function Types]({% link _scala3-reference/new-types/polymorphic-function-types.md %}) -- [Trait Parameters]({% link _scala3-reference/other-new-features/trait-parameters.md %}) +The TASTy reader supports all the traditional language features as well as the following Scala 3 features: +- [Enumerations]({{ site.scala3ref }}/enums/enums.html) +- [Intersection Types]({{ site.scala3ref }}/new-types/intersection-types.html) +- [Opaque Type Aliases]({{ site.scala3ref }}/other-new-features/opaques.html) +- [Type Lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html) +- [Contextual Abstractions]({{ site.scala3ref }}/contextual) (new syntax) +- [Open Classes]({{ site.scala3ref }}/other-new-features/open-classes.html) (and inheritance of super traits) +- [Export Clauses]({{ site.scala3ref }}/other-new-features/export.html) + +It partially supports: +- [Top-Level Definitions]({{ site.scala3ref }}/dropped-features/package-objects.html) +- [Extension Methods]({{ site.scala3ref }}/contextual/extension-methods.html) + +It does not support the more advanced features: +- [Context Functions]({{ site.scala3ref }}/contextual/context-functions.html) +- [Polymorphic Function Types]({{ site.scala3ref }}/new-types/polymorphic-function-types.html) +- [Trait Parameters]({{ site.scala3ref }}/other-new-features/trait-parameters.html) - `@static` Annotation - `@alpha` Annotation -- [Functions and Tuples larger than 22 parameters]({% link _scala3-reference/dropped-features/limit22.md %}) -- [Match Types]({% link _scala3-reference/new-types/match-types.md %}) -- [Union Types]({% link _scala3-reference/new-types/union-types.md %}) -- [Multiversal Equality]({% link _scala3-reference/contextual/multiversal-equality.md %}) (unless explicit) -- [Inline]({% link _scala3-reference/metaprogramming/inline.md %}) (including Scala 3 macros) -- [Kind Polymorphism]({% link _scala3-reference/other-new-features/kind-polymorphism.md %}) (the `scala.AnyKind` upper bound) +- [Functions and Tuples larger than 22 parameters]({{ site.scala3ref }}/dropped-features/limit22.html) +- [Match Types]({{ site.scala3ref }}/new-types/match-types.html) +- [Union Types]({{ site.scala3ref }}/new-types/union-types.html) +- [Multiversal Equality]({{ site.scala3ref }}/contextual/multiversal-equality.html) (unless explicit) +- [Inline]({{ site.scala3ref }}/metaprogramming/inline.html) (including Scala 3 macros) +- [Kind Polymorphism]({{ site.scala3ref }}/other-new-features/kind-polymorphism.html) (the `scala.AnyKind` upper bound) ### A Scala 2.13 module can depend on a Scala 3 artifact @@ -94,18 +93,19 @@ By enabling the TASTy reader with `-Ytasty-reader`, a Scala 2.13 module can depe ![Scala 2 module depending on a Scala 3 artifact](/resources/images/scala3-migration/compatibility-213-to-3.svg) -As an sbt build it can be illustrated by: +As an sbt build, it looks like this: ```scala +// build.sbt (sbt 1.5 or higher) lazy val foo = project.in.file("foo") .settings( - scalaVersion := "2.13.6", + scalaVersion := "2.13.11", scalacOptions += "-Ytasty-reader" ) .dependsOn(bar) lazy val bar = project.in(file("bar")) - .settings(scalaVersion := "3.0.0") + .settings(scalaVersion := "3.3.1") ``` Or, in case `bar` is a published Scala 3 library: @@ -113,7 +113,7 @@ Or, in case `bar` is a published Scala 3 library: ```scala lazy val foo = project.in.file("foo") .settings( - scalaVersion := "2.13.6", + scalaVersion := "2.13.11", scalacOptions += "-Ytasty-reader", libraryDependencies += ("org.bar" %% "bar" % "1.0.0").cross(CrossVersion.for2_13Use3) ) @@ -137,7 +137,5 @@ The inverted pattern, with a 2.13 module in the middle, is also possible. > #### Disclaimer for library maintainers > -> Using the interoperability between Scala 2.13 and Scala 3 in a published library is generally not safe for your end-users. -> > Unless you know exactly what you are doing, it is discouraged to publish a Scala 3 library that depends on a Scala 2.13 library (the scala-library being excluded) or vice versa. > The reason is to prevent library users from ending up with two conflicting versions `foo_2.13` and `foo_3` of the same foo library in their classpath, this problem being unsolvable in some cases. diff --git a/_overviews/scala3-migration/compatibility-metaprogramming.md b/_overviews/scala3-migration/compatibility-metaprogramming.md index c8d7ae25db..675f5fc4a3 100644 --- a/_overviews/scala3-migration/compatibility-metaprogramming.md +++ b/_overviews/scala3-migration/compatibility-metaprogramming.md @@ -14,7 +14,7 @@ Therefore it is not possible for the Scala 3 compiler to expand any Scala 2.13 m In contrast, Scala 3 introduces a new principled approach of metaprogramming that is designed for stability. Scala 3 macros, and inline methods in general, will be compatible with future versions of the Scala 3 compiler. -While this is an uncontested improvement, it also means that all Scala 2.13 macros have to be rewritten from the ground up, by using the new metaprogramming features. +While this is an uncontested improvement, it also means that all Scala 2.13 macros have to be rewritten from the ground up, using the new metaprogramming features. ## Macro Dependencies @@ -54,16 +54,19 @@ They are comprised of: Before getting deep into reimplementing a macro you should ask yourself: - Can I use `inline` and the `scala.compiletime` operations to reimplement my logic? -- Can I use the simpler and safer expression based macros? +- Can I use the simpler and safer expression-based macros? - Do I really need to access the AST? -- Can I use a [match type](/scala3/reference/new-types/match-types.html) as return type? +- Can I use a [match type]({{ site.scala3ref }}/new-types/match-types.html) as return type? -You can learn all the new metaprogramming concepts by reading the [Macro Tutorial][scala3-macros]. +You can learn all the new metaprogramming concepts by reading the [Macros in Scala 3][scala3-macros] tutorial. ## Cross-building a Macro Library You have written a wonderful macro library and you would like it to be available in Scala 2.13 and Scala 3. -There are two different approaches, the traditional cross-building technique and the more recent mixing macro technique. +There are two different approaches, the traditional cross-building technique and the more flexible macro mixing technique. + +The benefit of macro mixing is that consumers who take advantage of the `-Ytasty-reader` option can still use your macros. + You can learn about them by reading these tutorials: - [Cross-Building a Macro Library](tutorial-macro-cross-building.html) - [Mixing Scala 2.13 and Scala 3 Macros](tutorial-macro-mixing.html) diff --git a/_overviews/scala3-migration/external-resources.md b/_overviews/scala3-migration/external-resources.md index 4915537176..1055f4bc95 100644 --- a/_overviews/scala3-migration/external-resources.md +++ b/_overviews/scala3-migration/external-resources.md @@ -1,8 +1,8 @@ --- title: External Resources -type: section +type: chapter description: This section lists external resources about the migration to Scala 3. -num: 27 +num: 29 previous-page: plugin-kind-projector next-page: --- diff --git a/_overviews/scala3-migration/incompat-contextual-abstractions.md b/_overviews/scala3-migration/incompat-contextual-abstractions.md index e215fff469..ea5947f2e4 100644 --- a/_overviews/scala3-migration/incompat-contextual-abstractions.md +++ b/_overviews/scala3-migration/incompat-contextual-abstractions.md @@ -2,16 +2,16 @@ title: Contextual Abstractions type: section description: This chapter details all incompatibilities caused by the redesign of contextual abstractions -num: 18 +num: 19 previous-page: incompat-dropped-features next-page: incompat-other-changes --- -The redesign of [contextual abstractions]({% link _scala3-reference/contextual.md %}) brings some incompatibilities. +The redesign of [contextual abstractions]({{ site.scala3ref }}/contextual) brings some incompatibilities. |Incompatibility|Scala 2.13|Scala 3 Migration Rewrite|Scalafix Rule|Runtime Incompatibiltiy| |--- |--- |--- |--- |--- | -|[Type of implicit def](#type-of-implicit-definition)|||[✅](https://github.com/ohze/scala-rewrites#fixexplicittypesexplicitimplicittypes)|| +|[Type of implicit def](#type-of-implicit-definition)|||[✅](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html)|| |[Implicit views](#implicit-views)||||**Possible**| |[View bounds](#view-bounds)|Deprecation|||| |[Ambiguous conversion on `A` and `=> A`](#ambiguous-conversion-on-a-and--a)||||| @@ -21,31 +21,37 @@ The redesign of [contextual abstractions]({% link _scala3-reference/contextual.m The type of implicit definitions (`val` or `def`) needs to be given explicitly in Scala 3. They cannot be inferred anymore. -The Scalafix rule named `ExplicitImplicitTypes` in [ohze/scala-rewrites](https://github.com/ohze/scala-rewrites#fixexplicittypesexplicitimplicittypes) repository can write the missing type annotations automatically. +The Scalafix rule named [ExplicitResultTypes](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html) can write the missing type annotations automatically. ## Implicit Views Scala 3 does not support implicit conversion from an implicit function value, of the form `implicit val ev: A => B`. -The following piece of code is now invalid: +{% tabs scala-2-implicit_1 %} +{% tab 'Scala 2 Only' %} -```scala +The following piece of code is now invalid in Scala 3: +~~~ scala trait Pretty { val print: String } def pretty[A](a: A)(implicit ev: A => Pretty): String = - a.print // Error: value print is not a member of A -``` + a.print // In Scala 3, Error: value print is not a member of A +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) can warn you about those cases, but it does not try to fix it. Be aware that this incompatibility can produce a runtime incompatibility and break your program. Indeed the compiler can find another implicit conversion from a broader scope, which would eventually cause an undesired behavior at runtime. -This example illustrates the case: +{% tabs shared-implicit_2 %} +{% tab 'Scala 2 and 3' %} -```scala +This example illustrates the case: +~~~ scala trait Pretty { val print: String } @@ -54,13 +60,15 @@ implicit def anyPretty(any: Any): Pretty = new Pretty { val print = "any" } def pretty[A](a: A)(implicit ev: A => Pretty): String = a.print // always print "any" -``` +~~~ +{% endtab %} +{% endtabs %} The resolved conversion depends on the compiler mode: - `-source:3.0-migration`: the compiler performs the `ev` conversion - `-source:3.0`: the compiler cannot perform the `ev` conversion but it can perform the `anyPretty`, which is undesired -One simple fix is to supply the right conversion explicitly: +In Scala 3, one simple fix is to supply the right conversion explicitly: {% highlight diff %} def pretty[A](a: A)(implicit ev: A => Pretty): String = @@ -73,11 +81,15 @@ def pretty[A](a: A)(implicit ev: A => Pretty): String = View bounds have been deprecated for a long time but they are still supported in Scala 2.13. They cannot be compiled with Scala 3 anymore. -```scala +{% tabs scala-2-bounds_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala def foo[A <% Long](a: A): Long = a -``` +~~~ +{% endtab %} +{% endtabs %} -In this example we get: +In this example, in Scala 3, we get this following error message: {% highlight text %} -- Error: src/main/scala/view-bound.scala:2:12 @@ -104,12 +116,16 @@ It is not the case in Scala 3 anymore, and leads to an ambiguous conversion. For instance, in this example: -```scala +{% tabs scala-2-ambiguous_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala implicit def boolFoo(bool: Boolean): Foo = ??? implicit def lazyBoolFoo(lazyBool: => Boolean): Foo = ??? true.foo() -``` +~~~ +{% endtab %} +{% endtabs %} The Scala 2.13 compiler chooses the `boolFoo` conversion but the Scala 3 compiler fails to compile. diff --git a/_overviews/scala3-migration/incompat-dropped-features.md b/_overviews/scala3-migration/incompat-dropped-features.md index eef4ce46a3..845a58b143 100644 --- a/_overviews/scala3-migration/incompat-dropped-features.md +++ b/_overviews/scala3-migration/incompat-dropped-features.md @@ -2,7 +2,7 @@ title: Dropped Features type: section description: This chapter details all the dropped features -num: 17 +num: 18 previous-page: incompat-syntactic next-page: incompat-contextual-abstractions --- @@ -19,6 +19,7 @@ Most of these changes can be handled automatically during the [Scala 3 migration |[`any2stringadd` conversion](#any2stringadd-conversion)|Deprecation||[✅](https://github.com/scala/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/Any2StringAdd.scala)| |[Early initializer](#early-initializer)|Deprecation||| |[Existential type](#existential-type)|Feature warning||| +|[@specialized](#specialized)|Deprecation||| ## Symbol literals @@ -27,14 +28,17 @@ But the `scala.Symbol` class still exists so that each string literal can be saf This piece of code cannot be compiled with Scala 3: -```scala +{% tabs scala-2-literals_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala val values: Map[Symbol, Int] = Map('abc -> 1) -val abc = values('abc) // Migration Warning: symbol literal 'abc is no longer supported -``` +val abc = values('abc) // In Scala 3, Migration Warning: symbol literal 'abc is no longer supported +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the code into: - {% highlight diff %} val values: Map[Symbol, Int] = Map(Symbol("abc") -> 1) @@ -47,26 +51,33 @@ You are recommended, as a second step, to replace every use of `Symbol` with a p ## `do`-`while` construct -The `do` keyword has acquired a different meaning in the [New Control Syntax]({% link _scala3-reference/other-new-features/control-syntax.md %}). +The `do` keyword has acquired a different meaning in the [New Control Syntax]({{ site.scala3ref }}/other-new-features/control-syntax.html). To avoid confusion, the traditional `do <body> while (<cond>)` construct is dropped. It is recommended to use the equivalent `while ({ <body>; <cond> }) ()` that can be cross-compiled, or the new Scala 3 syntax `while { <body>; <cond> } do ()`. The following piece of code cannot be compiled with Scala 3. -```scala -do { // Migration Warning: `do <body> while <cond>` is no longer supported +{% tabs scala-2-do_while_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala +do { // In Scala 3, Migration Warning: `do <body> while <cond>` is no longer supported i += 1 } while (f(i) == 0) -``` +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites it into. - -```scala +{% tabs scala-3-do_while_2 %} +{% tab 'Scala 3 Only' %} +~~~ scala while ({ { i += 1 } ; f(i) == 0}) () -``` +~~~ +{% endtab %} +{% endtabs %} ## Auto-application @@ -75,16 +86,19 @@ It is deprecated in Scala 2.13 and dropped in Scala 3. The following code is invalid in Scala 3: -```scala +{% tabs scala-2-auto_application_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala object Hello { def message(): String = "Hello" } -println(Hello.message) // Migration Warning: method message must be called with () argument -``` +println(Hello.message) // In Scala 3, Migration Warning: method message must be called with () argument +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites it into: - {% highlight diff %} object Hello { def message(): String = "Hello" @@ -94,22 +108,25 @@ object Hello { +println(Hello.message()) {% endhighlight %} -Auto-application is covered in detail in [this page](/scala3/reference/dropped-features/auto-apply.html) of the Scala 3 reference documentation. +Auto-application is covered in detail in [this page]({{ site.scala3ref }}/dropped-features/auto-apply.html) of the Scala 3 reference documentation. ## Value eta-expansion -Scala 3 introduces [Automatic Eta-Expansion](/scala3/reference/changed-features/eta-expansion-spec.html) which will deprecate the method to value syntax `m _`. +Scala 3 introduces [Automatic Eta-Expansion]({{ site.scala3ref }}/changed-features/eta-expansion-spec.html) which will deprecate the method to value syntax `m _`. Furthermore Scala 3 does not allow eta-expansion of values to nullary functions anymore. Thus, this piece of code is invalid in Scala 3: -```scala +{% tabs scala-2-eta_expansion_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala val x = 1 -val f: () => Int = x _ // Migration Warning: The syntax `<function> _` is no longer supported; -``` +val f: () => Int = x _ // In Scala 3, Migration Warning: The syntax `<function> _` is no longer supported; +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites it into: - {% highlight diff %} val x = 1 -val f: () => Int = x _ @@ -120,14 +137,17 @@ val x = 1 The implicit `Predef.any2stringadd` conversion is deprecated in Scala 2.13 and dropped in Scala 3. -This piece of code does not compile anymore. +This piece of code does not compile anymore in Scala 3. -```scala -val str = new AnyRef + "foo" // Error: value + is not a member of Object -``` +{% tabs scala-2-any2stringadd_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala +val str = new AnyRef + "foo" // In Scala 3, Error: value + is not a member of Object +~~~ +{% endtab %} +{% endtabs %} The conversion to `String` must be applied explicitly, for instance with `String.valueOf`. - {% highlight diff %} -val str = new AnyRef + "foo" +val str = String.valueOf(new AnyRef) + "foo" @@ -138,11 +158,13 @@ This rewrite can be applied by the `fix.scala213.Any2StringAdd` Scalafix rule in ## Early Initializer Early initializers are deprecated in Scala 2.13 and dropped in Scala 3. -They were rarely used, and mostly to compensate for the lack of [Trait parameters](/scala3/reference/other-new-features/trait-parameters.html) which are now supported in Scala 3. +They were rarely used, and mostly to compensate for the lack of [Trait parameters]({{ site.scala3ref }}/other-new-features/trait-parameters.html) which are now supported in Scala 3. -That is why the following piece of code does not compile anymore. +That is why the following piece of code does not compile anymore in Scala 3. -```scala +{% tabs scala-2-initializer_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala trait Bar { val name: String val size: Int = name.size @@ -151,7 +173,9 @@ trait Bar { object Foo extends { val name = "Foo" } with Bar -``` +~~~ +{% endtab %} +{% endtabs %} The Scala 3 compiler produces two error messages: @@ -161,7 +185,6 @@ The Scala 3 compiler produces two error messages: | ^ | `extends` must be followed by at least one parent {% endhighlight %} - {% highlight text %} -- [E009] Syntax Error: src/main/scala/early-initializer.scala:8:2 8 |} with Bar @@ -171,51 +194,115 @@ The Scala 3 compiler produces two error messages: It suggests to use trait parameters which would give us: -```scala +{% tabs scala-3-initializer_2 %} +{% tab 'Scala 3 Only' %} +~~~ scala trait Bar(name: String) { val size: Int = name.size } object Foo extends Bar("Foo") -``` +~~~ +{% endtab %} +{% endtabs %} Since trait parameters are not available in Scala 2.13, it does not cross-compile. If you need a cross-compiling solution you can use an intermediate class that carries the early initialized `val`s and `var`s as constructor parameters. -```scala +{% tabs shared-initializer_4 %} +{% tab 'Scala 2 and 3' %} +~~~ scala abstract class BarEarlyInit(val name: String) extends Bar object Foo extends BarEarlyInit("Foo") -``` +~~~ In the case of a class, it is also possible to use a secondary constructor with a fixed value, as shown by: - -```scala +~~~ scala class Fizz private (val name: String) extends Bar { def this() = this("Fizz") } -``` +~~~ +{% endtab %} +{% endtabs %} + +Another use case for early initializers in Scala 2 is private state in the subclass that is accessed (through an overridden method) by the constructor of the superclass: + +{% tabs scala-2-initializer_5 %} +{% tab 'Scala 2 Only' %} +~~~ scala +class Adder { + var sum = 0 + def add(x: Int): Unit = sum += x + add(1) +} +class LogAdder extends { + private var added: Set[Int] = Set.empty +} with Adder { + override def add(x: Int): Unit = { added += x; super.add(x) } +} +~~~ +{% endtab %} +{% endtabs %} + +This case can be refactored by moving the private state into a nested `object`, which is initialized on demand: + +{% tabs shared-initializer_6 %} +{% tab 'Scala 2 and 3' %} +~~~ scala +class Adder { + var sum = 0 + def add(x: Int): Unit = sum += x + add(1) +} +class LogAdder extends Adder { + private object state { + var added: Set[Int] = Set.empty + } + import state._ + override def add(x: Int): Unit = { added += x; super.add(x) } +} +~~~ +{% endtab %} +{% endtabs %} ## Existential Type -Existential type is a [dropped feature](/scala3/reference/dropped-features/existential-types.html), which makes the following code invalid. - -```scala -def foo: List[Class[T]] forSome { type T } // Error: Existential types are no longer supported -``` +Existential type is a [dropped feature]({{ site.scala3ref }}/dropped-features/existential-types.html), which makes the following code invalid. + +{% tabs scala-2-existential_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala +def foo: List[Class[T]] forSome { type T } // In Scala 3, Error: Existential types are no longer supported +~~~ +{% endtab %} +{% endtabs %} > Existential type is an experimental feature in Scala 2.13 that must be enabled explicitly either by importing `import scala.language.existentials` or by setting the `-language:existentials` compiler flag. -The proposed solution is to introduce an enclosing type that carries the dependent type: +In Scala 3, the proposed solution is to introduce an enclosing type that carries the dependent type: -```scala +{% tabs shared-existential_1 %} +{% tab 'Scala 2 and 3' %} +~~~ scala trait Bar { type T val value: List[Class[T]] } def foo: Bar -``` +~~~ +{% endtab %} +{% endtabs %} Note that using a wildcard argument, `_` or `?`, is often simpler but is not always possible. For instance you could replace `List[T] forSome { type T }` by `List[?]`. + +## Specialized + +The `@specialized` annotation from Scala 2 is ignored in Scala 3. + +However, there is limited support for specialized `Function` and `Tuple`. + +Similar benefits can be derived from `inline` declarations. + diff --git a/_overviews/scala3-migration/incompat-other-changes.md b/_overviews/scala3-migration/incompat-other-changes.md index 8fc2ae4402..a7a003d8ec 100644 --- a/_overviews/scala3-migration/incompat-other-changes.md +++ b/_overviews/scala3-migration/incompat-other-changes.md @@ -2,7 +2,7 @@ title: Other Changed Features type: section description: This chapter details all incompatibilities caused by changed features -num: 19 +num: 20 previous-page: incompat-contextual-abstractions next-page: incompat-type-checker --- @@ -25,14 +25,18 @@ Some other features are simplified or restricted to make the language easier, sa An inherited member, from a parent trait or class, can shadow an identifier defined in an outer scope. That pattern is called inheritance shadowing. -```scala +{% tabs shared-inheritance_1 %} +{% tab 'Scala 2 and 3' %} +~~~ scala object B { val x = 1 class C extends A { println(x) } } -``` +~~~ +{% endtab %} +{% endtabs %} For instance, in this preceding piece of code, the `x` term in C can refer to the `x` member defined in the outer class `B` or it can refer to a `x` member of the parent class `A`. You cannot know until you go to the definition of `A`. @@ -42,8 +46,9 @@ This is known for being error prone. That's why, in Scala 3, the compiler requires disambiguation if the parent class `A` does actually have a member `x`. It prevents the following piece of code from compiling. - -```scala +{% tabs scala-2-inheritance_2 %} +{% tab 'Scala 2 Only' %} +~~~ scala class A { val x = 2 } @@ -54,8 +59,11 @@ object B { println(x) } } -``` +~~~ +{% endtab %} +{% endtabs %} +But if you try to compile with Scala 3 you should see an error of the same kind as: {% highlight text %} -- [E049] Reference Error: src/main/scala/inheritance-shadowing.scala:9:14 9 | println(x) @@ -72,21 +80,24 @@ The [Scala 3 migration compilation](tooling-migration-mode.html) can automatical The Scala 3 compiler requires the constructor of private classes to be private. For instance, in the example: - -```scala +{% tabs scala-2-constructor_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala package foo private class Bar private[foo] () {} -``` +~~~ +{% endtab %} +{% endtabs %} -The error message is: -``` +If you try to compile in scala 3 you should get the following error message: +{% highlight text %} -- Error: /home/piquerez/scalacenter/scala-3-migration-guide/incompat/access-modifier/src/main/scala-2.13/access-modifier.scala:4:19 4 | private class Bar private[foo] () | ^ | non-private constructor Bar in class Bar refers to private class Bar | in its type signature (): foo.Foo.Bar -``` +{% endhighlight %} The [Scala 3 migration compilation](tooling-migration-mode.html) warns about this but no automatic rewrite is provided. @@ -97,8 +108,9 @@ The solution is to make the constructor private, since the class is private. In Scala 3, overriding a concrete def with an abstract def causes subclasses to consider the def abstract, whereas in Scala 2 it was considered as concrete. In the following piece of code, the `bar` method in `C` is considered concrete by the Scala 2.13 compiler but abstract by the Scala 3 compiler, causing the following error. - -```scala +{% tabs scala-2-abstract_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala trait A { def bar(x: Int): Int = x + 3 } @@ -107,10 +119,12 @@ trait B extends A { def bar(x: Int): Int } -class C extends B // Error: class C needs to be abstract, since def bar(x: Int): Int is not defined -``` +class C extends B // In Scala 3, Error: class C needs to be abstract, since def bar(x: Int): Int is not defined +~~~ +{% endtab %} +{% endtabs %} -This behavior was decided in [Dotty issue #4770](https://github.com/lampepfl/dotty/issues/4770). +This behavior was decided in [Dotty issue #4770](https://github.com/scala/scala3/issues/4770). An easy fix is simply to remove the abstract def, since in practice it had no effect in Scala 2. @@ -120,17 +134,20 @@ The companion object of a case class does not extend any of the `Function{0-23}` In particular, it does not inherit their methods: `tupled`, `curried`, `andThen`, `compose`... For instance, this is not permitted anymore: - -```scala +{% tabs scala-2-companion_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala case class Foo(x: Int, b: Boolean) Foo.curried(1)(true) Foo.tupled((2, false)) -``` +~~~ +{% endtab %} +{% endtabs %} A cross-compiling solution is to explicitly eta-expand the method `Foo.apply`. - {% highlight diff %} + -Foo.curried(1)(true) +(Foo.apply _).curried(1)(true) @@ -139,53 +156,68 @@ A cross-compiling solution is to explicitly eta-expand the method `Foo.apply`. {% endhighlight %} Or, for performance reasons, you can introduce an intermediate function value. - -```scala +{% tabs scala-3-companion_2 %} +{% tab 'Scala 2 and 3' %} +~~~ scala val fooCtr: (Int, Boolean) => Foo = (x, b) => Foo(x, b) fooCtr.curried(1)(true) fooCtr.tupled((2, false)) -``` +~~~ +{% endtab %} +{% endtabs %} ## Explicit Call to `unapply` In Scala, case classes have an auto-generated extractor method, called `unapply` in their companion object. Its signature has changed between Scala 2.13 and Scala 3. -The new signature is option-less (see the new [Pattern Matching](/scala3/reference/changed-features/pattern-matching.html) reference), which causes an incompatibility when `unapply` is called explicitly. +The new signature is option-less (see the new [Pattern Matching]({{ site.scala3ref }}/changed-features/pattern-matching.html) reference), which causes an incompatibility when `unapply` is called explicitly. Note that this problem does not affect user-defined extractors, whose signature stays the same across Scala versions. Given the following case class definition: - -```scala +{% tabs shared-unapply_1 %} +{% tab 'Scala 2 and 3' %} +~~~ scala case class Location(lat: Double, long: Double) -``` +~~~ +{% endtab %} +{% endtabs %} The Scala 2.13 compiler generates the following `unapply` method: - -```scala +{% tabs scala-2-unapply_2 %} +{% tab 'Scala 2 Only' %} +~~~ scala object Location { def unapply(location: Location): Option[(Double, Double)] = Some((location.lat, location.long)) } -``` +~~~ +{% endtab %} +{% endtabs %} Whereas the Scala 3 compiler generates: - -```scala +{% tabs scala-3-unapply_2 %} +{% tab 'Scala 3 Only' %} +~~~ scala object Location { def unapply(location: Location): Location = location } -``` - -Consequently the following code does not compile anymore. - -```scala +~~~ +{% endtab %} +{% endtabs %} + +Consequently the following code does not compile anymore in Scala 3. +{% tabs scala-2-unapply_3 %} +{% tab 'Scala 2 Only' %} +~~~ scala def tuple(location: Location): (Int, Int) = { - Location.unapply(location).get // [E008] Not Found Error: value get is not a member of Location + Location.unapply(location).get // [E008] In Scala 3, Not Found Error: value get is not a member of Location } -``` +~~~ +{% endtab %} +{% endtabs %} -A possible solution is to use pattern binding: +A possible solution, in Scala 3, is to use pattern binding: {% highlight diff %} def tuple(location: Location): (Int, Int) = { @@ -199,21 +231,24 @@ def tuple(location: Location): (Int, Int) = { The getter and setter methods generated by the `BeanProperty` annotation are now invisible in Scala 3 because their primary use case is the interoperability with Java frameworks. -For instance, in the below example: - -```scala +For instance, the below Scala 2 code would fail to compile in Scala 3: +{% tabs scala-2-bean_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala class Pojo() { @BeanProperty var fooBar: String = "" } val pojo = new Pojo() -pojo.setFooBar("hello") // [E008] Not Found Error: value setFooBar is not a member of Pojo +pojo.setFooBar("hello") // [E008] In Scala 3, Not Found Error: value setFooBar is not a member of Pojo -println(pojo.getFooBar()) // [E008] Not Found Error: value getFooBar is not a member of Pojo -``` +println(pojo.getFooBar()) // [E008] In Scala 3, Not Found Error: value getFooBar is not a member of Pojo +~~~ +{% endtab %} +{% endtabs %} -The solution is to call the more idiomatic `pojo.fooBar` getter and setter. +In Scala 3, the solution is to call the more idiomatic `pojo.fooBar` getter and setter. {% highlight diff %} val pojo = new Pojo() @@ -229,11 +264,11 @@ val pojo = new Pojo() A type of the form `=> T` cannot be used as an argument to a type parameter anymore. -This decision is explained in [this comment](https://github.com/lampepfl/dotty/blob/0f1a23e008148f76fd0a1c2991b991e1dad600e8/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala#L144-L152) of the Scala 3 source code. +This decision is explained in [this comment](https://github.com/scala/scala3/blob/0f1a23e008148f76fd0a1c2991b991e1dad600e8/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala#L144-L152) of the Scala 3 source code. For instance, it is not allowed to pass a function of type `Int => (=> Int) => Int` to the `uncurried` method since it would assign `=> Int` to the type parameter `T2`. -``` +{% highlight text %} -- [E134] Type Mismatch Error: src/main/scala/by-name-param-type-infer.scala:3:41 3 | val g: (Int, => Int) => Int = Function.uncurried(f) | ^^^^^^^^^^^^^^^^^^ @@ -244,7 +279,7 @@ For instance, it is not allowed to pass a function of type `Int => (=> Int) => I | [T1, T2, T3, R](f: T1 => T2 => T3 => R): (T1, T2, T3) => R | [T1, T2, R](f: T1 => T2 => R): (T1, T2) => R |match arguments ((Test.f : Int => (=> Int) => Int)) -``` +{% endhighlight %} The solution depends on the situation. In the given example, you can either: - define your own `uncurried` method with the appropriate signature @@ -254,15 +289,18 @@ The solution depends on the situation. In the given example, you can either: Scala 3 cannot reduce the application of a higher-kinded abstract type member to the wildcard argument. -For instance, the following example does not compile. - -```scala +For instance, the below Scala 2 code would fail to compile in Scala 3: +{% tabs scala-2-wildcard_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala trait Example { type Foo[A] - def f(foo: Foo[_]): Unit // [E043] Type Error: unreducible application of higher-kinded type Example.this.Foo to wildcard arguments + def f(foo: Foo[_]): Unit // [E043] In Scala 3, Type Error: unreducible application of higher-kinded type Example.this.Foo to wildcard arguments } -``` +~~~ +{% endtab %} +{% endtabs %} We can fix this by using a type parameter: @@ -272,10 +310,13 @@ We can fix this by using a type parameter: {% endhighlight %} But this simple solution does not work when `Foo` is itself used as a type argument. - -```scala +{% tabs scala-2-wildcard_2 %} +{% tab 'Scala 2 Only' %} +~~~ scala def g(foos: Seq[Foo[_]]): Unit -``` +~~~ +{% endtab %} +{% endtabs %} In such case, we can use a wrapper class around `Foo`: @@ -284,4 +325,4 @@ In such case, we can use a wrapper class around `Foo`: -def g(foos: Seq[Foo[_]]): Unit +def g(foos: Seq[FooWrapper[_]]): Unit -{% endhighlight %} +{% endhighlight %} \ No newline at end of file diff --git a/_overviews/scala3-migration/incompat-syntactic.md b/_overviews/scala3-migration/incompat-syntactic.md index 35846f886f..0e88e2b034 100644 --- a/_overviews/scala3-migration/incompat-syntactic.md +++ b/_overviews/scala3-migration/incompat-syntactic.md @@ -2,7 +2,7 @@ title: Syntactic Changes type: section description: This chapter details all the incompatibilities caused by syntactic changes -num: 16 +num: 17 previous-page: incompatibility-table next-page: incompat-dropped-features --- @@ -18,7 +18,7 @@ It is worth noting that most of the changes can be automatically handled during |--- |--- |--- |--- | |[Restricted keywords](#restricted-keywords)||✅|| |[Procedure syntax](#procedure-syntax)|Deprecation|✅|[✅](https://scalacenter.github.io/scalafix/docs/rules/ProcedureSyntax.html)| -|[Parentheses around lambda parameter](#parentheses-around-lambda-parameter)||✅|[✅](https://github.com/ohze/scala-rewrites/tree/dotty/#fixscala213parensaroundlambda)| +|[Parentheses around lambda parameter](#parentheses-around-lambda-parameter)||✅|| |[Open brace indentation for passing an argument](#open-brace-indentation-for-passing-an-argument)||✅|| |[Wrong indentation](#wrong-indentation)|||| |[`_` as a type parameter](#_-as-a-type-parameter)|||| @@ -38,18 +38,21 @@ It is composed of: - `=>>` - `?=>` -For instance, the following piece of code can be compiled with Scala 2.13 but not wtih Scala 3. +{% tabs scala-2-keywords_1 %} +{% tab 'Scala 2 Only' %} -```scala -object given { // Error: given is now a keyword - val enum = ??? // Error: enum is now a keyword +For instance, the following piece of code can be compiled with Scala 2.13 but not with Scala 3. +~~~ scala +object given { // In Scala 3, Error: given is now a keyword. + val enum = ??? // In Scala 3, Error: enum is now a keyword. - println(enum) // Error: enum is now a keyword + println(enum) // In Scala 3, Error: enum is now a keyword. } -``` +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the code into: - {% highlight diff %} -object given { +object `given` { @@ -58,31 +61,35 @@ The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the co - println(enum) + println(`enum`) -} + } {% endhighlight %} ## Procedure Syntax Procedure syntax has been deprecated for a while and it is dropped in Scala 3. -The following pieces of code are now illegal: -```scala +{% tabs scala-2-procedure_1 %} +{% tab 'Scala 2 Only' %} + +The following pieces of code are now illegal: +~~~ scala object Bar { - def print() { // Error: Procedure syntax no longer supported; `: Unit =` should be inserted here + def print() { // In Scala 3, Error: Procedure syntax no longer supported; `: Unit =` should be inserted here. println("bar") } } -``` +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the code into. - {% highlight diff %} -object Bar { + object Bar { - def print() { + def print(): Unit = { - println("bar") - } -} + println("bar") + } + } {% endhighlight %} ## Parentheses Around Lambda Parameter @@ -90,12 +97,15 @@ object Bar { When followed by its type, the parameter of a lambda is now required to be enclosed in parentheses. The following piece of code is invalid. -```scala -val f = { x: Int => x * x } // Error: parentheses are required around the parameter of a lambda -``` +{% tabs scala-2-lambda_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala +val f = { x: Int => x * x } // In Scala 3, Error: parentheses are required around the parameter of a lambda. +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the code into: - {% highlight diff %} -val f = { x: Int => x * x } +val f = { (x: Int) => x * x } @@ -106,42 +116,43 @@ The [Scala 3 migration compilation](tooling-migration-mode.html) rewrites the co In Scala 2 it is possible to pass an argument after a new line by enclosing it into braces. Although valid, this style of coding is not encouraged by the [Scala style guide](https://docs.scala-lang.org/style) and is no longer supported in Scala 3. -This syntax is now invalid: -```scala +{% tabs scala-2-brace_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala test("my test") -{ // Error: This opening brace will start a new statement in Scala 3. +{ // In Scala 3, Error: This opening brace will start a new statement. assert(1 == 1) } -``` +~~~ +{% endtab %} +{% endtabs %} The [Scala 3 migration compiler](tooling-migration-mode.html) indents the first line of the block. - {% highlight diff %} -test("my test") + test("my test") -{ + { - assert(1 == 1) -} + assert(1 == 1) + } {% endhighlight %} This migration rule applies to other patterns as well, such as refining a type after a new line. {% highlight diff %} -type Bar = Foo -- { -+ { - def bar(): Int -} + type Bar = Foo +-{ ++ { + def bar(): Int + } {% endhighlight %} A preferable solution is to write: - {% highlight diff %} -test("my test") -{ +test("my test") { - assert(1 == 1) -} + assert(1 == 1) + } {% endhighlight %} ## Wrong indentation @@ -149,23 +160,27 @@ A preferable solution is to write: The Scala 3 compiler now requires correct indentation. The following piece of code, that was compiled in Scala 2.13, does not compile anymore because of the indentation. -```scala +{% tabs scala-2-indentation_1 %} +{% tab 'Scala 2 Only' %} + +~~~ scala def bar: (Int, Int) = { val foo = 1.0 - val bar = foo // [E050] Type Error: value foo does not take parameters + val bar = foo // [E050] In Scala 3, type Error: value foo does not take parameters. (1, 1) -} // [E007] Type Mismatch Error: Found Unit, Required (Int, Int) -``` +} // [E007] In Scala 3, type Mismatch Error: Found Unit, Required (Int, Int). +~~~ +{% endtab %} +{% endtabs %} The indentation must be fixed. - {% highlight diff %} -def bar: (Int, Int) = { - val foo = 1.0 - val bar = foo + def bar: (Int, Int) = { + val foo = 1.0 + val bar = foo - (1, 1) + (1, 1) -} + } {% endhighlight %} These errors can be prevented by using a Scala formatting tool such as [scalafmt](https://scalameta.org/scalafmt/) or the [IntelliJ Scala formatter](https://www.jetbrains.com/help/idea/reformat-and-rearrange-code.html). @@ -176,10 +191,13 @@ Beware that these tools may change the entire code style of your project. The usage of the `_` identifier as a type parameter is permitted in Scala 2.13, even if it has never been mentioned in the Scala 2 specification. It is used in the API of [fastparse](https://index.scala-lang.org/lihaoyi/fastparse), in combination with a context bound, to declare an implicit parameter. - -```scala +{% tabs scala-2-identifier_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala def foo[_: Foo]: Unit = ??? -``` +~~~ +{% endtab %} +{% endtabs %} Here, the method `foo` takes a type parameter `_` and an implicit parameter of type `Foo[_]` where `_` refers to the type parameter, not the wildcard symbol. diff --git a/_overviews/scala3-migration/incompat-type-checker.md b/_overviews/scala3-migration/incompat-type-checker.md index 8c111a199f..41afc5ebc7 100644 --- a/_overviews/scala3-migration/incompat-type-checker.md +++ b/_overviews/scala3-migration/incompat-type-checker.md @@ -2,7 +2,7 @@ title: Type Checker type: section description: This chapter details the unsoundness fixes in the type checker -num: 20 +num: 21 previous-page: incompat-other-changes next-page: incompat-type-inference --- @@ -14,11 +14,13 @@ Scala 3 being based on stronger theoretical foundations, these unsoundness bugs ## Unsoundness Fixes in Variance checks In Scala 2, default parameters and inner-classes are not subject to variance checks. -It is unsound and might cause runtime failures, as demonstrated by this [test](https://github.com/lampepfl/dotty/blob/10526a7d0aa8910729b6036ee51942e05b71abf6/tests/neg/variances.scala) in the Scala 3 repository. +It is unsound and might cause runtime failures, as demonstrated by this [test](https://github.com/scala/scala3/blob/10526a7d0aa8910729b6036ee51942e05b71abf6/tests/neg/variances.scala) in the Scala 3 repository. The Scala 3 compiler does not permit this anymore. -```scala +{% tabs scala-2-unsound_vc_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala class Foo[-A](x: List[A]) { def f[B](y: List[B] = x): Unit = ??? } @@ -26,8 +28,11 @@ class Foo[-A](x: List[A]) { class Outer[+A](x: A) { class Inner(y: A) } -``` +~~~ +{% endtab %} +{% endtabs %} +So if you compile in Scala 3, you will get the following error. {% highlight text %} -- Error: src/main/scala/variance.scala:2:8 2 | def f[B](y: List[B] = x): Unit = y @@ -75,7 +80,9 @@ Scala 3 fixes some unsoundness bugs in pattern matching, preventing some semanti For instance, the match expression in `combineReq` can be compiled with Scala 2.13 but not with Scala 3. -```scala +{% tabs scala-2-unsound_pm_1 %} +{% tab 'Scala 2 Only' %} +~~~ scala trait Request case class Fetch[A](ids: Set[A]) extends Request @@ -88,9 +95,11 @@ object Request { } } } -``` +~~~ +{% endtab %} +{% endtabs %} -The error message is: +In Scala 3, the error message is: {% highlight text %} -- [E007] Type Mismatch Error: src/main/scala/pattern-match.scala:9:59 @@ -100,6 +109,7 @@ The error message is: | Required: Fetch[A$1] {% endhighlight %} + Which is right, there is no proof that `x` and `y` have the same type parameter `A`. Coming from Scala 2, this is clearly an improvement to help us locate mistakes in our code. @@ -108,9 +118,13 @@ It is not always easy and sometimes it is even not possible, in which case the c In this example, we can relax the constraint on `x` and `y` by stating that `A` is a common ancestor of both type arguments. This makes the compiler type-check the code successfully. - -```scala +{% tabs shared-unsound_pm_2 %} +{% tab 'Scala 2 and 3' %} +~~~ scala def combineFetch[A](x: Fetch[_ <: A], y: Fetch[_ <: A]): Fetch[A] = Fetch(x.ids ++ y.ids) -``` +~~~ +{% endtab %} +{% endtabs %} Alternatively, a general but unsafe solution is to cast. + diff --git a/_overviews/scala3-migration/incompat-type-inference.md b/_overviews/scala3-migration/incompat-type-inference.md index e279a440ec..bb6fc3052a 100644 --- a/_overviews/scala3-migration/incompat-type-inference.md +++ b/_overviews/scala3-migration/incompat-type-inference.md @@ -2,7 +2,7 @@ title: Type Inference type: section description: This chapter details the incompatibilities caused by the new type inference algorithm -num: 21 +num: 22 previous-page: incompat-type-checker next-page: options-intro --- @@ -14,26 +14,15 @@ The new algorithm is better than the old one, but sometime it can fail where Sca > It is always good practice to write the result types of all public values and methods explicitly. > It prevents the public API of your library from changing with the Scala version, because of different inferred types. -> +> > This can be done prior to the Scala 3 migration by using the [ExplicitResultTypes](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html) rule in Scalafix. ## Return Type of an Override Method In Scala 3 the return type of an override method is inferred by inheritance from the base method, whereas in Scala 2.13 it is inferred from the left hand side of the override method. -```scala -class Parent { - def foo: Foo = new Foo -} - -class Child extends Parent { - override def foo = new RichFoo(super.foo) -} -``` - -In this example, `Child#foo` returns a `RichFoo` in Scala 2.13 but a `Foo` in Scala 3. -It can lead to compiler errors as demonstrated below. - +{% tabs define_parent_child %} +{% tab 'Scala 2 and 3' %} ```scala class Foo @@ -48,13 +37,24 @@ class Parent { class Child extends Parent { override def foo = new RichFoo(super.foo) } +``` +{% endtab %} +{% endtabs %} + +In this example, `Child#foo` returns a `RichFoo` in Scala 2.13 but a `Foo` in Scala 3. +It can lead to compiler errors as demonstrated below. +{% tabs extend_parent_child %} +{% tab 'Scala 3 Only' %} +```scala (new Child).foo.show // Scala 3 error: value show is not a member of Foo ``` +{% endtab %} +{% endtabs %} In some rare cases involving implicit conversions and runtime casting it could even cause a runtime failure. -The solution is to make the return type of the override method explicit: +The solution is to make the return type of the override method explicit so that it matches what is inferred in 2.13: {% highlight diff %} class Child extends Parent { @@ -65,22 +65,35 @@ class Child extends Parent { ## Reflective Type -Scala 2 reflective calls are dropped and replaced by the broader [Programmatic Structural Types](/scala3/reference/changed-features/structural-types.html). +Scala 2 reflective calls are dropped and replaced by the broader [Programmatic Structural Types]({{ site.scala3ref }}/changed-features/structural-types.html). Scala 3 can imitate Scala 2 reflective calls by making `scala.reflect.Selectable.reflectiveSelectable` available wherever `scala.language.reflectiveCalls` is imported. -However the Scala 3 compiler does not infer structural types by default, and thus fails at compiling: +{% tabs define_structural %} +{% tab 'Scala 2 and 3' %} ```scala import scala.language.reflectiveCalls val foo = new { def bar: Unit = ??? } +``` +{% endtab %} +{% endtabs %} +However the Scala 3 compiler does not infer structural types by default. +It infers the type `Object` for `foo` instead of `{ def bar: Unit }`. +Therefore, the following structural selection fails to compile: + +{% tabs use_structural %} +{% tab 'Scala 3 Only' %} +```scala foo.bar // Error: value bar is not a member of Object ``` +{% endtab %} +{% endtabs %} -The straightforward solution is to write down the structural type. +The straightforward solution is to explicitly write down the structural type. {% highlight diff %} import scala.language.reflectiveCalls diff --git a/_overviews/scala3-migration/incompatibility-table.md b/_overviews/scala3-migration/incompatibility-table.md index 36e156a868..9fc9f8bf18 100644 --- a/_overviews/scala3-migration/incompatibility-table.md +++ b/_overviews/scala3-migration/incompatibility-table.md @@ -2,7 +2,7 @@ title: Incompatibility Table type: chapter description: This chapter list all the known incompatibilities between Scala 2.13 and Scala 3 -num: 15 +num: 16 previous-page: tooling-syntax-rewriting next-page: incompat-syntactic --- @@ -19,7 +19,7 @@ Each incompatibility is described by: - The existence of a Scalafix rule that can fix it > #### Scala 2.13 deprecations and feature warnings -> Run the 2.13 compilation with `-source:3` to locate those incompatibilities in the code. +> Run the 2.13 compilation with `-Xsource:3` to locate those incompatibilities in the code. > #### Scala 3 migration versus Scalafix rewrites > The Scala 3 migration mode comes out-of-the-box. @@ -37,7 +37,7 @@ Some of the old syntax is not supported anymore. |--- |--- |--- |--- | |[Restricted keywords](incompat-syntactic.html#restricted-keywords)||✅|| |[Procedure syntax](incompat-syntactic.html#procedure-syntax)|Deprecation|✅|[✅](https://scalacenter.github.io/scalafix/docs/rules/ProcedureSyntax.html)| -|[Parentheses around lambda parameter](incompat-syntactic.html#parentheses-around-lambda-parameter)||✅|[✅](https://github.com/ohze/scala-rewrites/tree/dotty/#fixscala213parensaroundlambda)| +|[Parentheses around lambda parameter](incompat-syntactic.html#parentheses-around-lambda-parameter)||✅|| |[Open brace indentation for passing an argument](incompat-syntactic.html#open-brace-indentation-for-passing-an-argument)||✅|| |[Wrong indentation](incompat-syntactic.html#wrong-indentation)|||| |[`_` as a type parameter](incompat-syntactic.html#_-as-a-type-parameter)|||| @@ -59,17 +59,17 @@ Some features are dropped to simplify the language. ### Contextual Abstractions -The redesign of [contextual abstractions]({% link _scala3-reference/contextual.md %}) brings some well defined incompatibilities. +The redesign of [contextual abstractions]({{ site.scala3ref }}/contextual) brings some well defined incompatibilities. |Incompatibility|Scala 2.13|Scala 3 Migration Rewrite|Scalafix Rule|Runtime Incompatibility| |--- |--- |--- |--- |--- | -|[Type of implicit def](incompat-contextual-abstractions.html#type-of-implicit-definition)|||[✅](https://github.com/ohze/scala-rewrites#fixexplicittypesexplicitimplicittypes)|| +|[Type of implicit def](incompat-contextual-abstractions.html#type-of-implicit-definition)|||[✅](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html)|| |[Implicit views](incompat-contextual-abstractions.html#implicit-views)||||**Possible**| |[View bounds](incompat-contextual-abstractions.html#view-bounds)|Deprecation|||| |[Ambiguous conversion on `A` and `=> A`](incompat-contextual-abstractions.html#ambiguous-conversion-on-a-and--a)||||| Furthermore we have changed the implicit resolution rules so that they are more useful and less surprising. -The new rules are described [here](/scala3/reference/changed-features/implicit-resolution.html). +The new rules are described [here]({{ site.scala3ref }}/changed-features/implicit-resolution.html). Because of these changes, the Scala 3 compiler could possibly fail at resolving some implicit parameters of existing Scala 2.13 code. diff --git a/_overviews/scala3-migration/options-intro.md b/_overviews/scala3-migration/options-intro.md index be7223a02e..9fc2d04d48 100644 --- a/_overviews/scala3-migration/options-intro.md +++ b/_overviews/scala3-migration/options-intro.md @@ -2,7 +2,7 @@ title: Compiler Options type: chapter description: This chapter shows the difference between Scala 2.13 and Scala 3 compiler options -num: 22 +num: 23 previous-page: incompat-type-inference next-page: options-lookup --- @@ -16,6 +16,6 @@ To do so you can refer to the [Lookup Table](options-lookup.html). > Passing an unavailable option to the Scala 3 compiler does not make it fail. > It just prints a warning and ignores the option. -You can also discover the new Scala 3 compiler options, that have no equivalent in Scala 2.13, in the [New Compiler Options](options-new.html) page. +You can also discover the new Scala 3 compiler options, which have no equivalent in Scala 2.13, in the [New Compiler Options](options-new.html) page. -For scaladoc settings reference and their compatibility with Scala2 scaladoc, read [Scaladoc settings compatibility between Scala2 and Scala3](scaladoc-settings-compatibility.html) page. +For Scaladoc settings reference and their compatibility with Scala2 Scaladoc, read [Scaladoc settings compatibility between Scala2 and Scala3](scaladoc-settings-compatibility.html) page. diff --git a/_overviews/scala3-migration/options-lookup.md b/_overviews/scala3-migration/options-lookup.md index 062166b176..36db00ad8e 100644 --- a/_overviews/scala3-migration/options-lookup.md +++ b/_overviews/scala3-migration/options-lookup.md @@ -2,31 +2,44 @@ title: Compiler Options Lookup Table type: section description: This section contains the compiler options lookup tables -num: 23 +num: 24 previous-page: options-intro next-page: options-new --- -The compiler options are classified and ordered according to their Scala 2.13 name. -Each Scala 2.13 option is associated with its status in Scala 3. +This table lists the Scala 2.13 compiler options with their equivalent in Scala 3. +Some options have cross-version support, such as `-Vprint`. +Others have a close equivalent with a different name. A number of Scala 2 options +have no equivalent in Scala 3, such as options for debugging Scala 2 macros. + +The compiler options are shown as displayed by the help output `scalac -help`, `scalac -X`, etc. +A few aliases are shown here, but most older aliases, such as `-Xprint` for `-Vprint`, +or `-Ytyper-debug` for `-Vtyper`, are listed by the latest name. + +The option groups `-V` and `-W` were introduced in Scala 2.13, for "verbose" options that +request additional diagnostic output and "warnings" that request additional checks which +may or may not indicate errors in code. `-Werror` elevates warnings to errors, and `-Wconf` +allows precise control over warnings by either ignoring them or taking them as errors. +The configuration string for `-Wconf` will likely require adjustment when migrating to Scala 3, +since the configuration syntax and the error messages it matches are different. | Status | Meaning | |-|-| -| <i class="fa fa-check fa-lg"></i> | it is available in Scala 3 | -| `<new-name>` | It has been renamed to `<new-name>` | -| <i class="fa fa-times fa-lg"></i> | It is not available in 3.0.0 but it could be added later | +| <i class="fa fa-check fa-lg"></i> | It is available in Scala 3. | +| `<new-name>` | It has been renamed to `<new-name>`. | +| <i class="fa fa-times fa-lg"></i> | It is not yet available but could be added later. | -> The current comparison is based on Scala 2.13.4 and 3.0.0-M3. +> The current comparison is based on Scala 2.13.10 and 3.3.0. ## Standard Settings -| 2.13.x | 3.0.x | +| 2.13.x | 3.3.x | |-|-| -| `-Dproperty=value` | <i class="fa fa-times fa-lg"></i> | -| `-J<flag>` | <i class="fa fa-times fa-lg"></i> | +| `-Dproperty=value` | <i class="fa fa-check fa-lg"></i> | +| `-J<flag>` | <i class="fa fa-check fa-lg"></i> | | `-P:<plugin>:<opt>` |<i class="fa fa-check fa-lg"></i>| -| `-V` | <i class="fa fa-times fa-lg"></i> | -| `-W` | <i class="fa fa-times fa-lg"></i> | +| `-V` | <i class="fa fa-check fa-lg"></i> | +| `-W` | <i class="fa fa-check fa-lg"></i> | | `-X` |<i class="fa fa-check fa-lg"></i>| | `-Y` |<i class="fa fa-check fa-lg"></i>| | `-bootclasspath` |<i class="fa fa-check fa-lg"></i>| @@ -63,12 +76,75 @@ Each Scala 2.13 option is associated with its status in Scala 3. | `-verbose` |<i class="fa fa-check fa-lg"></i>| | `-version` |<i class="fa fa-check fa-lg"></i>| +## Verbose Settings + +| 2.13.x | 3.3.x | +|-|-| +| `-Vbrowse:<phases>` | <i class="fa fa-times fa-lg"></i> | +| `-Vclasspath` | `-Ylog-classpath` | +| `-Vdebug` | `-Ydebug` | +| `-Vdebug-tasty` | <i class="fa fa-times fa-lg"></i> | +| `-Vdebug-type-error` | <i class="fa fa-times fa-lg"></i> | +| `-Vdoc` | <i class="fa fa-times fa-lg"></i> | +| `-Vfree-terms` | <i class="fa fa-times fa-lg"></i> | +| `-Vfree-types` | <i class="fa fa-times fa-lg"></i> | +| `-Vhot-statistics`| <i class="fa fa-times fa-lg"></i> | +| `-Vide`| <i class="fa fa-times fa-lg"></i> | +| `-Vimplicit-conversions`| <i class="fa fa-times fa-lg"></i> | +| `-Vimplicits`| <i class="fa fa-times fa-lg"></i> | +| `-Vimplicits-max-refined`| <i class="fa fa-times fa-lg"></i> | +| `-Vimplicits-verbose-tree`| <i class="fa fa-times fa-lg"></i> | +| `-Vinline <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | +| `-Vlog:<phases>` | `-Ylog:<phases>`| +| `-Vmacro` | <i class="fa fa-times fa-lg"></i> | +| `-Vmacro-lite` | <i class="fa fa-times fa-lg"></i> | +| `-Vopt <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | +| `-Vpatmat` | <i class="fa fa-times fa-lg"></i> | +| `-Vphases` | <i class="fa fa-check fa-lg"></i> | +| `-Vpos`| <i class="fa fa-times fa-lg"></i> | +| `-Vprint:<phases>` | <i class="fa fa-check fa-lg"></i> | +| `-Vprint-args <file>` | <i class="fa fa-times fa-lg"></i> | +| `-Vprint-pos` | `-Yprint-pos` | +| `-Vprint-types` | `-Xprint-types` | +| `-Vquasiquote` | <i class="fa fa-times fa-lg"></i> | +| `-Vreflective-calls` | <i class="fa fa-times fa-lg"></i> | +| `-Vreify` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow:<phases>` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow-class <class>` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow-member-pos <output style>` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow-object <object>` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow-symkinds` | <i class="fa fa-times fa-lg"></i> | +| `-Vshow-symowners` | <i class="fa fa-times fa-lg"></i> | +| `-Vstatistics <phases>` | <i class="fa fa-times fa-lg"></i> | +| `-Vsymbols` | <i class="fa fa-times fa-lg"></i> | +| `-Vtype-diffs` | <i class="fa fa-times fa-lg"></i> | +| `-Vtyper` | <i class="fa fa-times fa-lg"></i> | + +## Warning Settings + +| 2.13.x | 3.3.x | +|-|-| +| `-Wconf` | <i class="fa fa-check fa-lg"></i> | +| `-Wdead-code` | <i class="fa fa-times fa-lg"></i> | +| `-Werror` | <i class="fa fa-check fa-lg"></i> | +| `-Wextra-implicit` | <i class="fa fa-times fa-lg"></i> | +| `-Wmacros:<mode>` | <i class="fa fa-times fa-lg"></i> | +| `-Wnonunit-if` | <i class="fa fa-times fa-lg"></i> | +| `-Wnonunit-statement` | <i class="fa fa-check fa-lg"></i> | +| `-Wnumeric-widen` | <i class="fa fa-times fa-lg"></i> | +| `-Woctal-literal` | <i class="fa fa-times fa-lg"></i> | +| `-Wopt` | <i class="fa fa-times fa-lg"></i> | +| `-Wperformance` | <i class="fa fa-times fa-lg"></i> | +| `-Wself-implicit` | <i class="fa fa-times fa-lg"></i> | +| `-Wunused:<warnings>` | <i class="fa fa-check fa-lg"></i> | +| `-Wvalue-discard`| <i class="fa fa-check fa-lg"></i> | + ## Advanced Settings -| 2.13.x | 3.0.x | +| 2.13.x | 3.3.x | |-|-| -| `-X` |<i class="fa fa-check fa-lg"></i>| -| `-Xcheckinit` | `-Ycheck-init` | +| `-Xasync` | <i class="fa fa-times fa-lg"></i> | +| `-Xcheckinit` | `-Ysafe-init` | | `-Xdev` | <i class="fa fa-times fa-lg"></i> | | `-Xdisable-assertions` | <i class="fa fa-times fa-lg"></i> | | `-Xelide-below` | <i class="fa fa-times fa-lg"></i> | @@ -78,30 +154,25 @@ Each Scala 2.13 option is associated with its status in Scala 3. | `-Xjline` | <i class="fa fa-times fa-lg"></i> | | `-Xlint:deprecation` | `-deprecation` | | `-Xlint:<warnings>` | <i class="fa fa-times fa-lg"></i> | -| `-Xlog-implicit-conversion` | <i class="fa fa-times fa-lg"></i> | -| `-Xlog-implicits` | <i class="fa fa-times fa-lg"></i> | -| `-Xlog-reflective-calls` | <i class="fa fa-times fa-lg"></i> | | `-Xmacro-settings` | <i class="fa fa-times fa-lg"></i> | | `-Xmain-class` | <i class="fa fa-times fa-lg"></i> | | `-Xmaxerrs` | <i class="fa fa-times fa-lg"></i> | | `-Xmaxwarns` | <i class="fa fa-times fa-lg"></i> | -| `-Xmigration` |<i class="fa fa-check fa-lg"></i>| -| `-Xmixin-force-forwarders` |<i class="fa fa-check fa-lg"></i>| +| `-Xmigration` |<i class="fa fa-check fa-lg"></i>| +| `-Xmixin-force-forwarders` |<i class="fa fa-check fa-lg"></i>| | `-Xno-forwarders` |<i class="fa fa-check fa-lg"></i>| | `-Xno-patmat-analysis` | <i class="fa fa-times fa-lg"></i> | +| `-Xnon-strict-patmat-analysis` | <i class="fa fa-times fa-lg"></i> | | `-Xnojline` | <i class="fa fa-times fa-lg"></i> | -| `-Xplugin` |<i class="fa fa-check fa-lg"></i>| -| `-Xplugin-disable` |<i class="fa fa-check fa-lg"></i>| -| `-Xplugin-list` |<i class="fa fa-check fa-lg"></i>| -| `-Xplugin-require` |<i class="fa fa-check fa-lg"></i>| +| `-Xplugin` |<i class="fa fa-check fa-lg"></i>| +| `-Xplugin-disable` |<i class="fa fa-check fa-lg"></i>| +| `-Xplugin-list` |<i class="fa fa-check fa-lg"></i>| +| `-Xplugin-require` |<i class="fa fa-check fa-lg"></i>| | `-Xpluginsdir` |<i class="fa fa-check fa-lg"></i>| -| `-Xprint-args` | <i class="fa fa-times fa-lg"></i> | | `-Xprompt` |<i class="fa fa-check fa-lg"></i>| | `-Xreporter` | <i class="fa fa-times fa-lg"></i> | | `-Xresident` | <i class="fa fa-times fa-lg"></i> | | `-Xscript` | <i class="fa fa-times fa-lg"></i> | -| `-Xshow-class <class>` | <i class="fa fa-times fa-lg"></i> | -| `-Xshow-object <object>` | <i class="fa fa-times fa-lg"></i> | | `-Xsource` | `-source` | | `-Xsource-reader` | <i class="fa fa-times fa-lg"></i> | | `-Xverify` | `-Xverify-signatures` | @@ -121,14 +192,10 @@ Each Scala 2.13 option is associated with its status in Scala 3. | `-Ydelambdafy` | <i class="fa fa-times fa-lg"></i> | | `-Ydump-classes` |<i class="fa fa-check fa-lg"></i>| | `-Ygen-asmp` | <i class="fa fa-times fa-lg"></i> | -| `-Yhot-statistics` | <i class="fa fa-times fa-lg"></i> | -| `-Yide-debug` | <i class="fa fa-times fa-lg"></i> | | `-Yimports` | <i class="fa fa-times fa-lg"></i> | | `-Yissue-debug` | <i class="fa fa-times fa-lg"></i> | | `-Yjar-compression-level` | <i class="fa fa-times fa-lg"></i> | | `-YjarFactory` | <i class="fa fa-times fa-lg"></i> | -| `-Ymacro-debug-lite` | <i class="fa fa-times fa-lg"></i> | -| `-Ymacro-debug-verbose` | <i class="fa fa-times fa-lg"></i> | | `-Ymacro-annotations` | <i class="fa fa-times fa-lg"></i> | | `-Ymacro-classpath` | <i class="fa fa-times fa-lg"></i> | | `-Ymacro-expand` | <i class="fa fa-times fa-lg"></i> | @@ -139,109 +206,37 @@ Each Scala 2.13 option is associated with its status in Scala 3. | `-Yno-imports` |<i class="fa fa-check fa-lg"></i>| | `-Yno-predef` |<i class="fa fa-check fa-lg"></i>| | `-Yopt-inline-heuristics` | <i class="fa fa-times fa-lg"></i> | -| `-Yopt-log-inline <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | -| `-Yopt-trace <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | -| `-Ypatmat-debug` | <i class="fa fa-times fa-lg"></i> | | `-Ypatmat-exhaust-depth` | <i class="fa fa-times fa-lg"></i> | -| `-Ypos-debug` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-any-thread` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-debug` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-delay` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-locate-source-file` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-log` | <i class="fa fa-times fa-lg"></i> | +| `-Ypresentation-replay` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-strict` | <i class="fa fa-times fa-lg"></i> | | `-Ypresentation-verbose` | <i class="fa fa-times fa-lg"></i> | | `-Yprint-trees` | <i class="fa fa-times fa-lg"></i> | -| `-Yprofile-destination` |<i class="fa fa-check fa-lg"></i>| +| `-Yprofile-destination` |<i class="fa fa-check fa-lg"></i>| | `-Yprofile-enabled` |<i class="fa fa-check fa-lg"></i>| +| `-Yprofile-external-tool` |<i class="fa fa-check fa-lg"></i>| +| `-Yprofile-run-gc` |<i class="fa fa-check fa-lg"></i>| | `-Yprofile-trace` | <i class="fa fa-times fa-lg"></i> | -| `-Yquasiquote-debug` | <i class="fa fa-times fa-lg"></i> | | `-Yrangepos` | <i class="fa fa-times fa-lg"></i> | | `-Yrecursion` | <i class="fa fa-times fa-lg"></i> | | `-Yreify-copypaste` | <i class="fa fa-times fa-lg"></i> | -| `-Yreify-debug` | <i class="fa fa-times fa-lg"></i> | | `-Yrepl-class-based` | <i class="fa fa-times fa-lg"></i> | | `-Yrepl-outdir` | <i class="fa fa-times fa-lg"></i> | | `-Yrepl-use-magic-imports` | <i class="fa fa-times fa-lg"></i> | | `-Yresolve-term-conflict` |<i class="fa fa-check fa-lg"></i>| +| `-Yscala3-implicit-resolution` | <i class="fa fa-times fa-lg"></i> | | `-Yscriptrunner` | <i class="fa fa-times fa-lg"></i> | -| `-Yskip` |<i class="fa fa-check fa-lg"></i>| -| `-Yshow:<phases>` | <i class="fa fa-times fa-lg"></i> | -| `-Yshow-member-pos <output style>` | <i class="fa fa-times fa-lg"></i> | -| `-Yshow-symkinds` | <i class="fa fa-times fa-lg"></i> | -| `-Yshow-symowners` | <i class="fa fa-times fa-lg"></i> | -| `-Yshow-syms` | <i class="fa fa-times fa-lg"></i> | -| `-Ystatistics <phases>` | <i class="fa fa-times fa-lg"></i> | -| `-Ystop-after` |<i class="fa fa-check fa-lg"></i>| -| `-Ystop-before` |<i class="fa fa-check fa-lg"></i>| -| `-Ytyper-debug` | <i class="fa fa-times fa-lg"></i> | +| `-Yskip` |<i class="fa fa-check fa-lg"></i>| +| `-Ystop-after` |<i class="fa fa-check fa-lg"></i>| +| `-Ystop-before` |<i class="fa fa-check fa-lg"></i>| +| `-Ytasty-no-annotations` | <i class="fa fa-times fa-lg"></i> | +| `-Ytasty-reader` | <i class="fa fa-times fa-lg"></i> | +| `-Ytrack-dependencies` | <i class="fa fa-times fa-lg"></i> | | `-Yvalidate-pos` | <i class="fa fa-times fa-lg"></i> | -| `-Ywarn-dead-code` | <i class="fa fa-times fa-lg"></i> | -| `-Ywarn-numeric-widen` | <i class="fa fa-times fa-lg"></i> | -| `-Ywarn-unused:<warnings>` | <i class="fa fa-times fa-lg"></i> | -| `-Ywarn-value-discard` | <i class="fa fa-times fa-lg"></i> | - -## Verbose Settings - -Verbose settings were introduced in 2.13. -Most of them are not yet implemented in Scala 3. - -| 2.13.x | 3.0.x | -|-|-| -| `-Vbrowse:<phases>` | <i class="fa fa-times fa-lg"></i> | -| `-Vdebug-tasty` | <i class="fa fa-times fa-lg"></i> | -| `-Vdoc` | <i class="fa fa-times fa-lg"></i> | -| `-Vfree-terms` | <i class="fa fa-times fa-lg"></i> | -| `-Vfree-types` | <i class="fa fa-times fa-lg"></i> | -| `-Vhot-statistics`| <i class="fa fa-times fa-lg"></i> | -| `-Vide`| <i class="fa fa-times fa-lg"></i> | -| `-Vimplicit-conversions`| <i class="fa fa-times fa-lg"></i> | -| `-Vimplicits`| <i class="fa fa-times fa-lg"></i> | -| `-Vinline <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | -| `-Vissue`| <i class="fa fa-times fa-lg"></i> | -| `-Vmacro` | <i class="fa fa-times fa-lg"></i> | -| `-Vmacro-lite` | <i class="fa fa-times fa-lg"></i> | -| `-Vopt <package/Class.method>` | <i class="fa fa-times fa-lg"></i> | -| `-Vpatmat` | <i class="fa fa-times fa-lg"></i> | -| `-Vpos`| <i class="fa fa-times fa-lg"></i> | -| `-Vprint:<phases>` | `-Xprint:<phases>` | -| `-Vphases` | `-Xshow-phases` | -| `-Vclasspath` | `-Ylog-classpath` | -| `-Vlog:<phases>` | `-Ylog:<phases>`| -| `-Vdebug` | `-Ydebug` | -| `-Vprint-args <file>` | <i class="fa fa-times fa-lg"></i> | -| `-Vprint-pos` | `-Yprint-pos` | -| `-Vprint-types` | `-Xprint-types` | -| `-Vquasiquote` | <i class="fa fa-times fa-lg"></i> | -| `-Vreflective-calls` | <i class="fa fa-times fa-lg"></i> | -| `-Vreify` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow:<phases>` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow-class <class>` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow-member-pos <output style>` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow-object <object>` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow-symkinds` | <i class="fa fa-times fa-lg"></i> | -| `-Vshow-symowners` | <i class="fa fa-times fa-lg"></i> | -| `-Vstatistics <phases>` | <i class="fa fa-times fa-lg"></i> | -| `-Vsymbols` | <i class="fa fa-times fa-lg"></i> | -| `-Vtyper` | <i class="fa fa-times fa-lg"></i> | - -## Warning Settings - -Warning settings were introduced in 2.13. -Most of them are not yet implemented in Scala 3. - -| 2.13.x | 3.0.x | -|-|-| -| `-Wconf` | <i class="fa fa-times fa-lg"></i> | -| `-Wdead-code` | <i class="fa fa-times fa-lg"></i> | -| `-Werror` | `-Xfatal-warnings` | -| `-Wextra-implicit` | <i class="fa fa-times fa-lg"></i> | -| `-Wmacros:<mode>` | <i class="fa fa-times fa-lg"></i> | -| `-Wnumeric-widen` | <i class="fa fa-times fa-lg"></i> | -| `-Woctal-literal` | <i class="fa fa-times fa-lg"></i> | -| `-Wunused:<warnings>` | <i class="fa fa-times fa-lg"></i> | -| `-Wvalue-discard`| <i class="fa fa-times fa-lg"></i> | -| `-Wself-implicit` | <i class="fa fa-times fa-lg"></i> | ## Compiler Plugins diff --git a/_overviews/scala3-migration/options-new.md b/_overviews/scala3-migration/options-new.md index 8c6227f2a2..d101412f95 100644 --- a/_overviews/scala3-migration/options-new.md +++ b/_overviews/scala3-migration/options-new.md @@ -2,7 +2,7 @@ title: New Compiler Options type: section description: This chapter lists all the new compiler options in Scala 3 -num: 24 +num: 25 previous-page: options-lookup next-page: scaladoc-settings-compatibility --- @@ -32,20 +32,28 @@ The current page only contains the options that were added in Scala 3.0.x. | `-siteroot` | A directory containing static files from which to generate documentation. Default: ./docs. | | `-sourceroot` | Specify workspace root directory. Default: .. | +## Verbose settings + +| 3.2.x | description | +|-|-| +| `-Vprofile` | Show metrics about sources and internal representations to estimate compile-time complexity. | +| `-Vprofile-sorted-by:<column-name>` | Show metrics about sources and internal representations sorted by given column name. | +| `-Vprofile-details N` | Like -Vprofile, but also show metrics about sources and internal representations of the N most complex methods | + ## Advanced settings | 3.0.x | description | |-|-| -| `-Xignore-scala2-macros` | Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime. | -| `-Ximport-suggestion-timeout` | Timeout (in ms) for searching for import suggestions when errors are reported. | -| `-Xmax-inlined-trees` | Maximal number of inlined trees. Default: 2000000 | -| `-Xmax-inlines` | Maximal number of successive inlines. Default: 32. | -| `-Xprint-diff` | Print changed parts of the tree since last print. | -| `-Xprint-diff-del` | Print changed parts of the tree since last print including deleted parts. | -| `-Xprint-inline` | Show where inlined code comes from. | -| `-Xprint-suspension` | Show when code is suspended until macros are compiled. | -| `-Xrepl-disable-display` | Do not display definitions in REPL. | -| `-Xwiki-syntax` | Retains the Scala2 behavior of using Wiki Syntax in Scaladoc. | +| `-Xignore-scala2-macros` | Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime. | +| `-Ximport-suggestion-timeout` | Timeout (in ms) for searching for import suggestions when errors are reported. | +| `-Xmax-inlined-trees` | Maximal number of inlined trees. Default: 2000000 | +| `-Xmax-inlines` | Maximal number of successive inlines. Default: 32. | +| `-Xprint-diff` | Print changed parts of the tree since last print. | +| `-Xprint-diff-del` | Print changed parts of the tree since last print including deleted parts. | +| `-Xprint-inline` | Show where inlined code comes from. | +| `-Xprint-suspension` | Show when code is suspended until macros are compiled. | +| `-Xrepl-disable-display` | Do not display definitions in REPL. | +| `-Xwiki-syntax` | Retains the Scala2 behavior of using Wiki Syntax in Scaladoc. | ## Private settings diff --git a/_overviews/scala3-migration/plugin-intro.md b/_overviews/scala3-migration/plugin-intro.md index cd2155b626..96bfc18078 100644 --- a/_overviews/scala3-migration/plugin-intro.md +++ b/_overviews/scala3-migration/plugin-intro.md @@ -2,7 +2,7 @@ title: Compiler Plugins type: chapter description: This section shows how to migrate from using Scala 2 compiler plugins -num: 26 +num: 27 previous-page: options-new next-page: plugin-kind-projector --- diff --git a/_overviews/scala3-migration/plugin-kind-projector.md b/_overviews/scala3-migration/plugin-kind-projector.md index 8557cdb559..ab996ec586 100644 --- a/_overviews/scala3-migration/plugin-kind-projector.md +++ b/_overviews/scala3-migration/plugin-kind-projector.md @@ -2,18 +2,18 @@ title: Kind Projector Migration type: section description: This section shows how to migrate from the kind-projector plugin to Scala 3 kind-projector syntax -num: 27 +num: 28 previous-page: plugin-intro next-page: external-resources --- In the future, Scala 3 will use the `_` underscore symbol for placeholders in type lambdas---just as the underscore is currently used for placeholders in (ordinary) term-level lambdas. -The new type lambda syntax is not enabled by default, to enable it, use a compiler flag `-Ykind-projector:underscores`. Note that enabling underscore type lambdas will disable usage of `_` as a wildcard, you will only be able to write wildcards using the `?` symbol. +The new type lambda syntax is not enabled by default, to enable it, use a compiler flag `-Ykind-projector:underscores`. Note that enabling underscore type lambdas will disable usage of `_` as a wildcard, you will only be able to write wildcards using the `?` symbol. If you wish to cross-compile a project for Scala 2 & Scala 3 while using underscore type lambdas for both, you may do so starting with [kind-projector](https://github.com/typelevel/kind-projector) version `0.13.0` and up and Scala 2 versions `2.13.6` and `2.12.14`. To enable it, add the compiler flags `-Xsource:3 -P:kind-projector:underscore-placeholders` to your build. -As in Scala 3, this will disable usage of `_` as a wildcard, however, the flag `-Xsource:3` will allow you to replace it with the `?` symbol. +As in Scala 3, this will disable usage of `_` as a wildcard, however, the flag `-Xsource:3` will allow you to replace it with the `?` symbol. The following `sbt` configuration will set up the correct flags to cross-compile with new syntax: @@ -34,31 +34,49 @@ In turn, you will also have to rewrite all usages of `_` as the wildcard to use For example the following usage of the wildcard: +{% tabs wildcard_scala2 %} +{% tab 'Scala 2 Only' %} ```scala -def getWidget(widgets: Set[_ <: Widget], name: String): Option[Widget] = widgets.find(_.name == name) +def getWidget(widgets: Set[_ <: Widget], name: String): Option[Widget] = + widgets.find(_.name == name) ``` +{% endtab %} +{% endtabs %} Must be rewritten to: +{% tabs wildcard_scala3 %} +{% tab 'Scala 3 Only' %} ```scala -def getWidget(widgets: Set[? <: Widget], name: String): Option[Widget] = widgets.find(_.name == name) +def getWidget(widgets: Set[? <: Widget], name: String): Option[Widget] = + widgets.find(_.name == name) ``` +{% endtab %} +{% endtabs %} And the following usages of kind-projector's `*` placeholder: +{% tabs kind_projector_scala2 %} +{% tab 'Scala 2 Only' %} ```scala Tuple2[*, Double] // equivalent to: type R[A] = Tuple2[A, Double] Either[Int, +*] // equivalent to: type R[+A] = Either[Int, A] Function2[-*, Long, +*] // equivalent to: type R[-A, +B] = Function2[A, Long, B] ``` +{% endtab %} +{% endtabs %} Must be rewritten to: +{% tabs kind_projector_scala3 %} +{% tab 'Scala 3 Only' %} ```scala Tuple2[_, Double] // equivalent to: type R[A] = Tuple2[A, Double] Either[Int, +_] // equivalent to: type R[+A] = Either[Int, A] Function2[-_, Long, +_] // equivalent to: type R[-A, +B] = Function2[A, Long, B] ``` +{% endtab %} +{% endtabs %} ## Compiling Existing Code @@ -66,11 +84,15 @@ Even without migrating to underscore type lambdas, you will likely be able to co Use the flag `-Ykind-projector` to enable support for `*`-based type lambdas (without enabling underscore type lambdas), the following forms will now compile: +{% tabs kind_projector_cross %} +{% tab 'Scala 2 and 3' %} ```scala Tuple2[*, Double] // equivalent to: type R[A] = Tuple2[A, Double] Either[Int, +*] // equivalent to: type R[+A] = Either[Int, A] Function2[-*, Long, +*] // equivalent to: type R[-A, +B] = Function2[A, Long, B] ``` +{% endtab %} +{% endtabs %} ## Rewriting Incompatible Constructs @@ -82,6 +104,8 @@ Scala 3's `-Ykind-projector` & `-Ykind-projector:underscores` implement only a s You must rewrite ALL of the following forms: +{% tabs kind_projector_illegal_scala2 %} +{% tab 'Scala 2 Only' %} ```scala // classic EitherT[*[_], Int, *] // equivalent to: type R[F[_], B] = EitherT[F, Int, B] @@ -92,36 +116,58 @@ EitherT[_[_], Int, _] // equivalent to: type R[F[_], B] = EitherT[F, Int, B] // named Lambda Lambda[(F[_], A) => EitherT[F, Int, A]] ``` +{% endtab %} +{% endtabs %} Into the following long-form to cross-compile with Scala 3: +{% tabs kind_projector_illegal_cross %} +{% tab 'Scala 2 and 3' %} ```scala type MyLambda[F[_], A] = EitherT[F, Int, A] MyLambda ``` +{% endtab %} +{% endtabs %} -Alternatively you may use Scala 3's [Native Type Lambdas](https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas.html) if you do not need to cross-compile: +Alternatively you may use Scala 3's [Native Type Lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html) if you do not need to cross-compile: +{% tabs kind_projector_illegal_scala3 %} +{% tab 'Scala 3 Only' %} ```scala [F[_], A] =>> EitherT[F, Int, A] ``` +{% endtab %} +{% endtabs %} For `Lambda` you must rewrite the following form: +{% tabs kind_projector_illegal_lambda_scala2 %} +{% tab 'Scala 2 Only' %} ```scala Lambda[(`+E`, `+A`) => Either[E, A]] ``` +{% endtab %} +{% endtabs %} To the following to cross-compile: +{% tabs kind_projector_illegal_lambda_cross %} +{% tab 'Scala 2 and 3' %} ```scala λ[(`+E`, `+A`) => Either[E, A]] ``` +{% endtab %} +{% endtabs %} Or alternatively to Scala 3 type lambdas: +{% tabs kind_projector_illegal_lambda_scala3 %} +{% tab 'Scala 3 Only' %} ```scala [E, A] =>> Either[E, A] ``` +{% endtab %} +{% endtabs %} Note: Scala 3 type lambdas no longer need `-` or `+` variance markers on parameters, these are now inferred. diff --git a/_overviews/scala3-migration/scala3-migrate.md b/_overviews/scala3-migration/scala3-migrate.md index 4a6c0f98bb..e4c66d9ac4 100644 --- a/_overviews/scala3-migration/scala3-migrate.md +++ b/_overviews/scala3-migration/scala3-migrate.md @@ -1,320 +1,444 @@ --- -title: Scala3-migrate plugin (sbt) -type: chapter +title: Porting an sbt Project (using sbt-scala3-migrate) +type: section description: This section shows how to use scala3-migrate to migrate a project -num: 7 -previous-page: tooling-tour -next-page: tooling-migration-mode +num: 11 +previous-page: tutorial-prerequisites +next-page: tutorial-sbt --- -## Context +`sbt-scala3-migrate` is an sbt plugin to assist you during the migration of your sbt project to Scala 3. +It consists of four sbt commands: +- `migrateDependencies` helps you update the list of `libraryDependencies` +- `migrateScalacOptions` helps you update the list of `scalacOptions` +- `migrateSyntax` fixes a number of syntax incompatibilities between Scala 2.13 and Scala 3 +- `migrateTypes` tries to compile your code to Scala 3 by infering types and resolving implicits where needed. -Scala3-migrate tool is part of a series of initiatives to make the migration to Scala 3 as easy as possible. -The goal is to provide a tool that will help you migrate both your build and your code to Scala 3. -The solution consists of 4 independent steps that are packaged into an sbt plugin: - -- **migrate-libs**: helps you update the list of `libraryDependencies` -- **migrate-scalacOptions**: helps you update the list of `scalacOptions` -- **migrate-syntax**: fixes a number of syntax incompatibilities in Scala 2.13 code -- **migrate**: tries to make your code compile with Scala 3 by adding the minimum required inferred types and implicit arguments. - -Each one of these steps is an sbt command that is described in details below. +Each one of these commands is described in details below. > #### Requirements -> - Scala 2.13, preferred 2.13.7 +> - Scala 2.13, preferred 2.13.13 > - sbt 1.5 or higher > - **Disclaimer:** This tool cannot migrate libraries containing macros. +> +> #### Recommendation +> Before the migration, add `-Xsource:3` to your scalac options to enable Scala 3 migration warnings in the Scala 2 compiler. +> See the page [Scala 2 with -Xsource:3](tooling-scala2-xsource3.html) for more details. -## Installation +In this tutorial, we will migrate the project in [scalacenter/scala3-migration-example](https://github.com/scalacenter/scala3-migration-example). +To learn about the migration, and train yourself, you can clone this repository and follow the tutorial steps. -Currently, you can use scala3-migrate via an sbt plugin. You can add it as follows to your build. +## 1. Installation + +Add `sbt-scala3-migrate` in the `project/plugins.sbt` file of your sbt project. ``` scala // project/plugins.sbt -addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.5.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.6.1") ``` -<div><p>The latest published version is <a href="https://index.scala-lang.org/scalacenter/scala3-migrate/scala3-migrate"> -<img src="https://index.scala-lang.org/scalacenter/scala3-migrate/scala3-migrate/latest-by-scala-version.svg" alt="scala3-migrate Scala version support" style="margin-bottom: 0px; max-width: 30%;"> + +<div><p>The latest published version is <a href="https://index.scala-lang.org/scalacenter/scala3-migrate/sbt-scala3-migrate"> +<img src="https://index.scala-lang.org/scalacenter/scala3-migrate/sbt-scala3-migrate/latest-by-scala-version.svg" alt="scala3-migrate Scala version support" style="margin-bottom: 0px; max-width: 30%;"> </a></p> </div> -## Choose a module +## 2. Choose a module + If your project contains more than one module, the first step is to choose which module to migrate first. -Follow [this section to choose the first module.](tutorial-sbt.html#2-choose-a-module) -> Scala3-migrate operates on one module at a time. -> Make sure the module you choose is not an aggregate of modules. +Thanks to the interoperability between Scala 2.13 and Scala 3 you can start with any module. +However it is probably simpler to start with the module that has the fewest dependencies. + +> `sbt-scala3-migrate` operates on one module at a time. +> Make sure the module you choose is not an aggregate. + +## 3. Migrate the dependencies -## Migrate library dependencies -> All the commands will be run in an sbt shell +> All the commands in this tutorial must be run in the sbt shell. -**Usage:** `migrate-libs projectId` where projectId is the name of the module to migrate. +**Usage:** `migrateDependencies <project>` + +For the purpose of this tutorial we will consider the following build configuration: -For example, let's migrate the following sbt build. ```scala //build.sbt lazy val main = project .in(file(".")) .settings( - name := "main", - scalaVersion := "2.13.7", - semanticdbEnabled := true, - scalacOptions ++= Seq("-explaintypes", "-Wunused"), + scalaVersion := "2.13.11", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.2.0", - "ch.epfl.scala" % "scalafix-interfaces" % "0.9.26", - "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test, - "ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % Test, - compilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full), - compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") - ) + "org.typelevel" %% "cats-core" % "2.4.0", + "io.github.java-diff-utils" % "java-diff-utils" % "4.12", + "org.scalameta" %% "parsers" % "4.8.9", + "org.scalameta" %% "munit" % "0.7.23" % Test, + "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test + ), + addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)), + addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") ) ``` -First, let's run the command and see the result. -``` -> migrate-libs main -[info] Starting to migrate libDependencies for main -[info] -[info] X : Cannot be updated to Scala 3 -[info] Valid : Already a valid version for Scala 3 -[info] To be updated : Need to be updated to the following version -[info] -[info] com.softwaremill.scalamacrodebug:macros:0.4.1:test -> X : Contains Macros and is not yet published for Scala 3. -[info] com.olegpy:better-monadic-for:0.3.1:plugin->default(compile) -> X : Scala 2 compiler plugins are not supported in Scala 3. You need to find an alternative. -[info] "ch.epfl.scala" % "scalafix-interfaces" % "0.9.26" -> Valid : Java libraries are compatible. -[info] ch.epfl.scala:scalafix-rules:0.9.26:test -> "ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % "test" cross CrossVersion.for3Use2_13 : It's only safe to use the 2.13 version if it's inside an application. -[info] org.typelevel:cats-core:2.2.0 -> "org.typelevel" %% "cats-core" % "2.6.1" : Other versions are avaialble for Scala 3: "2.7.0" -[info] org.typelevel:kind-projector:0.11.0:plugin->default(compile) -> -Ykind-projector : This compiler plugin has a scalacOption equivalent. Add it to your scalacOptions. -``` - -### Valid libraries -``` -[info] ch.epfl.scala:scalafix-interfaces:0.9.26 -> Valid +Running `migrateDependencies main` outputs: + +<pre> +<code class="hljs hljs-skip"> +sbt:main> migrateDependencies main +[info] +[info] Starting migration of libraries and compiler plugins of project main +[info] +[info] <span style="color:green">Valid dependencies:</span> +[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12" + +[warn] +[warn] <span style="color:orange">Versions to update:</span> +[warn] "org.typelevel" %% "cats-core" % "<span style="color:orange">2.6.1</span>" <span style="color:orange">(Other versions: 2.7.0, ..., 2.10.0)</span> +[warn] "org.scalameta" %% "munit" % "<span style="color:orange">0.7.25</span>" % Test <span style="color:orange">(Other versions: 0.7.26, ..., 1.0.0-M8)</span> +[warn] +[warn] <span style="color:orange">For Scala 3 use 2.13:</span> +[warn] ("org.scalameta" %% "parsers" % "4.8.9")<span style="color:orange">.cross(CrossVersion.for3Use2_13)</span> +[warn] +[warn] <span style="color:orange">Integrated compiler plugins:</span> +[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) +[warn] replaced by <span style="color:orange">scalacOptions += "-Ykind-projector"</span> +[error] +[error] <span style="color:red">Incompatible Libraries:</span> +[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test <span style="color:red">(Macro Library)</span> +[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") <span style="color:red">(Compiler Plugin)</span> +[info] +[success] Total time: 0 s, completed Aug 28, 2023 9:18:04 AM +</code> +</pre> + +Let's take a closer look at each part of this output message. + +### Valid dependencies + +Valid dependencies are compatible with Scala 3, either because they are standard Java libraries or because they have been cross-published to Scala 3. + +<pre> +<code class="hljs hljs-skip"> +[info] <span style="color:green">Valid dependencies:</span> +[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12" +</code> +</pre> + +You can keep them as they are. + +### Versions to update + +These libraries have been cross-published to Scala 3 in later versions. +You need to update their versions. + +<pre> +<code class="hljs hljs-skip"> +[warn] <span style="color:orange">Versions to update:</span> +[warn] "org.typelevel" %% "cats-core" % "<span style="color:orange">2.6.1</span>" <span style="color:orange">(Other versions: 2.7.0, ..., 2.10.0)</span> +[warn] "org.scalameta" %% "munit" % "<span style="color:orange">0.7.25</span>" % Test <span style="color:orange">(Other versions: 0.7.26, ..., 1.0.0-M8)</span> +</code> +</pre> + +In the given example we need to bump the version of cats-core to 2.6.1 and the version of munit to 0.7.25. + +> The `Other versions` part of the output message indicates which other versions are available in Scala 3. +If you wish you can bump to one of the most recent version, but take care of choosing a source compatible version. +According to [the semantic versionning scheme](https://semver.org/), a patch or minor version bump is safe but not a major version bump. + + +### For Scala 3 use 2.13 + +These libraries are not yet cross-published to Scala 3 but they are cross-compatible. +You can use their 2.13 versions to compile to Scala 3. + +Add `.cross(CrossVersion.for3Use2_13)` on the libraries to tell sbt to use the `_2.13` suffix, instead of `_3`. + +<pre> +<code class="hljs hljs-skip"> +[warn] <span style="color:orange">For Scala 3 use 2.13:</span> +[warn] ("org.scalameta" %% "parsers" % "4.8.9")<span style="color:orange">.cross(CrossVersion.for3Use2_13)</span> +</code> +</pre> + +> #### Disclaimer about `CrossVersion.for3Use2_13`: +- It can cause a conflict on the `_2.13` and `_3` suffixes of a transitive dependency. +In such situation, sbt will fail to resolve the dependency, with a clear error message. +- It is generally not safe to publish a Scala 3 library which depends on a Scala 2.13 library. +Otherwise users of the library can have conflicting `_2.13` and `_3` suffixes on the same dependency. + +### Integrated compiler plugins + +Some compiler plugins were integrated into the Scala 3 compiler itself. +In Scala 3 you don't need to resolve them as dependencies but you can activate them with compiler flags. + +<pre> +<code class="hljs hljs-skip"> +[warn] <span style="color:orange">Integrated compiler plugins:</span> +[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) +[warn] replaced by <span style="color:orange">scalacOptions += "-Ykind-projector"</span> +</code> +</pre> + +Here for instance you can activate kind-projector by adding `-Ykind-projector` to the list of `scalacOptions`. + +During the migration process, it is important to maintain the compatibility with Scala 2.13. +The later `migrateSyntax` and `migrateTypes` commands will use the Scala 2.13 compilation to rewrite some parts of the code automatically. + +You can configure kind-projector in a cross-compatible way like this: +```scala +// add kind-projector as a dependency on Scala 2 +libraryDependencies ++= { + if (scalaVersion.value.startsWith("3.")) Seq.empty + else Seq( + compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) + ) +}, +// activate kind-projector in Scala 3 +scalacOptions ++= { + if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector") + else Seq.empty +} ``` -Valid libraries are libraries that can be kept as they are. Those libraries are either already -compatible with Scala 3, or they are Java libraries. - -### Libraries that need to be updated -```text -org.typelevel:cats-core:2.2.0 -> "org.typelevel" %% "cats-core" % "2.6.1" : Other versions are avaialble for Scala 3: "2.7.0" -ch.epfl.scala:scalafix-rules:0.9.26:test -> "ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % "test" cross CrossVersion.for3Use2_13 : It's only safe to use the 2.13 version if it's inside an application. -``` +### Incompatible libraries -- For `cats-core` there is a version that has been published for Scala 3 - which is the proposed version `2.6.1`. We can update the build with this new version. There is also - a more recent version available which is `2.7.0` (but we need to use at least version 2.6.1 to get Scala 3 compatibility). - -- For `scalafix-rules`, there is no available version for Scala 3, but the library does not contain macros, - and therefore the `2.13` version can be used as it is in Scala 3. The syntax still needs to be updated to - `"ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % "test" cross CrossVersion.for3Use2_13`. - It’s not recommended to publish a Scala 3 library that depends on a Scala 2.13 library. - The reason is to prevent library users from ending up with two conflicting versions of - the same library in their classpath (one for Scala 2.13 and one for Scala 3), this problem can not be solved in some cases. - Read more about this topic in [the interoperability-overview](compatibility-classpath.html#interoperability-overview). - -### Macro library -```text -com.softwaremill.scalamacrodebug:macros:0.4.1:test -> X : Contains Macros and is not yet published for Scala 3. -``` -Scala 2 macros cannot be executed by the Scala 3 compiler. -So if you depend on a library that relies on macros, you will have to wait until this library is published for Scala 3. +Some macro libraries or compiler plugins are not compatible with Scala 3. -### Compiler plugins -```text -com.olegpy:better-monadic-for:0.3.1:plugin->default(compile) -> X : Scala 2 compiler plugins are not supported in Scala 3. You need to find an alternative. -org.typelevel:kind-projector:0.11.0:plugin->default(compile) -> -Ykind-projector : This compiler plugin has a scalacOption equivalent. Add it to your scalacOptions. -``` +<pre> +<code class="hljs hljs-skip"> +[error] <span style="color:red">Incompatible Libraries:</span> +[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test <span style="color:red">(Macro Library)</span> +[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") <span style="color:red">(Compiler Plugin)</span> +</code> +</pre> -`better-monadic-for` is a Scala 2 compiler plugin. -As explained in this [section](tutorial-sbt.html#2-choose-a-module), Scala 2 compiler plugins are not -supported in Scala 3. -In this case, we need to remove `better-monadic-for` and fix the code manually to make it compile without the compiler plugin. +To solve these incompatibilities, you can either: +- Check with the maintainers if they plan to port them to Scala 3, and possibly help them to do so. +- Remove these dependencies from your build and adapt the code accordingly. -For `kind-projector`, which is also a Scala 2 compiler plugin, there is an equivalent compiler option, `-Ykind-projector` (as shown in the message), which -can be added to your `scalacOptions`. +### The updated build -### The new build file -To update the build, for all incompatible settings or libraries, we assign different `scalacOptions` and `libraryDependencies` depending on the `scalaVersion`. +After you updated the build, it should look like this: ```scala //build.sbt lazy val main = project .in(file(".")) .settings( - name := "main", - scalaVersion := "2.13.7", - semanticdbEnabled := true, - scalacOptions ++= { - if (scalaVersion.value.startsWith("3")) Seq("-Werror", "-Ykind-projector") - else Seq("-Werror", "-Wunused") - }, - libraryDependencies ++= ( - if (scalaVersion.value.startsWith("3")) Seq() - else - Seq(compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full)) - ), + scalaVersion := "2.13.11", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.6.1", - "ch.epfl.scala" % "scalafix-interfaces" % "0.9.26", - "ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % "test" cross CrossVersion.for3Use2_13 - ) + "org.typelevel" %% "cats-core" % "2.6.1", + "io.github.java-diff-utils" % "java-diff-utils" % "4.12", + ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13), + "org.scalameta" %% "munit" % "0.7.25" % Test + ), + libraryDependencies ++= { + if (scalaVersion.value.startsWith("3.")) Seq.empty + else Seq( + compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) + ) + }, + scalacOptions ++= { + if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector") + else Seq.empty + } ) ``` -## Migrate `scalacOptions` -**Usage:** `migrate-scalacOptions projectId` where projectId is the name of the module chosen to be migrated. - -This command helps with the process of updating the compiler settings. It is based on -[the Compiler Options Table](options-lookup.html). -Between Scala 2.13 and Scala 3.1.0, the available compiler options are different: -- some Scala 2.13 settings have been removed -- others have been renamed -- some remain the same. - -The previous build file specifies two scalacOptions: `-Werror` and `-Wunused` -``` -> migrate-scalacOptions main -[info] X : the option is not available in Scala 3 -[info] Renamed : the option has been renamed -[info] Valid : the option is still valid -[info] Plugin : the option is related to a plugin, previously handled by migrate-libs -[info] -[info] -Wunused -> X -[info] -Werror -> -Xfatal-warnings - -[info] Plugins options -[info] -Yrangepos -> X -[info] -Xplugin:/Users/meriamlachkar/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/typelevel/kind-projector_2.13.3/0.13.2/kind-projector_2.13.3-0.13.2.jar -> Plugin -[info] -Xplugin:/Users/meriamlachkar/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scalameta/semanticdb-scalac_2.13.3/4.4.20/semanticdb-scalac_2.13.3-4.4.20.jar -> Plugin -[info] -P:semanticdb:synthetics:on -> Plugin -[info] -P:semanticdb:sourceroot:/Users/meriamlachkar/perso/plugin-test -> Plugin -[info] -P:semanticdb:targetroot:/Users/meriamlachkar/perso/plugin-test/target/scala-2.13/meta -> Plugin -[info] -P:semanticdb:failures:warning -> Plugin - -``` +Reload sbt, check that the project compiles (to Scala 2.13), check that the tests run successfully, and commit your changes. +You are now ready to migrate the compiler options. -We see that `-Wunusued` is specific to Scala 2 and doesn't have an equivalent in scala3, so we need to -remove it, whereas `-Werror` exists under a different name: `-Xfatal-warnings`, and can be renamed. +## 4. Migrate the compiler options -The command also outputs information specific to sbt plugins. -There is no need to modify them, the plugins are supposed to adapt the settings for Scala 3. +**Usage:** `migrateScalacOptions <project>` -In this specific case: - - we don’t need to remove `-Yrangepos`. - - `kind-projector` plugin has been replaced in the previous step - - `Xplugin:semanticdb` and all the specific options of the `semanticdb` plugin starting by `-P:semanticdb:...` are -added through an sbt setting `semanticdbEnabled := true` that is set by scala3-migrate (this tool). -If `semanticdb` is added through `compilerPlugin` or `addCompilerPlugin`, it will be -listed as a library dependency when we execute migrate-libs. -The support of SemanticDB is now shipped into the Scala 3 compiler, and will be configured with the same setting: -`semanticdbEnabled := true`. Scala3-migrate doesn't enable SemanticDB in Scala 3 unless it's configured in the build. +The Scala 3 compiler does not contain the exact same set of options as the Scala 2 compiler. +You can check out the [the Compiler Options Table](options-lookup.html) to get a full comparison of all the compilers options. -To conclude, all the information specific to the sbt plugins displayed by `migrate-scalacOption` can be -ignored if the previous step has been followed successfully. +The `migrateScalacOptions` will help you update the list of `scalacOptions` in your build. -### The new build file -In the previous build file change, we have already introduce the distinction between scala verions, so this time we only need to update the values. +For the purpose of this tutorial we will consider the following build configuration: ```scala - scalacOptions ++= - (if (scalaVersion.value.startsWith("3")) - Seq("-Xfatal-warnings", "-Ykind-projector") - else Seq("-Werror", "-Wunused")) +lazy val main = project + .in(file(".")) + .settings( + scalaVersion := "2.13.11", + scalacOptions ++= Seq( + "-encoding", + "UTF-8", + "-target:jvm-1.8", + "-Xsource:3", + "-Wunused:imports,privates,locals", + "-explaintypes" + ) + ) ``` -> The build is now fully updated. -> You can change the `scalaVersion` in the sbt shell, and launch the compile task. -> Your project may already successfully compile in scala 3! - -## Fix some syntax incompatibilities -An incompatibility is a piece of code that compiles in Scala 2.13 but does not compile in Scala 3. -Migrating a code base involves finding and fixing all the incompatibilities of the source code. +Running `migrateScalacOptions main` outputs: + +<pre> +<code class="hljs hljs-skip"> +sbt:main> migrateScalacOptions main +[info] +[info] Starting migration of scalacOptions in main +[info] +[info] <span style="color:green">Valid scalacOptions:</span> +[info] -encoding UTF-8 +[info] -Wunused:imports,privates,locals +[warn] +[warn] <span style="color:orange">Renamed scalacOptions:</span> +[warn] -target:jvm-1.8 -> <span style="color:orange">-Xunchecked-java-output-version:8</span> +[warn] -explaintypes -> <span style="color:orange">-explain</span> +[warn] +[warn] <span style="color:orange">Removed scalacOptions:</span> +[warn] -Xsource:3 +[warn] -Yrangepos +[success] Total time: 0 s, completed Aug 29, 2023 2:00:57 PM +</code> +</pre> + +Some scalac options are still valid, some must be renamed and some must be removed. + +> Some options can appear in the output of `migrateScalacOptions` but not in your `build.sbt`. +> They are added by sbt or by some sbt plugins. +> Make sure to use up-to-date versions of sbt and sbt plugins. +> They should be able to adapt the added compiler options to the Scala version automatically. + +Once again, it is important to maintain the compatibility with Scala 2.13 because the `migrateSyntax` and `migrateTypes` commands will use the Scala 2.13 compilation to apply some patches automatically. + +Here is how we can update the list of scalacOptions: +```scala +lazy val main = project + .in(file(".")) + .settings( + scalaVersion := "2.13.11", + scalacOptions ++= { + if (scalaVersion.value.startsWith("3.")) scala3Options + else scala2Options + } + ) -This third command applies a number of scalafix rules that fix some of the deprecated syntaxes. -Once those changes are applied, the code still compiles in Scala 2.13 and you can -already commit those changes. +lazy val sharedScalacOptions = + Seq("-encoding", "UTF-8", "-Wunused:imports,privates,locals") -**Usage:** `migrate-syntax projectId` where projectId is the name of the module chosen to be migrated. +lazy val scala2Options = sharedScalacOptions ++ + Seq("-target:jvm-1.8", "-Xsource:3", "-explaintypes") -The list of scalafix rules applied are: -- ProcedureSyntax -- fix.scala213.ConstructorProcedureSyntax -- fix.scala213.ExplicitNullaryEtaExpansion -- fix.scala213.ParensAroundLambda -- fix.scala213.ExplicitNonNullaryApply -- fix.scala213.Any2StringAdd +lazy val scala3Options = sharedScalacOptions ++ + Seq("-Xunchecked-java-output-version:8", "-explain") +``` -For more information on the fixed incompatibilities, please refer to -[the Incompatibility Table](incompat-syntactic.html). +Reload sbt, check that the project compiles (to Scala 2.13), check that the tests run successfully, and commit your changes. +You are now ready to migrate the syntax. -> There are more incompatibilities listed in the migration guide. -> Most of them are not frequent and can easily be fixed by hand. -> If you want to contribute by developing automatic rewrite with scalafix, -> we will be happy to add your rule in the migrate-syntax command. +## 5. Migrate the syntax -This is the output of `migrate-syntax`. -``` -> migrate-syntax main -[info] We are going to fix some syntax incompatibilities -[info] -[info] Successfully run fixSyntaxForScala3 in 8839 milliseconds -[info] -[info] The syntax incompatibilities have been fixed on the project main -[info] You can now commit the change! -[info] You can also execute the next command to try to migrate to 3.1.0 -[info] -[info] migrate main -[info] -[success] Total time: 2 s, completed 9 Apr 2021, 11:12:05 -``` +**Usage:** `migrateSyntax <project>` -## Fix other Scala 3 incompatibilities -> First reload the build to take into account the modifications -> in scalacOptions and libraryDependencies. +This command runs a number of Scalafix rules to patch some discarded syntax. -**Usage:** `migrate projectId`: tries compiling your code in Scala 3 by adding the minimum required inferred types and implicit values. +The list of applied Scalafix rules are: +- [ProcedureSyntax](https://scalacenter.github.io/scalafix/docs/rules/ProcedureSyntax.html) +- [fix.scala213.ExplicitNullaryEtaExpansion](https://github.com/lightbend-labs/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/ExplicitNullaryEtaExpansion.scala) +- [migrate.ParensAroundLambda](https://github.com/scalacenter/scala3-migrate/blob/ebb4a4087ed11899b9010f4c75eb365532694c0a/scalafix/rules/src/main/scala/migrate/ParensAroundParam.scala#L9) +- [fix.scala213.ExplicitNonNullaryApply](https://github.com/lightbend-labs/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/ExplicitNullaryEtaExpansion.scala) +- [fix.scala213.Any2StringAdd](https://github.com/lightbend-labs/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/Any2StringAdd.scala) +- [ExplicitResultTypes](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html) -Scala 3 uses a new type inference algorithm, therefore the Scala 3 compiler -can infer a different type than the one inferred by the Scala 2.13. -The goal of this command is to find the minimal set of type ascriptions to add to make your code compile with Scala 3. +For more information about the syntax changes between Scala 2.13 and Scala 3, you can refer to [the Incompatibility Table](incompatibility-table.html). -If the libraries have not been ported correctly, running `migrate projectId` will -fail reporting the problematic libraries. +> Some incompatibilities listed in [the Incompatibility Table](incompatibility-table.html) are not fixed by migrateSyntax. +> Most of them are not frequent and can easily be fixed by hand. +> If you want to contribute with a Scalafix rewrite rule, we will be more than happy to add it in the `migrateSyntax` command. + +Running `migrateSyntax main` outputs: +<pre> +<code class="hljs hljs-skip"> +sbt:main> migrateSyntax main +[info] Starting migration of syntax in main +[info] Run syntactic rules in 7 Scala sources successfully +[info] Applied 3 patches in src/main/scala/example/SyntaxRewrites.scala +[info] Run syntactic rules in 8 Scala sources successfully +[info] Applied 1 patch in src/test/scala/example/SyntaxRewritesTests.scala +[info] Migration of syntax in main succeeded. +[success] Total time: 2 s, completed Aug 31, 2023 11:23:51 AM +</code> +</pre> + +Take a look at the applied changes, check that the project still compiles, check that the tests run successfully and commit the changes. +The next and final step is to migrate the types. + +## 6. Migrate the types + +**Usage:** `migrateTypes <project>` + +The Scala 3 compiler uses a slightly different type inference algorithm. +It can sometimes fail at infering the same types as the Scala 2 compiler, which can lead to compilation errors. +This final step will add the needed type ascriptions to make the code compile to Scala 3. + +Running `migrateTypes main` outputs: +<pre> +<code class="hljs hljs-skip"> +sbt:main> migrateTypes main +[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/classes ... +[warn] 1 deprecation; re-run with -deprecation for details +[warn] one warning found +[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/test-classes ... +[warn] 2 deprecations; re-run with -deprecation for details +[warn] one warning found +[success] Total time: 7 s, completed Aug 31, 2023 11:26:25 AM +[info] Defining scalaVersion +[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others. +[info] Run `last` for details. +[info] Reapplying settings... +[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/) +[info] +[info] Migrating types in main / Compile +[info] +[info] Found 3 patches in 1 Scala source +[info] Starting migration of src/main/scala/example/TypeIncompat.scala +[info] 3 remaining candidates +[info] 1 remaining candidate +[info] Found 1 required patch in src/main/scala/example/TypeIncompat.scala +[info] Compiling to Scala 3 with -source:3.0-migration -rewrite +[info] compiling 1 Scala source to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-3.3.1/classes ... +[info] +[info] Migrating types in main / Test +[info] +[info] Found 4 patches in 1 Scala source +[info] Starting migration of src/test/scala/example/TypeIncompatTests.scala.scala +[info] 4 remaining candidates +[info] 3 remaining candidates +[info] 2 remaining candidates +[info] Found 1 required patch in src/test/scala/example/TypeIncompatTests.scala.scala +[info] Compiling to Scala 3 with -source:3.0-migration -rewrite +[info] +[info] You can safely upgrade main to Scala 3: +[info] <span style="color:orange">scalaVersion := "3.3.1"</span> +[success] Total time: 18 s, completed Aug 31, 2023 11:26:45 AM +[info] Defining scalaVersion +[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others. +[info] Run `last` for details. +[info] Reapplying settings... +[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/) +sbt:main> +</code> +</pre> + +`migrateTypes main` found 2 required patches: one in `src/test/scala/example/TypeIncompatTests.scala.scala` and the other in `src/main/scala/example/TypeIncompat.scala`. +It applied them, then it compiled to Scala 3 with `-source:3.0-migration -rewrite` to finalize the migration. + +Congratulations! Your project can now compile to Scala 3. -The command will display the following output: -``` -> migrate main -[info] We are going to migrate main / [Compile, Test] to 3.1.0 -2022.01.06 21:56:39 [INFO] migrate:24 - Found 1 required patch(es) in Incompat4.scala after 823 milliseconds ms -2022.01.06 21:56:39 [INFO] compileWithRewrite:114 - Finalizing the migration: compiling in Scala 3 with -rewrite option -[info] -[info] main / Compile has been successfully migrated to Scala 3.1.0 -[info] -[info] You can now commit the change! -[info] Then you can permanently change the scalaVersion of main: -[info] -[info] crossScalaVersions += "3.1.0" // or -[info] scalaVersion := "3.1.0" -``` +## What to do next ? -In this example, a file has been modified by adding some implicit parameters, -implicit conversions or explicit result types. +If you project contains only one module, you can set `scalaVersion := 3.3.1`. -## What to do next ? -If you project contains only one module, you're done. Depending on the nature of your project, -you will either change permanently the `scalaVersion` of your project, or add Scala 3 to `crossScalaVerions`. +If you have more than one module, you can start again from [3. Migrate the dependencies](#3-migrate-the-dependencies) with another module. -If you have more than one module, you can start again with a second module `MODULE2`. -if `MODULE2` depends on the last module migrated which is now compiling in Scala 3, you can either keep this module in Scala 3 and add `-Ytasty-reader` to `MODULE2 scalacOptions`, -or `reload` the project to keep the migrated module on Scala 2 during the entire migration -which implies cross-compiling during the process of the migration. +Once you are done with all modules, you can remove `sbt-scala3-migrate` from `project/plugins.abt`, and all Scala 2.13 related settings. -Once you are done, you can remove scala3-migrate from your plugins. +## Feedback and contributions are welcome -## Contributions and feedback are welcome -The tool is still under development, and we would love to hear from you. -Every feedback will help us build a better tool: typos, clearer log messages, better documentation, -bug reports, ideas of features, so please open a [GitHub issue](https://github.com/scalacenter/scala3-migrate) -or contact us on [discord](https://discord.com/invite/scala). +Every feedback will help us improve `sbt-scala3-migrate`: typos, clearer log messages, better documentation, +bug reports, ideas of features. +Don't hesitate to open a [GitHub issue](https://github.com/scalacenter/scala3-migrate). diff --git a/_overviews/scala3-migration/scaladoc-settings-compatibility.md b/_overviews/scala3-migration/scaladoc-settings-compatibility.md index 5e1f2e6005..7c6d63a59b 100644 --- a/_overviews/scala3-migration/scaladoc-settings-compatibility.md +++ b/_overviews/scala3-migration/scaladoc-settings-compatibility.md @@ -2,20 +2,20 @@ title: Scaladoc settings compatibility between Scala2 and Scala3 type: section description: This chapter lists all the scaladoc options for Scala 2 and Scala 3, and explains the relations between them. -num: 25 +num: 26 previous-page: options-new next-page: plugin-intro --- -The current page is stating the status of scaladoc settings. The related Github issue can be found here for [discussion](https://github.com/lampepfl/dotty/issues/11907) +The current page is stating the status of scaladoc settings. The related Github issue can be found here for [discussion](https://github.com/scala/scala3/issues/11907) | Scala2 | Scala3 | Description | Comment | Is implemented? | ------------- | ------------- | --- | --- | --- | | -doc-format | _ | Selects in which format documentation is rendered. | Actually, old scaladoc supports only html, so it is in some way consistent with new scaladoc, which provides only html | <i class="fa fa-times fa-lg"></i> | -| -doc-title | -project | The overall name of the Scaladoc site | Aliased in [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | -| -doc-version | -project-version | | Aliased in [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | -| -doc-footer | -project-footer | A footer on every Scaladoc page, by default the EPFL/Lightbend copyright notice. Can be overridden with a custom footer. | Fixed by [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -doc-title | -project | The overall name of the Scaladoc site | Aliased in [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -doc-version | -project-version | | Aliased in [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -doc-footer | -project-footer | A footer on every Scaladoc page, by default the EPFL/Lightbend copyright notice. Can be overridden with a custom footer. | Fixed by [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | | -doc-no-compile | _ | A directory containing sources which should be parsed for docstrings without compiling (e.g. AnyRef.scala) | We don't need this as we have completely different approach to that issue using -Ydocument-synthetic-types flag for synthetic types | <i class="fa fa-check fa-lg"></i> | | -doc-source-url | -source-links | A URL pattern used to link to the source file, with some variables supported... | Scala3 implementation provides richer syntax. You can find migration steps below this [table](#source-links). | <i class="fa fa-check fa-lg"></i> | | -doc-external-doc | -external-mappings | Links describing locations of external dependencies' documentations. | Scala3 implementation provides richer syntax. You can find migration steps below this [table](#external-mappings). | <i class="fa fa-check fa-lg"></i> | @@ -34,7 +34,7 @@ The current page is stating the status of scaladoc settings. The related Github | -diagrams-max-implicits | _ | | We don't need this in Scala3 | <i class="fa fa-times fa-lg"></i> | | -diagrams-dot-timeout | _ | | We don't need this in Scala3 | <i class="fa fa-times fa-lg"></i> | | -diagrams-dot-restart | _ | | We don't need this in Scala3 | <i class="fa fa-times fa-lg"></i> | -| -author | -author | | Fixed by [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -author | -author | | Fixed by [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | | -raw-output | _ | | We don't need this in Scala3 | <i class="fa fa-check fa-lg"></i> | | -no-prefixes | _ | | We don't need this in Scala3 | <i class="fa fa-check fa-lg"></i> | | -skip-packages | -skip-packages | | | <i class="fa fa-check fa-lg"></i> | @@ -42,8 +42,8 @@ The current page is stating the status of scaladoc settings. The related Github | -expand-all-types | _ | | Setting has been removed | <i class="fa fa-times fa-lg"></i> | | -groups | -groups | | | <i class="fa fa-check fa-lg"></i> | | -no-java-comments | _ | | We don't need this in Scala3 | <i class="fa fa-check fa-lg"></i> | -| -doc-canonical-base-url | -doc-canonical-base-url | A base URL to use as prefix and add `canonical` URLs to all pages. The canonical URL may be used by search engines to choose the URL that you want people to see in search results. If unset no canonical URLs are generated. | Fixed by [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | -| -private | -private | Show all types and members. Unless specified, show only public and protected types and members. | Fixed by [#11965](https://github.com/lampepfl/dotty/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -doc-canonical-base-url | -doc-canonical-base-url | A base URL to use as prefix and add `canonical` URLs to all pages. The canonical URL may be used by search engines to choose the URL that you want people to see in search results. If unset no canonical URLs are generated. | Fixed by [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | +| -private | -private | Show all types and members. Unless specified, show only public and protected types and members. | Fixed by [#11965](https://github.com/scala/scala3/issues/11965) | <i class="fa fa-check fa-lg"></i> | | _ | -siteroot | | We don't backport it to old scaladoc | N/A | | _ | -project-logo | | Should we backport it to the old scaladoc? | N/A | | _ | -comment-syntax | | We don't backport it to the old scaladoc | N/A | @@ -60,7 +60,7 @@ Source links are used to point to source code at some remote repository like git Hopefully, the new syntax is almost superset of the old syntax. To migrate to the new scaladoc syntax, make sure that you don't use any of these variables: `€{TPL_OWNER}` or `€{FILE_PATH_EXT}`. Otherwise you have to rewrite your source link, using either other `variables` or you can use new -syntax, about which you can read more at [dotty docs](https://dotty.epfl.ch/docs/usage/scaladoc/settings.html) +syntax, about which you can read more in the [Scaladoc documentation]({% link _overviews/scala3-scaladoc/settings.md %}). Note that new syntax let you specify prefixes of your files paths to match specific url in case your sources are scattered in different directories or even different repositories. diff --git a/_overviews/scala3-migration/tooling-migration-mode.md b/_overviews/scala3-migration/tooling-migration-mode.md index 96bd3597a9..82c79bc695 100644 --- a/_overviews/scala3-migration/tooling-migration-mode.md +++ b/_overviews/scala3-migration/tooling-migration-mode.md @@ -3,7 +3,7 @@ title: Scala 3 Migration Mode type: chapter description: This section describes the migration mode of the Scala 3 compiler num: 8 -previous-page: scala3-migrate +previous-page: tooling-scala2-xsource3 next-page: tutorial-intro --- @@ -11,7 +11,7 @@ The Scala 3 compiler provides some helpful utilities to ease the migration. Try running `scalac` to have a glimpse of those utilities: -> `scalac` is the executable of the Scala compiler, it can be downloaded from [Github](https://github.com/lampepfl/dotty/releases/). +> `scalac` is the executable of the Scala compiler, it can be downloaded from [Github](https://github.com/scala/scala3/releases/). > > It can also be installed using Coursier with `cs install scala3-compiler`, in which case `scalac` is aliased `scala3-compiler`. @@ -22,7 +22,6 @@ where possible standard options include: ... -explain Explain errors in more detail. --explain-types Explain type errors in more detail. ... -rewrite When used in conjunction with a `...-migration` source version, rewrites sources to migrate to new version. ... @@ -45,7 +44,7 @@ Once your code compiles in the migration mode, almost all warnings can be resolv To do so you just need to compile again, this time with the `-source:3.0-migration` and the `-rewrite` options. > Beware that the compiler will modify the code! It is intended to be safe. -> However you may like to commit the initial state so that you can print the diff applied by the compiler and revert it if necessary. +> However you may want to commit the initial state so that you can print the diff applied by the compiler and revert it if necessary. > #### Good to know > - The rewrites are not applied if the code compiles in error. diff --git a/_overviews/scala3-migration/tooling-scala2-xsource3.md b/_overviews/scala3-migration/tooling-scala2-xsource3.md new file mode 100644 index 0000000000..e88f711c32 --- /dev/null +++ b/_overviews/scala3-migration/tooling-scala2-xsource3.md @@ -0,0 +1,146 @@ +--- +title: Scala 2 with -Xsource:3 +type: chapter +description: This section describes the Scala 2 compiler's -Xsource:3 flag +num: 7 +previous-page: tooling-tour +next-page: tooling-migration-mode +--- + +The Scala 2.13 compiler issues helpful migration warnings with the `-Xsource:3` flag. + +Before moving to the Scala 3 compiler, it's recommended to enable this flag in Scala 2 and address the new warnings. + +Usage information is shown with `scalac -Xsource:help`. + +## Migration vs cross-building + +With Scala 2.13.14 and newer, the `-Xsource:3` flag supports two scenarios: + + - `Xsource:3` enables warnings relevant for migrating a codebase to Scala 3. + In addition to new warnings, the flag enables certain benign Scala 3 syntaxes such as `import p.*`. + - Adding the `-Xsource-features:<features>` flag is useful to reduce the maintenance burden of projects that cross-build between Scala 2 and 3. + Certain language constructs have been backported from Scala 3 in order to improve compatibility. + Instead of warning about a behavior change in Scala 3, it adopts the new behavior. + +## Warnings as errors, and quick fixes + +By default, Scala 3 migration warnings emitted by Scala 2.13 are reported as errors, using the default configuration, `-Wconf:cat=scala3-migration:e`. +This ensures that migration messaging is more visible. +Diagnostics can be emitted as warnings by specifying `-Wconf:cat=scala3-migration:w`. +Typically, emitting warnings instead of errors will cause more diagnostics to be reported. + +The [`@nowarn` annotation](https://www.scala-lang.org/api/current/scala/annotation/nowarn.html) can be used in program sources to suppress individual warnings. +Diagnostics are suppressed before they are promoted to errors, so that `@nowarn` takes precedence over `-Wconf` and `-Werror`. + +The Scala 2.13 compiler implements quick fixes for many Scala 3 migration warnings. +Quick fixes are displayed in Metals-based IDEs (not yet in IntelliJ), or they can be applied directly to the source code using the `-quickfix` flag, for example `-quickfix:cat=scala3-migration`. +See also `scala -quickfix:help`. + +## Enabled Scala 3 syntax + +The `-Xsource:3` flag enables the following Scala 3 syntaxes in Scala 2: + + - `import p.*` + - `import p.m as n` + - `import p.{given, *}` + - `case C(xs*)` as an alias for `case C(xs @ _*)` + - `A & B` type intersection as an alias for `A with B` + - Selecting a method `x.f` performs an eta-expansion (`x.f _`), even without an expected type + +## Scala 3 migration warnings in detail + +Many Scala 3 migration warnings are easy to understand, e.g., for implicit definitions without an explicit type: + +{% highlight scala %} +scala> object O { implicit val s = "" } + ^ + error: Implicit definition must have explicit type (inferred String) [quickfixable] +{% endhighlight %} + +## Enabling Scala 3 features with `-Xsource-features` + +Certain Scala 3 language changes have been backported and can be enabled using `-Xsource-features`; usage and available features are shown with `-Xsource-features:help`. + +When enabling a feature, the corresponding migration warning is no longer issued. + +{% highlight scala %} +scala> raw"\u0061" + ^ + warning: Unicode escapes in raw interpolations are deprecated; use literal characters instead +val res0: String = a + +scala> :setting -Xsource:3 + +scala> raw"\u0061" + ^ + error: Unicode escapes in raw interpolations are ignored in Scala 3 (or with -Xsource-features:unicode-escapes-raw); use literal characters instead + Scala 3 migration messages are errors under -Xsource:3. Use -Wconf / @nowarn to filter them or add -Xmigration to demote them to warnings. + Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=scala3-migration, site=res1 + +scala> :setting -Xsource-features:unicode-escapes-raw + +scala> raw"\u0061" +val res1: String = \u0061 +{% endhighlight %} + +For every such language feature, a migration warning is issued under plain `-Xsource:3`. +Enabling the feature silences the warning and adopts the changed behavior. +To avoid silent language changes when upgrading to a new Scala 2.13 version, it is recommended to enable features explicitly or use a group (e.g., `-Xsource-features:v2.13.14`). + +`-Xsource:3-cross` is a shorthand for `-Xsource:3 -Xsource-features:_`. + +### Changes in language semantics + +The following table shows backported Scala 3 language semantics available in `-Xsource-features` / `-Xsource:3-cross`. + +| Feature flag | `-Xsource:3` behavior | `-Xsource-features` / `-Xsource:3-cross` behavior | +|--- |--- |--- | +| `any2stringadd`: `(x: Any) + ""` is deprecated | deprecation warning | does not compile, implicit `any2stringadd` is not inferred | +| `unicode-escapes-raw`: unicode escapes in triple-quoted strings and raw interpolations (`"""\u0061"""`) | fatal warning, escape is processed | escape is not processed | +| `leading-infix`: leading infix operators continue the previous line <sup>1</sup> | fatal warning, second line is a separate expression | operation continues the previous line | +| `string-context-scope`: desugaring of string interpolators using `StringContext` | fatal warning if the interpolation references a `StringContext` in scope different from `scala.StringContext` | desugaring always uses `scala.StringContext` | +| `package-prefix-implicits`: an implicit for type `p.A` is found in the package prefix `p` | fatal warning | the package prefix `p` is no longer part of the implicit search scope | +| `implicit-resolution`: specificity during implicit search | fatal warning | use Scala-3-style [downwards comparisons](https://github.com/scala/scala/pull/6037) for implicit search and overloading resolution | +| `case-apply-copy-access`: modifiers of synthetic methods | fatal warning | constructor modifiers are used for apply / copy methods of case classes | +| `case-companion-function`: companions are Functions | fatal warning at use site | synthetic case companion objects no longer extend FunctionN, but are adapted at use site with warning | +| `infer-override`: override type inference | fatal warning | inferred type of member uses type of overridden member | +| `double-definitions`: definitions differing in empty parens <sup>2</sup> | fatal warning | double definition error | + +Example 1: + +{% highlight scala %} + def f = + 1 + + 2 +{% endhighlight %} + +Example 2: + +{% highlight scala %} +class C(x: Int) { + def x(): Int = x // allowed in Scala 2, double definition error in Scala 3 +} +{% endhighlight %} + +### Changes affecting binary encoding + +As of Scala 2.13.15, there are 3 changes in `-Xsource-features` that affect binary encoding of classfiles: + + 1. `case-apply-copy-access`: the constructor modifiers of case classes (`case class C private[p] (x: Int)`) are copied to the synthetic `apply` and `copy` methods. + 1. `case-companion-function`: the synthetic companion objects of case classes no longer extend `FunctionN`. + 1. `infer-override`: overriding methods without an explicit return type inherit the return type from the parent (instead of using the inferred type of the method body). + +For projects that are already cross-building between Scala 2 and 3 with existing releases for both, enabling these changes breaks binary compatibility (make sure to use [MiMa to detect such changes](https://github.com/lightbend/mima)). For example, if a library defines + +{% highlight scala %} +trait A { def f: Object } +class B extends A { def f = "hi" } +{% endhighlight %} + + - enabling `-Xsource-features:infer-override` breaks binary compatibility on Scala 2.13: existing releases have `A.f: String`, the new version will have `A.f: Object` + - adding an explicit result type `A.f: String` breaks binary compatibility on Scala 3: existing releases have `A.f: Object` + +It is possible to work around this using version-dependent source files, see [scala/scala-xml#675](https://github.com/scala/scala-xml/pull/675) as an example. + +Instead of implementing such workarounds, it might be easier not to enable changes affecting binary encoding (`-Xsource-features:v2.13.14,-case-apply-copy-access,-case-companion-function,-infer-override`). diff --git a/_overviews/scala3-migration/tooling-syntax-rewriting.md b/_overviews/scala3-migration/tooling-syntax-rewriting.md index 976e3c097e..5cd51e0cc7 100644 --- a/_overviews/scala3-migration/tooling-syntax-rewriting.md +++ b/_overviews/scala3-migration/tooling-syntax-rewriting.md @@ -2,7 +2,7 @@ title: Scala 3 Syntax Rewriting type: chapter description: This section describes the syntax rewriting capability of the Scala 3 compiler -num: 14 +num: 15 previous-page: tutorial-macro-mixing next-page: incompatibility-table --- @@ -13,7 +13,7 @@ Both are optional so that the Scala 2 code style is still perfectly valid in Sca The new syntax for control structures makes it possible to write the condition of an `if`-expression, the condition of a `while`-loop or the generators of a `for`-expression without enclosing parentheses. The significant indentation syntax makes braces `{...}` not needed in many occurences: class and method bodies, `if`-expressions, `match`-expressions and more. -You can find a complete description in the [Optional Braces](https://docs.scala-lang.org/scala3/reference/other-new-features/indentation.html) page of the Scala 3 reference website. +You can find a complete description in the [Optional Braces]({{ site.scala3ref }}/other-new-features/indentation.html) page of the Scala 3 reference website. Converting existing Scala code to the new syntax by hand is tedious and error-prone. In this chapter we show how you can use the compiler to rewrite your code automatically from the classic Scala 2 style to the new style, or conversely. @@ -52,7 +52,7 @@ Each of the first four options corresponds to a specific syntax: | Syntax | Compiler Option | |-|-| | Significant Indentation | `-indent` | -| Classical Braces | `-noindent` | +| Classical Braces | `-no-indent` | As we will see in further detail these options can be used in combination with the `-rewrite` option to automate the conversion to a particular syntax. @@ -62,6 +62,8 @@ Let's have a look at how this works in a small example. Given the following source code written in a Scala 2 style. +{% tabs scala-2-location %} +{% tab 'Scala 2 Only' %} ```scala case class State(n: Int, minValue: Int, maxValue: Int) { @@ -80,25 +82,33 @@ case class State(n: Int, minValue: Int, maxValue: Int) { } } ``` +{% endtab %} +{% endtabs %} We will be able to move it to new syntax automatically in two steps: first by using the new control structure rewrite (`-new-syntax -rewrite`) and then the significant indentation rewrite (`-indent -rewrite`). > The `-indent` option does not work on the classic control structures. > So make sure to run the two steps in the correct order. -> Unfortunately, the compiler is not able to apply both steps at the same time: <del>`-indent -new-syntax -rewrite`</del>. +> Unfortunately, the compiler is not able to apply both steps at the same time: `-indent -new-syntax -rewrite`. ### New Control Structures We can use the `-new-syntax -rewrite` options by adding them to the list of scalac options in our build tool. +{% tabs sbt-location %} +{% tab 'sbt' %} ```scala -// build.sbt +// build.sbt, for Scala 3 project scalacOptions ++= Seq("-new-syntax", "-rewrite") ``` +{% endtab %} +{% endtabs %} After compiling the code, the result looks as follows: +{% tabs scala-3-location_2 %} +{% tab 'Scala 3 Only' %} ```scala case class State(n: Int, minValue: Int, maxValue: Int) { @@ -117,6 +127,8 @@ case class State(n: Int, minValue: Int, maxValue: Int) { } } ``` +{% endtab %} +{% endtabs %} Notice that the parentheses around the `n == maxValue` disappeared, as well as the braces around the `i <- minValue to maxValue` and `j <- 0 to n` generators. @@ -126,6 +138,8 @@ After this first rewrite, we can use the significant indentation syntax to remov To do that we use the `-indent` option in combination with the `-rewrite` option. It leads us to the following version: +{% tabs scala-3-location_3 %} +{% tab 'Scala 3 Only' %} ```scala case class State(n: Int, minValue: Int, maxValue: Int): @@ -142,6 +156,8 @@ case class State(n: Int, minValue: Int, maxValue: Int): j <- 0 to n do println(i + j) ``` +{% endtab %} +{% endtabs %} ## Moving back to the Classic syntax @@ -150,6 +166,8 @@ Starting from the latest state of our code sample, we can move backwards to its Let's rewrite the code using braces while retaining the new control structures. After compiling with the `-no-indent -rewrite` options, we obtain the following result: +{% tabs scala-3-location_4 %} +{% tab 'Scala 3 Only' %} ```scala case class State(n: Int, minValue: Int, maxValue: Int) { @@ -169,9 +187,13 @@ case class State(n: Int, minValue: Int, maxValue: Int) { } } ``` +{% endtab %} +{% endtabs %} Applying one more rewrite, with `-old-syntax -rewrite`, takes us back to the original Scala 2-style code. +{% tabs shared-location %} +{% tab 'Scala 2 and 3' %} ```scala case class State(n: Int, minValue: Int, maxValue: Int) { @@ -191,6 +213,8 @@ case class State(n: Int, minValue: Int, maxValue: Int) { } } ``` +{% endtab %} +{% endtabs %} With this last rewrite, we have come full circle. diff --git a/_overviews/scala3-migration/tooling-tour.md b/_overviews/scala3-migration/tooling-tour.md index 5270cb216e..5a5f1fe686 100644 --- a/_overviews/scala3-migration/tooling-tour.md +++ b/_overviews/scala3-migration/tooling-tour.md @@ -4,7 +4,7 @@ type: chapter description: This chapter is a tour of the migration tooling ecosystem num: 6 previous-page: compatibility-metaprogramming -next-page: scala3-migrate +next-page: tooling-scala2-xsource3 --- ## The Scala Compilers @@ -13,12 +13,10 @@ The migration has been carefully prepared beforehand in each of the two compiler ### The Scala 2.13 Compiler -The Scala 2.13 compiler supports `-Xsource:3`, an option that enables some Scala 3 syntax and behavior: -- Most deprecated syntax generates an error. -- Infix operators can start a line in the middle of a multiline expression. -- Implicit search and overload resolution follow Scala 3 handling of contravariance when checking specificity. +The Scala 2.13 compiler supports `-Xsource:3`, an option that enables migration warnings and certain Scala 3 syntax and behavior. + +The [Scala 2 with -Xsource:3](tooling-scala2-xsource3.html) page explains the flag in detail. -The `-Xsource:3` option is intended to encourage early migration. ### The Scala 3 Compiler @@ -42,9 +40,9 @@ Once your code is compiled in Scala 3 you can convert it to the new and optional > The `sbt-dotty` plugin was needed in sbt 1.4 to get support for Scala 3. > It is not useful anymore since sbt 1.5. -sbt 1.5 supports Scala 3 out-of-the-box. +sbt supports Scala 3 out-of-the-box. All common tasks and settings are intended to work the same. -Many plugins should also work exactly the same. +Many sbt plugins should also work exactly the same. To help with the migration, sbt 1.5 introduces new Scala 3 specific cross versions: @@ -62,27 +60,24 @@ libraryDependency += ("org.bar" %% "bar" % "1.0.0").cross(CrossVersion.for2_13Us ### Maven -Scala 3 support for Maven will soon land in the [scala-maven-plugin](https://github.com/davidB/scala-maven-plugin). +The Scala Maven plugin supports Scala 3 since 4.5.1. ## Code editors and IDEs ### Metals -[Metals](https://scalameta.org/metals/) is a Scala language server that works with VS Code, Vim, Emacs, Sublime Text and Eclipse. - -Scala 3 is already very well supported by Metals. -Some minor adjustments for the new syntax changes and new features are coming. +[Metals](https://scalameta.org/metals/) is the Scala extension for VS Code. +It also works with Vim, Emacs, Sublime Text, and other editors. ### IntelliJ IDEA -The Scala plugin for IntelliJ includes preliminary support for Scala 3. -Full-fledged support is being worked on by the team at JetBrains. +The [Scala plugin for IntelliJ](https://plugins.jetbrains.com/plugin/1347-scala) supports Scala 3. ## Formatting Tools ### Scalafmt -[Scalafmt](https://scalameta.org/scalafmt/) v3.0.0-RC3 supports both Scala 2.13 and Scala 3. +[Scalafmt](https://scalameta.org/scalafmt/) supports Scala 2.13 and Scala 3 since v3.0.0. To enable Scala 3 formatting you must set the `runner.dialect = scala3` in your `.scalafmt.conf` file. @@ -97,41 +92,36 @@ fileOverride { } ``` +Scalafmt can also enforce the new Scala 3 syntax with the [Scala 3 rewrites](https://scalameta.org/scalafmt/docs/configuration.html#scala3-rewrites). + ## Migration Tools ### Scalafix [Scalafix](https://scalacenter.github.io/scalafix/) is a refactoring tool for Scala. -At the time of writing, it only runs on Scala 2.13. -But it can be useful to prepare the code before jumping to Scala 3. The [Incompatibility Table](incompatibility-table.html) shows which incompatibility can be fixed by an existing Scalafix rule. So far the relevant rules are: - [Procedure Syntax](https://scalacenter.github.io/scalafix/docs/rules/ProcedureSyntax.html) - [Explicit Result Types](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html) - Value Eta-Expansion: `fix.scala213.ExplicitNullaryEtaExpansion` in [scala/scala-rewrites](https://github.com/scala/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/ExplicitNullaryEtaExpansion.scala) -- Parentheses Around Lambda Parameter: `fix.scala213.ParensAroundLambda` in [ohze/scala-rewrites](https://github.com/ohze/scala-rewrites/blob/dotty/rewrites/src/main/scala/fix/scala213/ParensAroundLambda.scala) - Auto Application: `fix.scala213.ExplicitNonNullaryApply` in [scala/scala-rewrites](https://github.com/scala/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala) - `any2stringadd` Conversion: `fix.scala213.Any2StringAdd` in [scala/scala-rewrites](https://github.com/scala/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/Any2StringAdd.scala) You can apply these rules in sbt using the `sbt-scalafix` plugin. They are also used internally in `sbt-scala3-migrate` described below. -### The Scala 3 Migrate Plugin +### The Scala 3 Migration Plugin for sbt [Scala 3 Migrate](https://github.com/scalacenter/scala3-migrate) is an sbt plugin that can assist you during the migration to Scala 3. -It proposes an incremental approach that can be described as follows: -- Migrate the library dependencies: - For every library dependency it checks, if there are available versions for Scala 3. -- Migrate the Scala compiler options (`scalacOptions`): - Some Scala 2 compiler options have been removed or renamed, others remain the same. - This step helps you adapt the compiler options of your project. -- Migrate the syntax: - This step relies on Scalafix and existing rules to fix the deprecated syntax. -- Migrate the code by expliciting the types: - Scala 3 has a new type inference algorithm that may infer slightly different types than the Scala 2 inference. - This last step explicits a minimum set of types so that the project can be compiled with Scala 3 without altering its runtime behavior. +It proposes an incremental approach, based on four sbt commands: +- `migrateDependencies` helps you update the list of `libraryDependencies` +- `migrateScalacOptions` helps you update the list of `scalacOptions` +- `migrateSyntax` fixes a number of syntax incompatibilities between Scala 2.13 and Scala 3 +- `migrateTypes` tries to code compile your code to Scala 3 by infering types and resolving implicits where needed. + +The detailed instructions on how to use Scala 3 Migrate can be found [here](scala3-migrate.html). ## Scaladex diff --git a/_overviews/scala3-migration/tutorial-intro.md b/_overviews/scala3-migration/tutorial-intro.md index 90f75161c4..822bfa2319 100644 --- a/_overviews/scala3-migration/tutorial-intro.md +++ b/_overviews/scala3-migration/tutorial-intro.md @@ -10,11 +10,12 @@ next-page: tutorial-prerequisites You are ready to port your project to Scala 3! The first step is to check that the [Prerequisites](tutorial-prerequisites.html) are met by your project. -Then you can go to the [Porting a sbt Project](tutorial-sbt.html) tutorial to learn about the migration workflow. + +If you use sbt we recommend you to go to [Porting an sbt Project (using sbt-scala3-migrate)](scala3-migrate.html), or you can go to [Porting an sbt Project (by hand)](tutorial-sbt.html). > **You are not using sbt?** > -> We still advise you to read the [Porting a sbt Project](tutorial-sbt.html) tutorial since the workflow should be very similar. +> We still advise you to read the [Porting a sbt Project (by hand)](tutorial-sbt.html) tutorial since the workflow is the same in other build tools. > Prior to that, make sure the version of your build tool is up-to-date to support Scala 3. diff --git a/_overviews/scala3-migration/tutorial-macro-cross-building.md b/_overviews/scala3-migration/tutorial-macro-cross-building.md index bcf2462768..8d45ed3f90 100644 --- a/_overviews/scala3-migration/tutorial-macro-cross-building.md +++ b/_overviews/scala3-migration/tutorial-macro-cross-building.md @@ -2,7 +2,7 @@ title: Cross-Building a Macro Library type: section description: This section shows how to cross-build a macro library -num: 12 +num: 13 previous-page: tutorial-sbt next-page: tutorial-macro-mixing --- @@ -24,13 +24,15 @@ In order to exemplify this tutorial, we will consider the minimal macro library lazy val example = project .in(file("example")) .settings( - scalaVersion := "2.13.6", + scalaVersion := "2.13.11", libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value ) ) ``` +{% tabs scala-2-location %} +{% tab 'Scala 2 Only' %} ```scala // example/src/main/scala/location/Location.scala package location @@ -52,6 +54,8 @@ object Macros { } } ``` +{% endtab %} +{% endtabs %} You should recognize some similarities with your library: one or more macro methods, in our case the `location` method, are implemented by consuming a macro `Context` and returning a `Tree` from this context. @@ -69,7 +73,7 @@ The main idea is to build the artifact twice and to publish two releases: You can add Scala 3 to the list of `crossScalaVersions` of your project: ```scala -crossScalaVersions := Seq("2.13.6", "3.0.0") +crossScalaVersions := Seq("2.13.11", "3.3.1") ``` The `scala-reflect` dependency won't be useful in Scala 3. @@ -87,21 +91,21 @@ libraryDependencies ++= { } ``` -After reloading sbt, you can switch to the Scala 3 context by running `++3.0.0`. -At any point you can go back to the Scala 2.13 context by running `++2.13.6`. +After reloading sbt, you can switch to the Scala 3 context by running `++3.3.1`. +At any point you can go back to the Scala 2.13 context by running `++2.13.11`. ## 2. Rearrange the code in version-specific source directories If you try to compile with Scala 3 you should see some errors of the same kind as: {% highlight text %} -sbt:example> ++3.0.0 +sbt:example> ++3.3.1 sbt:example> example / compile -[error] -- Error: /example/src/main/scala/location/Location.scala:15:35 +[error] -- Error: /example/src/main/scala/location/Location.scala:15:35 [error] 15 | val location = typeOf[Location] [error] | ^ [error] | No TypeTag available for location.Location -[error] -- Error: /example/src/main/scala/location/Location.scala:18:4 +[error] -- Error: /example/src/main/scala/location/Location.scala:18:4 [error] 18 | q"new $location($path, $line)" [error] | ^ [error] |Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html @@ -116,13 +120,19 @@ All the code that cannot be compiled by the Scala 3 compiler goes to the `src/ma In our example, the `Location` class stays in the `src/main/scala` folder but the `Macros` object is moved to the `src/main/scala-2` folder: +{% tabs shared-location %} +{% tab 'Scala 2 and 3' %} ```scala // example/src/main/scala/location/Location.scala package location case class Location(path: String, line: Int) ``` +{% endtab %} +{% endtabs %} +{% tabs scala-2-location_2 %} +{% tab 'Scala 2 Only' %} ```scala // example/src/main/scala-2/location/Macros.scala package location @@ -142,17 +152,23 @@ object Macros { } } ``` +{% endtab %} +{% endtabs %} Now we can initialize each of our Scala 3 macro definitions in the `src/main/scala-3` folder. They must have the exact same signature than their Scala 2.13 counterparts. +{% tabs scala-3-location_1 %} +{% tab 'Scala 3 Only' %} ```scala // example/src/main/scala-3/location/Macros.scala package location object Macros: - def location: Location = ??? + inline def location: Location = ??? ``` +{% endtab %} +{% endtabs %} ## 3. Implement the Scala 3 macro @@ -161,6 +177,8 @@ One needs to learn about the new [Metaprogramming](compatibility-metaprogramming We eventually come up with this implementation: +{% tabs scala-3-location_2 %} +{% tab 'Scala 3 Only' %} ```scala // example/src/main/scala-3/location/Macros.scala package location @@ -173,10 +191,12 @@ object Macros: private def locationImpl(using quotes: Quotes): Expr[Location] = import quotes.reflect.Position val pos = Position.ofMacroExpansion - val file = Expr(pos.sourceFile.jpath.toString) + val file = Expr(pos.sourceFile.path.toString) val line = Expr(pos.startLine + 1) '{new Location($file, $line)} ``` +{% endtab %} +{% endtabs %} ## 4. Cross-validate the macro @@ -184,6 +204,8 @@ Adding some tests is important to check that the macro method works the same in In our example, we add a single test. +{% tabs shared-test %} +{% tab 'Scala 2 and 3' %} ```scala // example/src/test/scala/location/MacrosSpec.scala package location @@ -194,17 +216,19 @@ class MacrosSpec extends munit.FunSuite { } } ``` +{% endtab %} +{% endtabs %} You should now be able to run the tests in both versions. {% highlight text %} -sbt:example> ++2.13.6 +sbt:example> ++2.13.11 sbt:example> example / test location.MacrosSpec: + location [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] -sbt:example> ++3.0.0 +sbt:example> ++3.3.1 sbt:example> example / test location.MacrosSpec: + location diff --git a/_overviews/scala3-migration/tutorial-macro-mixing.md b/_overviews/scala3-migration/tutorial-macro-mixing.md index fbd3b907a9..4a013b4284 100644 --- a/_overviews/scala3-migration/tutorial-macro-mixing.md +++ b/_overviews/scala3-migration/tutorial-macro-mixing.md @@ -2,15 +2,17 @@ title: Mixing Scala 2.13 and Scala 3 Macros type: section description: This section shows how to mix Scala 2.13 and Scala 3 macros in a single artifact -num: 13 +num: 14 previous-page: tutorial-macro-mixing next-page: tooling-syntax-rewriting --- -This tutorial shows how to mix Scala 2.13 and Scala 3 macros in a single artifact. +This tutorial shows how to mix Scala 2.13 and Scala 3 macros in a single artifact. This means that consumers can use `-Ytasty-reader` from Scala 2.13 code that uses your macros. -It can be used to create a new Scala 3 macro library and make it available for Scala 2.13 users. -It can also be used to port an existing Scala 2.13 macro library to Scala 3, although it is probably easier to cross-build. +There are two main benefits of this: + +1. Making a new or existing scala 3 macro library available for Scala 2.13 users without having to provide a separate 2.13 version +2. Allowing your macros to be usable in multi-project builds that are being upgraded module by module. ## Introduction @@ -21,6 +23,8 @@ This is only possible in Scala 3, since the Scala 3 compiler can read both the S Let's start by considering the following code skeleton: +{% tabs scala-3-location_1 %} +{% tab 'Scala 3 Only' %} ```scala // example/src/main/scala/location/Location.scala package location @@ -31,6 +35,8 @@ object Macros: def location: Location = macro ??? inline def location: Location = ${ ??? } ``` +{% endtab %} +{% endtabs %} As you can see the `location` macro is defined twice: - `def location: Location = macro ???` is a Scala 2.13 macro definition @@ -46,6 +52,8 @@ The explanation is that it recognizes the first definition is for Scala 2.13 onl You can put the Scala 3 macro implementation alongside the definition. +{% tabs scala-3-location_2 %} +{% tab 'Scala 3 Only' %} ```scala package location @@ -63,12 +71,17 @@ object Macros: val line = Expr(Position.ofMacroExpansion.startLine + 1) '{new Location($file, $line)} ``` +{% endtab %} +{% endtabs %} ## 2. Implement the Scala 2 macro The Scala 3 compiler can compile a Scala 2 macro implementation if it contains no quasiquote or reification. For instance this piece of code does compile with Scala 3, and so you can put it alongside the Scala 3 implementation. + +{% tabs scala-2-and-3-location %} +{% tab 'Scala 2 and 3' %} ```scala import scala.reflect.macros.blackbox.Context @@ -79,6 +92,8 @@ def locationImpl(c: Context): c.Tree = { New(c.mirror.staticClass(classOf[Location].getName()), path, line) } ``` +{% endtab %} +{% endtabs %} However, in many cases you will have to move the Scala 2.13 macro implementation in a Scala 2.13 submodule. @@ -87,14 +102,14 @@ However, in many cases you will have to move the Scala 2.13 macro implementation lazy val example = project.in(file("example")) .settings( - scalaVersion := "3.0.0" + scalaVersion := "3.3.1" ) .dependsOn(`example-compat`) lazy val `example-compat` = project.in(file("example-compat")) .settings( - scalaVersion := "2.13.6", - libraryDependency += "org.scala-lang" % "scala-reflect" % scalaVersion.value + scalaVersion := "2.13.12", + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value ) ``` @@ -102,6 +117,8 @@ Here `example`, our main library compiled in Scala 3, depends on `example-compat In such a case we can put the Scala 2 macro implementation in `example-compat` and use quasiquotes. +{% tabs scala-2-location %} +{% tab 'Scala 2 Only' %} ```scala package location @@ -120,6 +137,8 @@ object Scala2MacrosCompat { } } ``` +{% endtab %} +{% endtabs %} Note that we had to move the `Location` class downstream. @@ -133,8 +152,8 @@ Since we want to execute the tests in Scala 2.13 and Scala 3, we create a cross- // build.sbt lazy val `example-test` = project.in(file("example-test")) .settings( - scalaVersion := "3.0.0", - crossScalaVersions := Seq("3.0.0", "2.13.6"), + scalaVersion := "3.3.1", + crossScalaVersions := Seq("3.3.1", "2.13.12"), scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, 13)) => Seq("-Ytasty-reader") @@ -149,6 +168,9 @@ lazy val `example-test` = project.in(file("example-test")) > `-Ytasty-reader` is needed in Scala 2.13 to consume Scala 3 artifacts For instance the test can be: + +{% tabs scala-2-and-3-test %} +{% tab 'Scala 2 and 3' %} ```scala // example-test/src/test/scala/location/MacrosSpec.scala package location @@ -159,17 +181,19 @@ class MacrosSpec extends munit.FunSuite { } } ``` +{% endtab %} +{% endtabs %} You should now be able to run the tests in both versions. {% highlight text %} -sbt:example> ++2.13.6 +sbt:example> ++2.13.12 sbt:example> example-test / test location.MacrosSpec: + location [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] -sbt:example> ++3.0.0 +sbt:example> ++3.3.1 sbt:example> example-test / test location.MacrosSpec: + location @@ -191,7 +215,7 @@ You are now ready to publish your library. It can be used in Scala 3 projects, or in Scala 2.13 projects with these settings: ```scala -scalaVersion := "2.13.6" +scalaVersion := "2.13.12" libraryDependencies += ("org" %% "example" % "x.y.z").cross(CrossVersion.for2_13Use3) scalacOptions += "-Ytasty-reader" ``` diff --git a/_overviews/scala3-migration/tutorial-prerequisites.md b/_overviews/scala3-migration/tutorial-prerequisites.md index 5c64092431..55c1c3fe42 100644 --- a/_overviews/scala3-migration/tutorial-prerequisites.md +++ b/_overviews/scala3-migration/tutorial-prerequisites.md @@ -4,7 +4,7 @@ type: section description: This section details the prerequisites of migration to Scala 3 num: 10 previous-page: tutorial-intro -next-page: tutorial-sbt +next-page: scala3-migrate --- The migration to Scala 3 is made easier thanks to the interoperability between Scala 2.13 and Scala 3, as described in the [Compatibility Reference](compatibility-intro.html) page. @@ -43,10 +43,10 @@ The dependency to `"scalatest" %% "scalatest" % "3.0.9"` must be upgraded becaus - The `scalatest` API is based on some macro definitions. - The `3.0.9` version is not published for Scala 3. -We can upgrade it to version `3.2.7`, which is cross-published in Scala 2.13 and Scala 3. +We can upgrade it to version `3.2.19`, which is cross-published in Scala 2.13 and Scala 3. ```scala -libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" ``` ## Compiler plugins @@ -101,10 +101,9 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") ### Scala Native -Scala 3 does not yet support [Scala Native](https://scala-native.readthedocs.io/en/latest/). +Scala 3 is supported in [Scala Native](https://scala-native.org/) since v0.4.3. -If you project is cross-built to Scala Native, you can port it to Scala 3. -But you will not be able to compile for the Native platform. +The minimal version of Scala 3 supported by Scala Native is 3.1.0, due to fatal blockers in Scala 3.0.x ### Kind Projector diff --git a/_overviews/scala3-migration/tutorial-sbt.md b/_overviews/scala3-migration/tutorial-sbt.md index c09f6e5e8d..67d1ffed36 100644 --- a/_overviews/scala3-migration/tutorial-sbt.md +++ b/_overviews/scala3-migration/tutorial-sbt.md @@ -1,9 +1,9 @@ --- -title: Porting an sbt Project +title: Porting an sbt Project (by hand) type: section description: This section shows how to port an sbt project -num: 11 -previous-page: tutorial-prerequisites +num: 12 +previous-page: scala3-migrate next-page: tutorial-macro-cross-building --- @@ -43,16 +43,16 @@ This is crucial to avoid bugs that could happen when fixing the incompatibilitie Configuring cross-building in sbt is as short as: ```scala -scalaVersion := "3.0.0" -crossScalaVersions ++= Seq("2.13.6", "3.0.0") +scalaVersion := "3.3.1" +crossScalaVersions ++= Seq("2.13.11", "3.3.1") ``` This configuration means: -- The default version is `3.0.0`. -- 2.13.6 can be loaded by running the `++2.13.6` command. -- 3.0.0 can be loaded by running the `++3.0.0` command. +- The default version is `3.3.1`. +- 2.13.11 can be loaded by running the `++2.13.11` command. +- 3.3.1 can be loaded by running the `++3.3.1` command. -Beware that the `reload` command will always load the default version---here it is 3.0.0. +Beware that the `reload` command will always load the default version---here it is 3.3.1. ## 4. Prepare the dependencies @@ -89,8 +89,8 @@ Or for a Scala.js dependencies: Once you have fixed all the unresolved dependencies, you can check that the tests are still passing in Scala 2.13: {% highlight text %} -sbt:example> ++2.13.6 -[info] Setting Scala version to 2.13.6 on 1 project. +sbt:example> ++2.13.11 +[info] Setting Scala version to 2.13.11 on 1 project. ... sbt:example> example / test ... @@ -138,8 +138,8 @@ Also you should disable `-Xfatal-warnings` to take full advantage of the migrati It is now time to try compiling in Scala 3: {% highlight text %} -sbt:example> ++3.0.0 -[info] Setting Scala version to 3.0.0 on 1 project. +sbt:example> ++3.3.1 +[info] Setting Scala version to 3.3.1 on 1 project. ... sbt:example> example / compile ... @@ -171,8 +171,8 @@ This is particularly crucial if your project is a published library. After fixing an incompatibility, you can validate the solution by running the tests in Scala 2.13. {% highlight text %} -sbt:example> ++2.13.6 -[info] Setting Scala version to 2.13.6 on 1 project. +sbt:example> ++2.13.11 +[info] Setting Scala version to 2.13.11 on 1 project. ... sbt:example> example / test ... @@ -186,7 +186,7 @@ Only the migration warnings are remaining. You can patch them automatically by compiling with the `-source:3.0-migration -rewrite` options. {% highlight text %} -sbt:example> ++3.0.0 +sbt:example> ++3.3.1 sbt:example> set example / scalacOptions += "-rewrite" sbt:example> example / compile ... @@ -206,11 +206,11 @@ Good tests are the only guarantee to prevent such bugs from going unnoticed. Make sure that the tests are passing in both Scala 2.13 and Scala 3. {% highlight text %} -sbt:example> ++2.13.6 +sbt:example> ++2.13.11 sbt:example> example / test ... [success] -sbt:example> ++3.0.0 +sbt:example> ++3.3.1 sbt:example> example / test ... [success] diff --git a/_overviews/scala3-scaladoc/blog.md b/_overviews/scala3-scaladoc/blog.md index 612ee5c25e..ba845fc913 100644 --- a/_overviews/scala3-scaladoc/blog.md +++ b/_overviews/scala3-scaladoc/blog.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Built-in blog partof: scala3-scaladoc +languages: ["ru"] num: 5 previous-page: static-site next-page: site-versioning @@ -32,4 +33,44 @@ Scaladoc loads blog if the `_blog` directory exists. All the blogpost filenames should start with date in numeric format matching `YYYY-MM-DD`. Example name is `2015-10-23-dotty-compiler-bootstraps.md`. +## Page metadata +The blog pages in scaladoc support [Yaml Frontmatter](https://assemble.io/docs/YAML-front-matter.html) which allows you to specify different values which will be used for metadata in your page. Here are the possible fields: + +``` +--- +layout: <A reference to the layout page for the blog page> +author: <Name of the author of the page> +title: <Title of the page> +subTitle: <Subtitle of the page> +date: <Date of the creation of the page>, e.g. 2016-12-05 +authorImg: <Link to the author's image> +--- +<Content of your page> +``` + +You can also find more details about the front matter on the [Jekyll documentation site](https://jekyllrb.com/docs/front-matter/). + +## Syntax of the content +Keep in mind that the writing of your blog is done with Markdown. You can find more information about the syntax in [Markdown Guide](https://www.markdownguide.org/basic-syntax/). + +## Blog configuration +When creating your blog, Scaladoc also allows you to configure it. + +In order to modify the default settings of the blog documentation, users need to create a file named `blog.yml` in the **root directory of the blog**. The file should contain the parameters that the user wants to change. For example, if a user wants to change the input directory to "my_posts", the output directory to "my_docs", and temporarily hide the blog, they can create a file with the following content: + +``` +input: my_posts +output: my_docs +hidden: true +``` + +### Parameters: + +`input`: specifies the directory containing markdown files for the blog posts (default: "_posts" in "docs"). + +`output`: specifies the folder where HTML pages will be generated (default: "blog" in "target/docs"). + +`hidden`: allows users to temporarily hide the blog (default: "false"). + +To change these settings, create a file with the parameters and save it in the blog's root directory. The next time the blog is built, the new settings will be used. \ No newline at end of file diff --git a/_overviews/scala3-scaladoc/docstrings.md b/_overviews/scala3-scaladoc/docstrings.md index 5efb8b5f38..0aed8c373f 100644 --- a/_overviews/scala3-scaladoc/docstrings.md +++ b/_overviews/scala3-scaladoc/docstrings.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Docstrings - specific Tags and Features partof: scala3-scaladoc +languages: ["ru"] num: 2 previous-page: index next-page: linking @@ -51,7 +52,7 @@ In the example above, this Scaladoc comment is associated with the method square Scaladoc comments can go before fields, methods, classes, traits, objects. For now, scaladoc doesn't support straightforward solution to document packages. There is a dedicated github -[issue](https://github.com/lampepfl/dotty/issues/11284), where you can check the current status of the problem. +[issue](https://github.com/scala/scala3/issues/11284), where you can check the current status of the problem. For class primary constructors which in Scala coincide with the definition of the class itself, a @constructor tag is used to target a comment to be put on the primary constructor documentation rather than the class overview. @@ -187,9 +188,9 @@ Concise is nice! Get to the point quickly, people have limited time to spend on Omit unnecessary words. Prefer returns X rather than this method returns X, and does X,Y & Z rather than this method does X, Y and Z. DRY - don’t repeat yourself. Resist duplicating the method description in the @return tag and other forms of repetitive commenting. -More details on writing Scaladoc +### More details on writing Scaladoc -Further information on the formatting and style recommendations can be found in Scala-lang scaladoc style guide. +Further information on the formatting and style recommendations can be found in [Scala-lang scaladoc style guide](https://docs.scala-lang.org/style/scaladoc.html). ## Linking to API diff --git a/_overviews/scala3-scaladoc/index.md b/_overviews/scala3-scaladoc/index.md index b158e9d89e..c00475f4ba 100644 --- a/_overviews/scala3-scaladoc/index.md +++ b/_overviews/scala3-scaladoc/index.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Scaladoc partof: scala3-scaladoc +languages: ["ru"] num: 1 next-page: docstrings --- diff --git a/_overviews/scala3-scaladoc/linking.md b/_overviews/scala3-scaladoc/linking.md index 635c461abd..05301d8f85 100644 --- a/_overviews/scala3-scaladoc/linking.md +++ b/_overviews/scala3-scaladoc/linking.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Linking documentation partof: scala3-scaladoc +languages: ["ru"] num: 3 previous-page: docstrings next-page: static-site diff --git a/_overviews/scala3-scaladoc/search-engine.md b/_overviews/scala3-scaladoc/search-engine.md index 88a7b1a31b..612972811f 100644 --- a/_overviews/scala3-scaladoc/search-engine.md +++ b/_overviews/scala3-scaladoc/search-engine.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Type-based search partof: scala3-scaladoc +languages: ["ru"] num: 7 previous-page: site-versioning next-page: snippet-compiler diff --git a/_overviews/scala3-scaladoc/settings.md b/_overviews/scala3-scaladoc/settings.md index d490f82043..1fae4d3d5f 100644 --- a/_overviews/scala3-scaladoc/settings.md +++ b/_overviews/scala3-scaladoc/settings.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Settings partof: scala3-scaladoc +languages: ["ru"] num: 9 previous-page: snippet-compiler --- @@ -11,7 +12,7 @@ This chapter lists the configuration options that can be used when calling scala ## Parity with scaladoc for Scala 2 Scaladoc has been rewritten from scratch and some of the features turned out to be useless in the new context. -If you want to know what is current state of compatibility with scaladoc old flags, you can visit this issue for tracking [progress](https://github.com/lampepfl/dotty/issues/11907). +If you want to know what is current state of compatibility with scaladoc old flags, you can visit this issue for tracking [progress](https://github.com/scala/scala3/issues/11907). ## Providing settings @@ -31,7 +32,7 @@ The name of the project. To provide compatibility with Scala2 aliases with `-doc The current version of your project that appears in a top left corner. To provide compatibility with Scala2 aliases with `-doc-version` ##### -project-logo -The logo of your project that appears in a top left corner. To provide compatibility with Scala2 aliases with `-doc-logo` +The logo of your project that appears in a top left corner. A separate logo for the dark theme can be provided with the suffix `_dark`. If the logo is, for example, `mylogo.png`, then `mylogo_dark.png` is assumed for the dark theme. To provide compatibility with Scala2 aliases with `-doc-logo` ##### -project-footer The string message that appears in a footer section. To provide compatibility with Scala2 aliases with `-doc-footer` @@ -42,31 +43,30 @@ Currently we support two syntaxes: `markdown` or `wiki` If setting is not present, scaladoc defaults `markdown` ##### -revision -Revision (branch or ref) used to build project project. Useful with sourcelinks to prevent them from pointing always to the newest master that is subject to changes. +Revision (branch or ref) used to build project. Useful with sourcelinks to prevent them from pointing always to the newest main that is subject to changes. ##### -source-links Source links provide a mapping between file in documentation and code repository. Example source links is: -`-source-links:docs=github://lampepfl/dotty/master#docs` +`-source-links:docs=github://scala/scala3/main#docs` Accepted formats: -\<sub-path>=\<source-link> -\<source-link> +`<sub-path>=<source-link>` -where \<source-link> is one of following: +where `<source-link>` is one of following: - `github://<organization>/<repository>[/revision][#subpath]` will match https://github.com/$organization/$repository/\[blob|edit]/$revision\[/$subpath]/$filePath\[$lineNumber] when revision is not provided then requires revision to be specified as argument for scaladoc - `gitlab://<organization>/<repository>` will match https://gitlab.com/$organization/$repository/-/\[blob|edit]/$revision\[/$subpath]/$filePath\[$lineNumber] when revision is not provided then requires revision to be specified as argument for scaladoc - - \<scaladoc-template> + - `<scaladoc-template>` -\<scaladoc-template> is a format for `doc-source-url` parameter from old scaladoc. +`<scaladoc-template>` is a format for `doc-source-url` parameter from old scaladoc. NOTE: We only supports `€{FILE_PATH_EXT}`, `€{TPL_NAME}`, `€{FILE_EXT}`, - €{FILE_PATH}, and €{FILE_LINE} patterns. + `€{FILE_PATH}`, and `€{FILE_LINE}` patterns. Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`. @@ -81,15 +81,15 @@ Mapping between regexes matching classpath entries and external documentation. Example external mapping is: `-external-mappings:.*scala.*::scaladoc3::https://scala-lang.org/api/3.x/,.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/` -A mapping is of the form '\<regex>::\[scaladoc3|scaladoc|javadoc]::\<path>'. You can supply several mappings, separated by commas, as shown in the example. +A mapping is of the form `<regex>::[scaladoc3|scaladoc|javadoc]::<path>`. You can supply several mappings, separated by commas, as shown in the example. ##### -social-links Links to social sites. For example: -`-social-links:github::https://github.com/lampepfl/dotty,discord::https://discord.com/invite/scala,twitter::https://twitter.com/scala_lang` +`-social-links:github::https://github.com/scala/scala3,discord::https://discord.com/invite/scala,twitter::https://x.com/scala_lang` -Valid values are of the form: '\[github|twitter|gitter|discord]::link'. Scaladoc also supports 'custom::link::white_icon_name::black_icon_name'. In this case icons must be present in 'images/' directory. +Valid values are of the form: `[github|twitter|gitter|discord]::link`. Scaladoc also supports `custom::link::white_icon_name::black_icon_name`. In this case icons must be present in `images/` directory. ##### -skip-by-id @@ -156,11 +156,11 @@ where `path` is a prefix of the path to source files where snippets are located If the path is not present, the argument will be used as the default for all unmatched paths. Available flags: -compile - Enables snippet checking. -nocompile - Disables snippet checking. -fail - Enables snippet checking, asserts that snippet doesn't compile. +- compile - Enables snippet checking. +- nocompile - Disables snippet checking. +- fail - Enables snippet checking, asserts that snippet doesn't compile. -The fail flag comes in handy for snippets that present that some action would eventually fail during compilation, e. g. [Opaques page]({% link _scala3-reference/other-new-features/opaques.md %}) +The fail flag comes in handy for snippets that present that some action would eventually fail during compilation, e. g. [Opaques page]({{ site.scala3ref }}/other-new-features/opaques.html) Example usage: @@ -168,9 +168,21 @@ Example usage: Which means: -all snippets in files under directory `my/path/nc` should not be compiled at all -all snippets in files under directory `my/path/f` should fail during compilation -all other snippets should compile successfully +- all snippets in files under directory `my/path/nc` should not be compiled at all +- all snippets in files under directory `my/path/f` should fail during compilation +- all other snippets should compile successfully + +##### -scastie-configuration + +Define the additional sbt configuration for your Scastie snippets. For example, when you import external libraries into your snippets, you need to add the related dependencies. + +``` +"-scastie-configuration", """libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.12.0"""" +``` + +##### -dynamic-side-menu + +Generate the side menu (the tree-like overview of the project structure on the left-hand side) dynamically in the browser using JavaScript instead of embedding it in every HTML file. This can greatly reduce outputted HTML file sizes. Recommended for projects with 1000+ pages. For more info see [issue 18543](https://github.com/scala/scala3/issues/18543). ##### -Ysnippet-compiler-debug diff --git a/_overviews/scala3-scaladoc/site-versioning.md b/_overviews/scala3-scaladoc/site-versioning.md index d15fc49ab3..910b1f77ce 100644 --- a/_overviews/scala3-scaladoc/site-versioning.md +++ b/_overviews/scala3-scaladoc/site-versioning.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Site versioning partof: scala3-scaladoc +languages: ["ru"] num: 6 previous-page: blog next-page: search-engine diff --git a/_overviews/scala3-scaladoc/snippet-compiler.md b/_overviews/scala3-scaladoc/snippet-compiler.md index b89c81da76..77c9497d98 100644 --- a/_overviews/scala3-scaladoc/snippet-compiler.md +++ b/_overviews/scala3-scaladoc/snippet-compiler.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Snippet checking partof: scala3-scaladoc +languages: ["ru"] num: 8 previous-page: search-engine next-page: settings diff --git a/_overviews/scala3-scaladoc/static-site.md b/_overviews/scala3-scaladoc/static-site.md index 127f6c59e7..763ea11fe0 100644 --- a/_overviews/scala3-scaladoc/static-site.md +++ b/_overviews/scala3-scaladoc/static-site.md @@ -2,6 +2,7 @@ layout: multipage-overview title: Static documentation partof: scala3-scaladoc +languages: ["ru"] num: 4 previous-page: linking next-page: blog @@ -36,12 +37,45 @@ getting-started.html Scaladoc can transform both files and directories (to organize your documentation into a tree-like structure). By default, directories have a title based on the file name and have empty content. It is possible to provide index pages for each section by creating `index.html` or `index.md` (not both) in the dedicated directory. +Before generating your static site you need to set the `-siteroot` value in your doc `scalacOptions`. The value of this is the directory that holds your docs. The root URL for the generated documentation will also be `<site-root>`. + +For example if you have a directory called `docs` and you'd like that to be treated as your site root: + +``` +. +└── docs/ + └── _docs/ + ├── index.html + └── getting-started.html +``` + +Then the configuration would be as follows: + +``` +Compile / doc / scalacOptions ++= Seq("-siteroot", "docs") +``` + +Keep in mind that viewing your site locally with all the features it offers, like search or snippets, require a +local server. For example if your output directory was `output` you could use a python server to view everything +by doing the following and opening `localhost:8080`: + +```sh +cd output +python3 -m http.server 8080 +``` + ## Properties Scaladoc uses the [Liquid](https://shopify.github.io/liquid/) templating engine and provides several custom filters and tags specific to Scala documentation. +The following project related variables are available and can be accessed using +double curly braces (e.g. `{{ projectTitle }}`): + +- **projectTitle** the project title defined with the `-project` flag. +- **projectVersion** the project version defined with the `-project-version` flag. + In Scaladoc, all templates can contain YAML front-matter. The front-matter is parsed and put into the `page` variable available in templates via Liquid. @@ -63,6 +97,35 @@ Predefined properties: - **hasFrame** when set to `false` page will not include default layout (navigation, breadcrumbs, etc.) but only token HTML wrapper to provide metadata and resources (js and css files). **This setting is not exported to the template engine.** - **layout** - predefined layout to use, see below. **This setting is not exported to the template engine.** +Redirection properties: + +In addition to the predefined properties, Scaladoc also supports redirection properties, which allow you to redirect from one page to another. This can be useful when you move a page to a new location but want to keep the old URL working. + +- **redirectFrom** - Specifies the URL from which you want to redirect. By using the `redirectFrom` property, Scaladoc generates an empty page at the specified URL, which includes a browser-based redirection to the new location. + +Example: + +``` +--- +redirectFrom: /absolute/path/to/old/url.html +--- +``` + +In the above example, if you move the page from `/absolute/path/to/old/url.html` to a new location, you can use `redirectFrom` to ensure that the old URL still redirects to the new location. + +Please note that the `redirectFrom` property was inspired by the Jekyll plugin called [`jekyll-redirect-from`](https://github.com/jekyll/jekyll-redirect-from) . + +- **redirectTo** - Specifies the URL to which you want to redirect. This property is useful when you want to redirect to an external page or when you can't use `redirectFrom`. + +Example: + +``` +--- +redirectTo: https://docs.scala-lang.org/ +--- +``` + +In the above example, the page will be redirected to `https://docs.scala-lang.org/`. ## Using existing Templates and Layouts @@ -80,6 +143,7 @@ layout: main With a simple main template like this: {% raw %} + ```html <html> <head> @@ -108,6 +172,7 @@ Layouts must be placed in a `_layouts` directory in the site root: ## Assets In order to render assets along with static site, they need to be placed in the `_assets` directory in the site root: + ``` ├── _assets │ └── images @@ -115,6 +180,7 @@ In order to render assets along with static site, they need to be placed in the └── _docs └── getting-started.md ``` + To reference the asset on a page, one needs to create a link relative to the `_assets` directory ``` @@ -139,24 +205,27 @@ subsection: page: usage/sbt-projects.html hidden: false ``` + The root element needs to be a `subsection`. Nesting subsections will result in a tree-like structure of navigation. `subsection` properties are: - - `title` - Optional string - A default title of the subsection. + +- `title` - Optional string - A default title of the subsection. Front-matter titles have higher priorities. - - `index` - Optional string - A path to index page of a subsection. The path is relative to the `_docs` directory. - - `directory` - Optional string - A name of the directory that will contain the subsection in the generated site. +- `index` - Optional string - A path to index page of a subsection. The path is relative to the `_docs` directory. +- `directory` - Optional string - A name of the directory that will contain the subsection in the generated site. By default, the directory name is the subsection name converted to kebab case. - - `subsection` - Array of `subsection` or `page`. +- `subsection` - Array of `subsection` or `page`. Either `index` or `subsection` must be defined. The subsection defined with `index` and without `subsection` will contain pages and directories loaded recursively from the directory of the index page. `page` properties are: - - `title` - Optional string - A default title of the page. + +- `title` - Optional string - A default title of the page. Front-matter titles have higher priorities. - - `page` - String - A path to the page, relative to the `_docs` directory. - - `hidden` - Optional boolean - A flag that indicates whether the page should be visible in the navigation sidebar. By default, it is set to `false`. +- `page` - String - A path to the page, relative to the `_docs` directory. +- `hidden` - Optional boolean - A flag that indicates whether the page should be visible in the navigation sidebar. By default, it is set to `false`. **Note**: All the paths in the YAML configuration file are relative to `<static-root>/_docs`. @@ -179,14 +248,17 @@ If the title is specified multiple times, the priority is as follows (from highe Note that if you skip the `index` file in your tree structure or you don't specify the `title` in the frontmatter, there will be given a generic name `index`. The same applies when using `sidebar.yml` but not specifying `title` nor `index`, just a subsection. Again, a generic `index` name will appear. ## Blog + Blog feature is described in [a separate document]({% link _overviews/scala3-scaladoc/blog.md %}) ## Advanced configuration + ### Full structure of site root + ``` . └── <site-root>/ - ├── _layouts_/ + ├── _layouts/ │ └── ... ├── _docs/ │ └── ... @@ -201,6 +273,7 @@ Blog feature is described in [a separate document]({% link _overviews/scala3-sca │ └── ... └── ... ``` + It results in a static site containing documents as well as a blog. It also contains custom layouts and assets. The structure of the rendered documentation can be based on the file system but it can also be overridden by YAML configuration. ### Mapping directory structure @@ -208,6 +281,7 @@ It results in a static site containing documents as well as a blog. It also cont Using the YAML configuration file, we can define how the source directory structure should be transformed into an outputs directory structure. Take a look at the following subsection definition: + ```yaml - title: Some other subsection index: abc/index.html @@ -216,7 +290,8 @@ Take a look at the following subsection definition: - page: abc2/page1.md - page: foo/page2.md ``` + This subsection shows the ability of YAML configuration to map the directory structure. Even though the index page and all defined children are in different directories, they will be rendered in `custom-directory`. -The source page `abc/index.html` will generate a page `custom-directory/index.html`, the source page `abc2/page1.md` will generate a page `custom-directory/page1.html`, +The source page `abc/index.html` will generate a page `custom-directory/index.html`, the source page `abc2/page1.md` will generate a page `custom-directory/page1.html`, and the source page `foo/page2.md` will generate a page `custom-directory/page2.html`. diff --git a/_overviews/scaladoc/basics.md b/_overviews/scaladoc/basics.md deleted file mode 100644 index 4dc91dad79..0000000000 --- a/_overviews/scaladoc/basics.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -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/contribute.md b/_overviews/scaladoc/contribute.md index dc0af2ce57..0d8999574a 100644 --- a/_overviews/scaladoc/contribute.md +++ b/_overviews/scaladoc/contribute.md @@ -1,14 +1,9 @@ --- layout: multipage-overview title: Contributing to Scaladoc - -discourse: true - partof: scaladoc overview-name: Scaladoc - num: 5 - permalink: /overviews/scaladoc/:title.html --- diff --git a/_overviews/scaladoc/for-library-authors.md b/_overviews/scaladoc/for-library-authors.md index 2c16394c22..621861450e 100644 --- a/_overviews/scaladoc/for-library-authors.md +++ b/_overviews/scaladoc/for-library-authors.md @@ -7,6 +7,8 @@ overview-name: Scaladoc num: 3 permalink: /overviews/scaladoc/:title.html +redirect_from: + - /overviews/scaladoc/basics.html --- Scaladoc is a documentation system that lives in the comments of Scala source code @@ -82,7 +84,7 @@ include: ### Usage tags - `@see` reference other sources of information like external document links or related entities in the documentation. -- `@note` add a note for pre or post conditions, or any other notable restrictions +- `@note` add a note for pre- or post-conditions, or any other notable restrictions or expectations. - `@example` for providing example code or related example documentation. - `@usecase` provide a simplified method definition for when the full method @@ -97,7 +99,7 @@ They allow you to organize the Scaladoc page into distinct sections, with each one shown separately, in the order that you choose. These tags are *not* enabled by default! You must pass the `-groups` -flag to Scaladoc in order to turn them on. Typically the sbt for this +flag to Scaladoc in order to turn them on. Typically, the sbt for this will look something like: ``` scalacOptions in (Compile, doc) ++= Seq( @@ -106,13 +108,13 @@ scalacOptions in (Compile, doc) ++= Seq( ``` Each section should have a single-word identifier that is used in all of -these tags, shown as `<group>` below. By default, that identifier is -shown as the title of that documentation section, but you can use +these tags, shown as `<group>` below. By default, that identifier is +shown as the title of that documentation section, but you can use `@groupname` to provide a longer title. Typically, you should put `@groupprio` (and optionally `@groupname` and `@groupdesc`) in the Scaladoc for the package/trait/class/object itself, -describing what all the groups are, and their order. Then put `@group` +describing what all the groups are, and their order. Then put `@group` in the Scaladoc for each member, saying which group it is in. Members that do not have a `@group` tag will be listed as "Ungrouped" in @@ -130,7 +132,7 @@ the resulting documentation. ### Diagram tags - `@contentDiagram` - use with traits and classes to include a content hierarchy diagram showing included types. - The diagram content can be fine tuned with additional specifiers taken from `hideNodes`, `hideOutgoingImplicits`, + The diagram content can be fine-tuned with additional specifiers taken from `hideNodes`, `hideOutgoingImplicits`, `hideSubclasses`, `hideEdges`, `hideIncomingImplicits`, `hideSuperclasses` and `hideInheritedNode`. `hideDiagram` can be supplied to prevent a diagram from being created if it would be created by default. Packages and objects have content diagrams by default. @@ -168,7 +170,7 @@ If a comment is not provided for an entity at the current inheritance level, but is supplied for the overridden entity at a higher level in the inheritance hierarchy, the comment from the super-class will be used. -Likewise if `@param`, `@tparam`, `@return` and other entity tags are omitted +Likewise, if `@param`, `@tparam`, `@return` and other entity tags are omitted but available from a superclass, those comments will be used. ### Explicit @@ -180,7 +182,7 @@ For explicit comment inheritance, use the `@inheritdoc` tag. It is still possible to embed HTML tags in Scaladoc (like with Javadoc), but not necessary most of the time as markup may be used instead. -Some of the standard markup available: +Some types of markup available: `monospace` ''italic text'' @@ -201,16 +203,18 @@ Some of the standard markup available: Indentation is relative to the starting `*` for the comment. - **Headings** are defined with surrounding `=` characters, with more `=` denoting subheadings. E.g. `=Heading=`, `==Sub-Heading==`, etc. +- **Tables** are defined using `|` to separate elements in a row, + as described in the [blog](https://scala-lang.org/blog/2018/10/04/scaladoc-tables.html). - **List blocks** are a sequence of list items with the same style and level, with no interruptions from other block styles. Unordered lists can be bulleted using `-`; numbered lists can be denoted using `1.`, `i.`, `I.`, or `a.` for the various numbering styles. In both cases, you must have extra space in front, and - more space makes a sub-level. - + more space makes a sub-level. + The markup for list blocks looks like: /** Here is an unordered list: - * + * * - First item * - Second item * - Sub-item to the second @@ -235,6 +239,63 @@ The markup for list blocks looks like: - DRY - don't repeat yourself. Resist duplicating the method description in the `@return` tag and other forms of repetitive commenting. +## Resolving Ambiguous Links within Scaladoc Comments +When two methods are indistinguishable from each other lexically, it can cause Scaladoc to +report that there are ambiguous methods. As an example: + +```scala +import scala.collection.mutable.ListBuffer +class bar { + def foo(x: Int): Boolean = ??? + def foo(x: ListBuffer[Int], y: String): Int = ??? +} +``` + +If one references `foo` via `[[foo]]`, then the Scaladoc will complain and offer both +alternatives. Fixing this means elaborating the signature _enough_ so that it becomes unambiguous. +There are a few things to be aware of in general: + +* You must not use a space in the description of the signature: this will cause Scaladoc to + think the link has ended and move onto its description. +* You must fully qualify any types you are using: assume that you have written your program without + any import statements! + +Then, to disambiguate between objects and types, append `$` to designate a term name +and `!` for a type name. Term names include members which are not types, such as `val`, `def`, and +`object` definitions. For example: + - `[[scala.collection.immutable.List!.apply class List's apply method]]` and + - `[[scala.collection.immutable.List$.apply object List's apply method]]` + +When dealing with ambiguous overloads, however, it gets a bit more complex: + +* You must finish the signature, complete or otherwise, with a `*`, which serves as a wildcard + that allows you to cut off the signature when it is umambiguous. +* You must specify the names of the arguments and they must be _exactly_ as written in the + function definition: + - `[[bar.foo(Int)*]]` is **illegal** (no name) + - `[[bar.foo(y:Int)*]]` is **illegal** (wrong name) + - `[[bar.foo(x: Int)*]]` is **illegal** (space! Scaladoc sees this as `bar.foo(x:`) + - `[[bar.foo(x:Int):Boolean]]` is **illegal** (no `*`) + - `[[bar.foo(x:Int)*]]` is **legal** and unambiguous + - `[[bar.foo(x:Int*]]` is **legal**, the `Int` is enough to disambiguate so no closing paren needed +* The enclosing scope (package/class/object etc) of the method must use `.`, but within the arguments + and return type `\.` must be used instead to fully qualify types: + - `[[bar.foo(x:ListBuffer[Int],y:String)*]]` is **illegal** (no qualification on `ListBuffer`) + - `[[bar.foo(x:scala.collection.mutable.ListBuffer[Int],y:String)*]]` is **illegal** (non-escaped dots!) + - `[[bar\.foo(x:scala\.collection\.mutable\.ListBuffer[Int],y:String)*]]` is **illegal** (must not escape dots in the prefix) + - `[[bar.foo(x:scala\.collection\.mutable\.ListBuffer[Int],y:String)*]]` is **legal** + - `[[bar.foo(x:scala\.collection\.mutable\.ListBuffer[Int]*]]` is **legal**, the first argument is + enough to disambiguate. +* When generics are involved, additional square brackets may be used to avoid the + signature accidentally closing the link. Essentially, the number of leading left brackets + determines the number of right brackets required to complete the link: + - `[[baz(x:List[List[A]])*]]` is **illegal** (it is read as `baz(x:List[List[A`) + - `[[[baz(x:List[List[A]])*]]]` is **legal** (the `]]` is no longer a terminator, `]]]` is) + +### Known Limitations + * `#` syntax does not seem to be supported for parameters and return types. + * Spaces cannot be escaped with `\ `, so `implicit` parameters seem not to be supported either. + ## More details on writing Scaladoc Further information on the formatting and style recommendations can be found in diff --git a/_overviews/scaladoc/generate.md b/_overviews/scaladoc/generate.md index 98c0942ffe..0fbd38d533 100644 --- a/_overviews/scaladoc/generate.md +++ b/_overviews/scaladoc/generate.md @@ -1,14 +1,9 @@ --- layout: multipage-overview title: Generating Scaladoc - -discourse: true - partof: scaladoc overview-name: Scaladoc - num: 4 - permalink: /overviews/scaladoc/:title.html --- diff --git a/_overviews/scaladoc/interface.md b/_overviews/scaladoc/interface.md index b18d3caf57..e985de9c3c 100644 --- a/_overviews/scaladoc/interface.md +++ b/_overviews/scaladoc/interface.md @@ -7,6 +7,8 @@ overview-name: Scaladoc num: 2 permalink: /overviews/scaladoc/:title.html +redirect_from: + - /overviews/scaladoc/usage.html --- Many Scala developers, including those with a great deal of experience, are @@ -26,7 +28,7 @@ unaware of some of the more powerful features of Scaladoc. - Known subclasses lists all subclasses for this entity within the current Scaladoc. - Type hierarchy shows a graphical view of this class related to its super - classes and traits, immediate sub-types, and important related entities. The + classes and traits, immediate subtypes, and important related entities. The graphics themselves are links to the various entities. - The link in the Source section takes you to the online source for the class assuming it is available (and it certainly is for the core libraries and for diff --git a/_overviews/scaladoc/usage.md b/_overviews/scaladoc/usage.md deleted file mode 100644 index e1efd956f5..0000000000 --- a/_overviews/scaladoc/usage.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: inner-page-no-masthead -sitemap: false -permalink: /overviews/scaladoc/usage.html -redirect_to: /overviews/scaladoc/interface.html ---- diff --git a/_overviews/toolkit/OrderedListOfMdFiles b/_overviews/toolkit/OrderedListOfMdFiles new file mode 100644 index 0000000000..b2790bd58a --- /dev/null +++ b/_overviews/toolkit/OrderedListOfMdFiles @@ -0,0 +1,36 @@ +introduction.md +testing-intro.md +testing-suite.md +testing-run.md +testing-run-only.md +testing-exceptions.md +testing-asynchronous.md +testing-resources.md +testing-what-else.md +os-intro.md +os-read-directory.md +os-read-file.md +os-write-file.md +os-run-process.md +os-what-else.md +json-intro.md +json-parse.md +json-modify.md +json-deserialize.md +json-serialize.md +json-files.md +json-what-else.md +http-client-intro.md +http-client-request.md +http-client-uris.md +http-client-request-body.md +http-client-json.md +http-client-upload-file.md +http-client-what-else.md +web-server-intro.md +web-server-static.md +web-server-dynamic.md +web-server-query-parameters.md +web-server-input.md +web-server-websockets.md +web-server-cookies-and-decorators.md diff --git a/_overviews/toolkit/http-client-intro.md b/_overviews/toolkit/http-client-intro.md new file mode 100644 index 0000000000..7e8405f637 --- /dev/null +++ b/_overviews/toolkit/http-client-intro.md @@ -0,0 +1,21 @@ +--- +title: Sending HTTP requests with sttp +type: chapter +description: The introduction of the sttp library +num: 23 +languages: [ru] +previous-page: json-what-else +next-page: http-client-request +--- + +sttp is a popular and feature-rich library for making HTTP requests to web servers. + +It provides both a synchronous API and an asynchronous `Future`-based API. It also supports WebSockets. + +Extensions are available that add capabilities such as streaming, logging, telemetry, and serialization. + +sttp offers the same APIs on all platforms (JVM, Scala.js, and Scala Native). + +sttp is a good choice for small synchronous scripts as well as large-scale, highly concurrent, asynchronous applications. + +{% include markdown.html path="_markdown/install-sttp.md" %} diff --git a/_overviews/toolkit/http-client-json.md b/_overviews/toolkit/http-client-json.md new file mode 100644 index 0000000000..731c0cf0ff --- /dev/null +++ b/_overviews/toolkit/http-client-json.md @@ -0,0 +1,128 @@ +--- +title: How to send and receive JSON? +type: section +description: How to send JSON in a request and to parse JSON from the response. +num: 27 +previous-page: http-client-request-body +next-page: http-client-upload-file +--- + +{% include markdown.html path="_markdown/install-sttp.md" %} + +## HTTP and JSON + +JSON is a common format for HTTP request and response bodies. + +In the examples below, we use the [GitHub REST API](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28). +You will need a secret [GitHub authentication token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to run the programs. +Do not share your token with anyone. + +## Sending and receiving JSON + +To send a JSON request and parse a JSON response we use sttp in combination with uJson. + +### Sending JSON + +To send JSON, you can construct a `uJson.Value` and write it as a string in the body of the request. + +In the following example we use [GitHub users endpoint](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28) to update the profile of the authenticated user. +We provide the new location and bio of the profile in a JSON object. + +{% tabs 'json' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:compile-only +import sttp.client4.quick._ + +val json = ujson.Obj( + "location" -> "hometown", + "bio" -> "Scala programmer" +) + +val response = quickRequest + .patch(uri"https://api.github.com/user") + .auth.bearer(sys.env("GITHUB_TOKEN")) + .header("Content-Type", "application/json") + .body(ujson.write(json)) + .send() + +println(response.code) +// prints: 200 + +println(response.body) +// prints the full updated profile in JSON +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val json = ujson.Obj( + "location" -> "hometown", + "bio" -> "Scala programmer" +) + +val response = quickRequest + .patch(uri"https://api.github.com/user") + .auth.bearer(sys.env("GITHUB_TOKEN")) + .header("Content-Type", "application/json") + .body(ujson.write(json)) + .send() + +println(response.code) +// prints: 200 + +println(response.body) +// prints the full updated profile in JSON +``` +{% endtab %} +{% endtabs %} + +Before running the program, set the `GITHUB_TOKEN` environment variable. +After running it, you should see your new bio and location on your GitHub profile. + +### Parsing JSON from the response + +To parse JSON from the response of a request, you can use `ujson.read`. + +Again we use the GitHub user endpoint, this time to get the authenticated user. + +{% tabs 'json-2' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:compile-only +import sttp.client4.quick._ + +val response = quickRequest + .get(uri"https://api.github.com/user") + .auth.bearer(sys.env("GITHUB_TOKEN")) + .send() + +val json = ujson.read(response.body) + +println(json("login").str) +// prints your login +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val response = quickRequest + .get(uri"https://api.github.com/user") + .auth.bearer(sys.env("GITHUB_TOKEN")) + .send() + +val json = ujson.read(response.body) + +println(json("login").str) +// prints your login +``` +{% endtab %} +{% endtabs %} + +Before running the program, set the `GITHUB_TOKEN` environment variable. +Running the program should print your own login. + +## Sending and receiving Scala objects using JSON + +Alternatively, you can use uPickle to send or receive Scala objects using JSON. +Read the following to learn [*How to serialize an object to JSON*](/toolkit/json-serialize.html) and [*How to deserialize JSON to an object*](/toolkit/json-deserialize.html). diff --git a/_overviews/toolkit/http-client-request-body.md b/_overviews/toolkit/http-client-request-body.md new file mode 100644 index 0000000000..b54357e1cb --- /dev/null +++ b/_overviews/toolkit/http-client-request-body.md @@ -0,0 +1,61 @@ +--- +title: How to send a request with a body? +type: section +description: Sending a string body with sttp +num: 26 +previous-page: http-client-uris +next-page: http-client-json +--- + +{% include markdown.html path="_markdown/install-sttp.md" %} + +## Sending a request with a string body + +To send a POST request with a string body, you can chain `post` and `body` on a `quickRequest`: +{% tabs 'body' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import sttp.client4.quick._ + +val response = quickRequest + .post(uri"https://example.com/") + .body("Lorem ipsum") + .send() + +println(response.code) +// prints: 200 +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val response = quickRequest + .post(uri"https://example.com/") + .body("Lorem ipsum") + .send() + +println(response.code) +// prints: 200 +``` +{% endtab %} +{% endtabs %} + +In a request with string body, sttp adds the `Content-Type: text/plain; charset=utf-8` header and computes the `Content-Length` header. + +## Binary data + +The `body` method can also take a `Array[Byte]`, a `ByteBuffer` or an `InputStream`. + +{% tabs 'binarydata' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val bytes: Array[Byte] = "john".getBytes +val request = quickRequest.post(uri"https://example.com/").body(bytes) +``` +{% endtab %} +{% endtabs %} + +The binary body of a request is sent with `Content-Type: application/octet-stream`. + +Learn more in the [sttp documentation chapter about request bodies](https://sttp.softwaremill.com/en/latest/requests/body.html). diff --git a/_overviews/toolkit/http-client-request.md b/_overviews/toolkit/http-client-request.md new file mode 100644 index 0000000000..3a63e29c45 --- /dev/null +++ b/_overviews/toolkit/http-client-request.md @@ -0,0 +1,123 @@ +--- +title: How to send a request? +type: section +description: Sending a simple HTTP request with sttp. +num: 24 +previous-page: http-client-intro +next-page: http-client-uris +--- + +{% include markdown.html path="_markdown/install-sttp.md" %} + +## Sending an HTTP request + +The simplest way to send a request with sttp is `quickRequest`. + +You can define a GET request with `.get` and send it with `.send`. + +{% tabs 'request' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import sttp.client4.quick._ +import sttp.client4.Response + +val response: Response[String] = quickRequest + .get(uri"https://httpbin.org/get") + .send() + +println(response.code) +// prints: 200 + +println(response.body) +// prints some JSON string +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* +import sttp.client4.Response + +val response: Response[String] = quickRequest + .get(uri"https://httpbin.org/get") + .send() + +println(response.code) +// prints: 200 + +println(response.body) +// prints some JSON string +``` +{% endtab %} +{% endtabs %} + +A `Response[String]` contains a status code and a string body. + +## The request definition + +### The HTTP method and URI + +To specify the HTTP method and URI of a `quickRequest`, you can use `get`, `post`, `put`, or `delete`. + +To construct a URI you can use the `uri` interpolator, for e.g. `uri"https://example.com"`. +To learn more about that, see [*How to construct URIs and query parameters?*](/toolkit/http-client-uris). + +### The headers + +By default, the `quickRequest` contains the "Accept-Encoding" and the "deflate" headers. +To add more headers, you can call one of the `header` or `headers` overloads: + +{% tabs 'headers' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:reset +import sttp.client4.quick._ + +val request = quickRequest + .get(uri"https://example.com") + .header("Origin", "https://scala-lang.org") + +println(request.headers) +// prints: Vector(Accept-Encoding: gzip, deflate, Origin: https://scala-lang.org) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val request = quickRequest + .get(uri"https://example.com") + .header("Origin", "https://scala-lang.org") + +println(request.headers) +// prints: Vector(Accept-Encoding: gzip, deflate, Origin: https://scala-lang.org) +``` +{% endtab %} +{% endtabs %} + +sttp can also add "Content-Type" and "Content-Length" automatically if the request contains a body. + +## Authentication + +If you need authentication to access a resource, you can use one of the `auth.basic`, `auth.basicToken`, `auth.bearer` or `auth.digest` methods. + +{% tabs 'auth' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:reset +import sttp.client4.quick._ + +// a request with a authentication +val request = quickRequest + .get(uri"https://example.com") + .auth.basic(user = "user", password = "***") +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +// a request with a authentication +val request = quickRequest + .get(uri"https://example.com") + .auth.basic(user = "user", password = "***") +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/http-client-upload-file.md b/_overviews/toolkit/http-client-upload-file.md new file mode 100644 index 0000000000..a67af28fbd --- /dev/null +++ b/_overviews/toolkit/http-client-upload-file.md @@ -0,0 +1,80 @@ +--- +title: How to upload a file over HTTP? +type: section +description: Uploading a file over HTTP with sttp. +num: 28 +previous-page: http-client-json +next-page: http-client-what-else +--- + +{% include markdown.html path="_markdown/install-sttp.md" %} + +## Uploading a file + +To upload a file, you can put a Java `Path` in the body of a request. + +You can get a `Path` directly using `Paths.get("path/to/file")` or by converting an OS-Lib path to a Java path with `toNIO`. + +{% tabs 'file' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:compile-only +import sttp.client4.quick._ + +val file: java.nio.file.Path = (os.pwd / "image.png").toNIO +val response = quickRequest.post(uri"https://example.com/").body(file).send() + +println(response.code) +// prints: 200 +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val file: java.nio.file.Path = (os.pwd / "image.png").toNIO +val response = quickRequest.post(uri"https://example.com/").body(file).send() + +println(response.code) +// prints: 200 +``` +{% endtab %} +{% endtabs %} + +## Multi-part requests + +If the web server can receive multiple files at once, you can use a multipart body, as follows: + +{% tabs 'multipart' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import sttp.client4.quick._ + +val file1 = (os.pwd / "avatar1.png").toNIO +val file2 = (os.pwd / "avatar2.png").toNIO +val response = quickRequest + .post(uri"https://example.com/") + .multipartBody( + multipartFile("avatar1.png", file1), + multipartFile("avatar2.png", file2) + ) + .send() +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* + +val file1 = (os.pwd / "avatar1.png").toNIO +val file2 = (os.pwd / "avatar2.png").toNIO +val response = quickRequest + .post(uri"https://example.com/") + .multipartBody( + multipartFile("avatar1.png", file1), + multipartFile("avatar2.png", file2) + ) + .send() +``` +{% endtab %} +{% endtabs %} + +Learn more about multipart requests in the [sttp documention](https://sttp.softwaremill.com/en/latest/requests/multipart.html). diff --git a/_overviews/toolkit/http-client-uris.md b/_overviews/toolkit/http-client-uris.md new file mode 100644 index 0000000000..bfb9beb332 --- /dev/null +++ b/_overviews/toolkit/http-client-uris.md @@ -0,0 +1,128 @@ +--- +title: How to construct URIs and query parameters? +type: section +description: Using interpolation to construct URIs +num: 25 +previous-page: http-client-request +next-page: http-client-request-body +--- + +{% include markdown.html path="_markdown/install-sttp.md" %} + +## The `uri` interpolator + +`uri` is a custom [string interpolator](/overviews/core/string-interpolation.html) that allows you to create valid web addresses, also called URIs. For example, you can write `uri"https://example.com/"`. + +You can insert any variable or expression in your URI with the usual `$` or `${}` syntax. +For instance `uri"https://example.com/$name"`, interpolates the value of the variable `name` into an URI. +If `name` contains `"peter"`, the result is `https://example.com/peter`. + +`uri` escapes special characters automatically, as seen in this example: + +{% tabs 'uri' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import sttp.client4.quick._ +import sttp.model.Uri + +val book = "programming in scala" +val bookUri: Uri = uri"https://example.com/books/$book" + +println(bookUri) +// prints: https://example.com/books/programming%20in%20scala +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import sttp.client4.quick.* +import sttp.model.Uri + +val book = "programming in scala" +val bookUri: Uri = uri"https://example.com/books/$book" + +println(bookUri) +// prints: https://example.com/books/programming%20in%20scala +``` +{% endtab %} +{% endtabs %} + +## Query parameters + +A query parameter is a key-value pair that is appended to the end of a URI in an HTTP request to specify additional details about the request. +The web server can use those parameters to compute the appropriate response. + +For example, consider the following URL: + +``` +https://example.com/search?q=scala&limit=10&page=1 +``` + +It contains three query parameters: `q=scala`, `limit=10` and `page=1`. + +### Using a map of query parameters + +The `uri` interpolator can interpolate a `Map[String, String]` as query parameters: + +{% tabs 'queryparams' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val queryParams = Map( + "q" -> "scala", + "limit" -> "10", + "page" -> "1" +) +val uriWithQueryParams = uri"https://example.com/search?$queryParams" +println(uriWithQueryParams) +// prints: https://example.com/search?q=scala&limit=10&page=1 +``` +{% endtab %} +{% endtabs %} + +For safety, special characters in the parameters are automatically escaped by the interpolator. + +## Using an optional query parameter + +A query parameter might be optional. +The `uri` interpolator can interpolate `Option`s: + +{% tabs 'optional' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +def getUri(limit: Option[Int]): Uri = + uri"https://example.com/all?limit=$limit" + +println(getUri(Some(10))) +// prints: https://example.com/all?limit=100 + +println(getUri(None)) +// prints: https://example.com/all +``` +{% endtab %} +{% endtabs %} + +Notice that the query parameter disappears entirely when `limit` is `None`. + +## Using a sequence as values of a single query parameter + +A query parameter can be repeated in a URI to represent a list of values. +For example, the `version` parameter in `?version=1.0.0&version=1.0.1&version=1.1.0` contains 3 values: `1.0.0`, `1.0.1` and `1.1.0`. + +To build such query parameter in a URI, you can interpolate a `Seq` (or `List`, `Array`, etc) in a `uri"..."`. + +{% tabs 'seq' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:nest +def getUri(versions: Seq[String]): Uri = + uri"https://example.com/scala?version=$versions" + +println(getUri(Seq("3.2.2"))) +// prints: https://example.com/scala?version=3.2.2 + +println(getUri(Seq("2.13.8", "2.13.9", "2.13.10"))) +// prints: https://example.com/scala?version=2.13.8&version=2.13.9&version=2.13.10 + +println(getUri(Seq.empty)) +// prints: https://example.com/scala +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/http-client-what-else.md b/_overviews/toolkit/http-client-what-else.md new file mode 100644 index 0000000000..c467c7687a --- /dev/null +++ b/_overviews/toolkit/http-client-what-else.md @@ -0,0 +1,112 @@ +--- +title: What else can sttp do? +type: section +description: An incomplete list of features of sttp +num: 29 +previous-page: http-client-upload-file +next-page: web-server-intro +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +## Asynchronous requests + +To send a request asynchronously you can use a `DefaultFutureBackend`: + +{% tabs 'async' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import scala.concurrent.Future +import sttp.client4._ + +val asyncBackend = DefaultFutureBackend() +val response: Future[Response[String]] = quickRequest + .get(uri"https://example.com") + .send(asyncBackend) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import scala.concurrent.Future +import sttp.client4.* + +val asyncBackend = DefaultFutureBackend() +val response: Future[Response[String]] = quickRequest + .get(uri"https://example.com") + .send(asyncBackend) +``` +{% endtab %} +{% endtabs %} + +You can learn more about `Future`-based backends in the [sttp documentation](https://sttp.softwaremill.com/en/latest/backends/future.html). + +sttp supports other asynchronous wrappers such as Monix `Task`s, cats-effect `Effect`s, ZIO's `ZIO` type, and more. +You can see the full list of supported backends [here](https://sttp.softwaremill.com/en/latest/backends/summary.html). + +## Websockets + +You can use a `DefaultFutureBackend` to open a websocket, as follows. + +{% tabs 'ws' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:reset +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global + +import sttp.client4._ +import sttp.ws.WebSocket + +val asyncBackend = DefaultFutureBackend() + +def useWebSocket(ws: WebSocket[Future]): Future[Unit] = + for { + _ <- ws.sendText("Hello") + text <- ws.receiveText() + } yield { + println(text) + } + +val response = quickRequest + .get(uri"wss://ws.postman-echo.com/raw") + .response(asWebSocketAlways(useWebSocket)) + .send(asyncBackend) + +Await.result(response, Duration.Inf) +// prints: Hello +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global + +import sttp.client4.* +import sttp.ws.WebSocket + +val asyncBackend = DefaultFutureBackend() + +def useWebSocket(ws: WebSocket[Future]): Future[Unit] = + for + _ <- ws.sendText("Hello") + text <- ws.receiveText() + yield + println(text) + +val response = quickRequest + .get(uri"wss://ws.postman-echo.com/raw") + .response(asWebSocketAlways(useWebSocket)) + .send(asyncBackend) + +Await.result(response, Duration.Inf) +// prints: Hello +``` +{% endtab %} +{% endtabs %} + +Learn more about WebSockets in [sttp documentation](https://sttp.softwaremill.com/en/latest/other/websockets.html). + +## More features + +You can discover many more features such as streaming, logging, timeouts, and more in [sttp documentation](https://sttp.softwaremill.com/en/latest/quickstart.html#). diff --git a/_overviews/toolkit/introduction.md b/_overviews/toolkit/introduction.md new file mode 100644 index 0000000000..4e042e3575 --- /dev/null +++ b/_overviews/toolkit/introduction.md @@ -0,0 +1,82 @@ +--- +title: Introduction +type: chapter +description: Introducing the Scala Toolkit tutorials +num: 1 +languages: [ru] +previous-page: +next-page: testing-intro +toolkit-index: + - title: Tests + description: Testing code with MUnit. + icon: "fa fa-vial-circle-check" + link: /toolkit/testing-intro.html + - title: Files and Processes + description: Writing files and running processes with OS-Lib. + icon: "fa fa-folder-open" + link: /toolkit/os-intro.html + - title: JSON + description: Parsing JSON and serializing objects to JSON with uPickle. + icon: "fa fa-file-code" + link: /toolkit/json-intro.html + - title: HTTP Requests + description: Sending HTTP requests and uploading files with sttp. + icon: "fa fa-globe" + link: /toolkit/http-client-intro.html + - title: Web servers + description: Building web servers with Cask. + icon: "fa fa-server" + link: /toolkit/web-server-intro.html +--- + +## What is the Scala Toolkit? + +The Scala Toolkit is a set of libraries designed to effectively perform common programming tasks. It includes tools for working with files and processes, parsing JSON, sending HTTP requests, and unit testing. + +The Toolkit supports: +* Scala 3 and Scala 2 +* JVM, Scala.js, and Scala Native + +Use cases for the Toolkit include: + +- short-lived programs running on the JVM, to scrape a website, to collect and transform data, or to fetch and process some files, +- frontend scripts that run on the browser and power your websites, +- command-line tools packaged as native binaries for instant startup + +{% include inner-documentation-sections.html links=page.toolkit-index %} + +## What are these tutorials? + +This series of tutorials focuses on short code examples, to help you get started quickly. + +If you need more in-depth information, the tutorials include links to further documentation for all of the libraries in the toolkit. + +## How to run the code? + +You can follow the tutorials regardless of how you choose to run your +Scala code. The tutorials focus on the code itself, not on the process +of running it. + +Ways to run Scala code include: +* in your **browser** with [Scastie](https://scastie.scala-lang.org) + * pros: zero installation, online sharing + * cons: single-file only, online-only +* interactively in the Scala **REPL** (Read/Eval/Print Loop) + * pros: interactive exploration in the terminal + * cons: doesn't save your code anywhere +* interactively in a **worksheet** in your IDE such as [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) or [Metals](http://scalameta.org/metals/) + * pros: interactive exploration in a GUI + * cons: requires worksheet environment to run +* in **scripts**, using [Scala CLI](https://scala-cli.virtuslab.com) + * pros: terminal-based workflow with little setup + * cons: may not be suitable for large projects +* using a **build tool** (such as [sbt](https://www.scala-sbt.org) or [mill](https://com-lihaoyi.github.io/mill/)) + * pros: terminal-based workflow for projects of any size + * cons: requires some additional setup and learning +* using an **IDE** such as [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) or [Metals](http://scalameta.org/metals/) + * pros: GUI based workflow for projects of any size + * cons: requires some additional setup and learning + +These choices, with their pros and cons, are common to most programing +languages. +Feel free to use whichever option you're most comfortable with. diff --git a/_overviews/toolkit/json-deserialize.md b/_overviews/toolkit/json-deserialize.md new file mode 100644 index 0000000000..23fd6391d1 --- /dev/null +++ b/_overviews/toolkit/json-deserialize.md @@ -0,0 +1,121 @@ +--- +title: How to deserialize JSON to an object? +type: section +description: Parsing JSON to a custom data type +num: 19 +previous-page: json-modify +next-page: json-serialize +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +## Parsing vs. deserialization + +Parsing with uJson only accepts valid JSON, but it does not validate that the names and types of fields are as expected. + +Deserialization, on the other hand, transforms a JSON string to some user-specified Scala data type, if required fields are present and have the correct types. + +In this tutorial, we show how to deserialize to a `Map` and also to a custom `case class`. + +## Deserializing JSON to a `Map` + +For a type `T`, uPickle can deserialize JSON to a `Map[String, T]`, checking that all fields conform to `T`. + +We can for instance, deserialize to a `Map[String, List[Int]]`: + +{% tabs 'parsemap' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val json = """{"primes": [2, 3, 5], "evens": [2, 4, 6]} """ +val map: Map[String, List[Int]] = + upickle.default.read[Map[String, List[Int]]](json) + +println(map("primes")) +// prints: List(2, 3, 5) +``` +{% endtab %} +{% endtabs %} + +If a value is the wrong type, uPickle throws a `upickle.core.AbortException`. + +{% tabs 'parsemap-error' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:reset:crash +val json = """{"name": "Peter"} """ +upickle.default.read[Map[String, List[Int]]](json) +// throws: upickle.core.AbortException: expected sequence got string at index 9 +``` +{% endtab %} +{% endtabs %} + +### Deserializing JSON to a custom data type + +In Scala, you can use a `case class` to define your own data type. +For example, to represent a pet owner, you might: +```scala mdoc:reset +case class PetOwner(name: String, pets: List[String]) +``` + +To read a `PetOwner` from JSON, we must provide a `ReadWriter[PetOwner]`. +uPickle can do that automatically: + +{% tabs 'given' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import upickle.default._ + +implicit val ownerRw: ReadWriter[PetOwner] = macroRW[PetOwner] +``` +Some explanations: +- An `implicit val` is a value that can be automatically provided as an argument to a method or function call, without having to explicitly pass it. +- `macroRW` is a method provided by uPickle that can generate a instances of `ReadWriter` for case classes, using the information about its fields. +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default.* + +case class PetOwner(name: String, pets: List[String]) + derives ReadWriter +``` +The `derives` keyword is used to automatically generate given instances. +Using the compiler's knowledge of the fields in `PetOwner`, it generates a `ReadWriter[PetOwner]`. +{% endtab %} +{% endtabs %} + +This means that you can now read (and write) `PetOwner` objects from JSON with `upickle.default.read(petOwner)`. + +Notice that you do not need to pass the instance of `ReadWriter[PetOwner]` explicitly to the `read` method. But it does, nevertheless, get it from the context, as "given" value. You may find more information about contextual abstractions in the [Scala 3 Book](https://docs.scala-lang.org/scala3/book/ca-contextual-abstractions-intro.html). + +Putting everything together you should get: + +{% tabs 'full' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:reset +import upickle.default._ + +case class PetOwner(name: String, pets: List[String]) +implicit val ownerRw: ReadWriter[PetOwner] = macroRW + +val json = """{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}""" +val petOwner: PetOwner = read[PetOwner](json) + +val firstPet = petOwner.pets.head +println(s"${petOwner.name} has a pet called $firstPet") +// prints: Peter has a pet called Toolkitty +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default.* + +case class PetOwner(name: String, pets: List[String]) derives ReadWriter + +val json = """{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}""" +val petOwner: PetOwner = read[PetOwner](json) + +val firstPet = petOwner.pets.head +println(s"${petOwner.name} has a pet called $firstPet") +// prints: Peter has a pet called Toolkitty +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/json-files.md b/_overviews/toolkit/json-files.md new file mode 100644 index 0000000000..53072bb2b5 --- /dev/null +++ b/_overviews/toolkit/json-files.md @@ -0,0 +1,72 @@ +--- +title: How to read and write JSON files? +type: section +description: Reading and writing JSON files using UPickle and OSLib +num: 21 +previous-page: json-serialize +next-page: json-what-else +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +## Read and write raw JSON + +To read and write JSON files, you can use uJson and OS-Lib as follows: + +{% tabs 'raw' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +// read a JSON file +val json = ujson.read(os.read(os.pwd / "raw.json")) + +// modify the JSON content +json("updated") = "now" + +//write to a new file +os.write(os.pwd / "raw-updated.json", ujson.write(json)) +``` +{% endtab %} +{% endtabs %} + +## Read and write Scala objects using JSON + +To read and write Scala objects to and from JSON files, you can use uPickle and OS-Lib as follows: + +{% tabs 'object' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:compile-only +import upickle.default._ + +case class PetOwner(name: String, pets: List[String]) +implicit val ownerRw: ReadWriter[PetOwner] = macroRW + +// read a PetOwner from a JSON file +val petOwner: PetOwner = read[PetOwner](os.read(os.pwd / "pet-owner.json")) + +// create a new PetOwner +val petOwnerUpdated = petOwner.copy(pets = "Toolkitty" :: petOwner.pets) + +// write to a new file +os.write(os.pwd / "pet-owner-updated.json", write(petOwnerUpdated)) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default.* + +case class PetOwner(name: String, pets: List[String]) derives ReadWriter + +// read a PetOwner from a JSON file +val petOwner: PetOwner = read[PetOwner](os.read(os.pwd / "pet-owner.json")) + +// create a new PetOwner +val petOwnerUpdated = petOwner.copy(pets = "Toolkitty" :: petOwner.pets) + +// write to a new file +os.write(os.pwd / "pet-owner-updated.json", write(petOwnerUpdated)) +``` +{% endtab %} +{% endtabs %} + +To serialize and deserialize Scala case classes (or enums) to JSON we need an instance of `ReadWriter`. +To understand how uPickle generates it for you, you can read the [*How to deserialize JSON to an object?*](/toolkit/json-deserialize.html) or the [*How to serialize an object to JSON?*](/toolkit/json-serialize.html) tutorials. diff --git a/_overviews/toolkit/json-intro.md b/_overviews/toolkit/json-intro.md new file mode 100644 index 0000000000..9c5c789b49 --- /dev/null +++ b/_overviews/toolkit/json-intro.md @@ -0,0 +1,17 @@ +--- +title: Handling JSON with uPickle +type: chapter +description: Description of the uPickle library. +num: 16 +languages: [ru] +previous-page: os-what-else +next-page: json-parse +--- + +uPickle is a lightweight serialization library for Scala. + +It includes uJson, a JSON manipulation library that can parse JSON strings, access or mutate their values in memory, and write them back out again. + +uPickle can serialize and deserialize Scala objects directly to and from JSON. It knows how to handle the Scala collections such as `Map` and `Seq`, as well as your own data types, such as `case class`s and Scala 3 `enum`s. + +{% include markdown.html path="_markdown/install-upickle.md" %} diff --git a/_overviews/toolkit/json-modify.md b/_overviews/toolkit/json-modify.md new file mode 100644 index 0000000000..8f1cd63e6d --- /dev/null +++ b/_overviews/toolkit/json-modify.md @@ -0,0 +1,33 @@ +--- +title: How to modify JSON? +type: section +description: Modifying JSON with uPickle. +num: 18 +previous-page: json-parse +next-page: json-deserialize +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +`ujson.read` returns a mutable representation of JSON that you can update. Fields and elemnts can be added, modified, or removed. + +First you read the JSON string, then you update it in memory, and finally you write it back out again. + +{% tabs 'modify' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +// Parse the JSON string +val json: ujson.Value = ujson.read("""{"name":"John","pets":["Toolkitty","Scaniel"]}""") + +// Update it +json("name") = "Peter" +json("nickname") = "Pete" +json("pets").arr.remove(1) + +// Write it back to a String +val result: String = ujson.write(json) +println(result) +// prints: {"name":"Peter","pets":["Toolkitty"],"nickname":"Pete"} +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/json-parse.md b/_overviews/toolkit/json-parse.md new file mode 100644 index 0000000000..71b1fab391 --- /dev/null +++ b/_overviews/toolkit/json-parse.md @@ -0,0 +1,82 @@ +--- +title: How to access values inside JSON? +type: section +description: Accessing JSON values using ujson. +num: 17 +previous-page: json-intro +next-page: json-modify +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +## Accessing values inside JSON + +To parse a JSON string and access fields inside it, you can use uJson, which is part of uPickle. + +The method `ujson.read` parses a JSON string into memory: +{% tabs 'read' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val jsonString = """{"name": "Peter", "age": 13, "pets": ["Toolkitty", "Scaniel"]}""" +val json: ujson.Value = ujson.read(jsonString) +println(json("name").str) +// prints: Peter +``` +{% endtab %} +{% endtabs %} + +To access the `"name"` field, we do `json("name")` and then call `str` to type it as a string. + +To access the elements by index in a JSON array, + +{% tabs 'array' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val pets: ujson.Value = json("pets") + +val firstPet: String = pets(0).str +val secondPet: String = pets(1).str + +println(s"The pets are $firstPet and $secondPet") +// prints: The pets are Toolkitty and Scaniel +``` +{% endtab %} +{% endtabs %} + +You can traverse the JSON structure as deeply as you want, to extract any nested value. +For instance, `json("field1")(0)("field2").str` is the string value of `field2` in the first element of the array in `field1`. + +## JSON types + +In the previous examples we used `str` to type a JSON value as a string. +Similar methods exist for other types of values, namely: + - `num` for numeric values, returning `Double` + - `bool` for boolean values, returning `Boolean` + - `arr` for arrays, returning a mutable `Buffer[ujson.Value]` + - `obj` for objects, returning a mutable `Map[String, ujson.Value]` + +{% tabs 'typing' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:reset +import scala.collection.mutable + +val jsonString = """{"name": "Peter", "age": 13, "pets": ["Toolkitty", "Scaniel"]}""" +val json = ujson.read(jsonString) + +val person: mutable.Map[String, ujson.Value] = json.obj +val age: Double = person("age").num +val pets: mutable.Buffer[ujson.Value] = person("pets").arr +``` +{% endtab %} +{% endtabs %} + +If a JSON value does not conform to the expected type, uJson throws a `ujson.Value.InvalidData` exception. + +{% tabs 'exception' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:crash +val name: Boolean = person("name").bool +// throws a ujson.Value.InvalidData: Expected ujson.Bool (data: "Peter") +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/json-serialize.md b/_overviews/toolkit/json-serialize.md new file mode 100644 index 0000000000..bd3049def0 --- /dev/null +++ b/_overviews/toolkit/json-serialize.md @@ -0,0 +1,95 @@ +--- +title: How to serialize an object to JSON? +type: section +description: How to write JSON with Scala Toolkit. +num: 20 +previous-page: json-deserialize +next-page: json-files +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} + +## Serializing a Map to JSON + +uPickle can serialize your Scala objects to JSON, so that you can save them in files or send them over the network. + +By default it can serialize primitive types such as `Int` or `String`, as well as standard collections such as `Map` and `List`. + +{% tabs 'array' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val map: Map[String, Int] = + Map("Toolkitty" -> 3, "Scaniel" -> 5) +val jsonString: String = upickle.default.write(map) +println(jsonString) +// prints: {"Toolkitty":3,"Scaniel":5} +``` +{% endtab %} +{% endtabs %} + +## Serializing a custom object to JSON + +In Scala, you can use a `case class` to define your own data type. +For example, to represent a pet owner with the name of its pets, you can +```scala mdoc:reset +case class PetOwner(name: String, pets: List[String]) +``` + +To be able to write a `PetOwner` to JSON we need to provide a `ReadWriter` instance for the case class `PetOwner`. +Luckily, `upickle` is able to fully automate that: + +{% tabs 'given' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import upickle.default._ + +implicit val ownerRw: ReadWriter[PetOwner] = macroRW[PetOwner] +``` +Some explanations: +- An `implicit val` is a value that can be automatically provided as an argument to a method or function call, without having to explicitly pass it. +- `macroRW` is a method provided by uPickle that can generate a instances of `ReadWriter` for case classes, using the information about its fields. +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default.* + +case class PetOwner(name: String, pets: List[String]) derives ReadWriter +``` +The `derives` keyword is used to automatically generate given instances. +Using the compiler's knowledge of the fields in `PetOwner`, it generates a `ReadWriter[PetOwner]`. +{% endtab %} +{% endtabs %} + +This means that you can now write (and read) `PetOwner` objects to JSON with `upickle.default.write(petOwner)`. + +Notice that you do not need to pass the instance of `ReadWriter[PetOwner]` explicitly to the `write` method. But it does, nevertheless, get it from the context, as "given" value. You may find more information about contextual abstractions in the [Scala 3 Book](https://docs.scala-lang.org/scala3/book/ca-contextual-abstractions-intro.html). + +Putting everything together you should get: + +{% tabs 'full' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import upickle.default._ + +case class PetOwner(name: String, pets: List[String]) +implicit val ownerRw: ReadWriter[PetOwner] = macroRW + +val petOwner = PetOwner("Peter", List("Toolkitty", "Scaniel")) +val json: String = write(petOwner) +println(json) +// prints: {"name":"Peter","pets":["Toolkitty","Scaniel"]}" +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default._ + +case class PetOwner(name: String, pets: List[String]) derives ReadWriter + +val petOwner = PetOwner("Peter", List("Toolkitty", "Scaniel")) +val json: String = write(petOwner) +println(json) +// prints: {"name":"Peter","pets":["Toolkitty","Scaniel"]}" +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/json-what-else.md b/_overviews/toolkit/json-what-else.md new file mode 100644 index 0000000000..1f34daffe4 --- /dev/null +++ b/_overviews/toolkit/json-what-else.md @@ -0,0 +1,74 @@ +--- +title: What else can uPickle do? +type: section +description: An incomplete list of features of uPickle +num: 22 +previous-page: json-files +next-page: http-client-intro +--- + +{% include markdown.html path="_markdown/install-upickle.md" %} +## Construct a new JSON structure with uJson + +{% tabs construct%} +{% tab 'Scala 2 and Scala 3' %} +```scala mdoc +val obj: ujson.Value = ujson.Obj( + "library" -> "upickle", + "versions" -> ujson.Arr("1.6.0", "2.0.0", "3.1.0"), + "documentation" -> "https://com-lihaoyi.github.io/upickle/", +) +``` +{% endtab %} +{% endtabs %} + +Learn more about constructing JSON in the [uJson documentation](https://com-lihaoyi.github.io/upickle/#Construction). + +## Defining custom JSON serialization + +You can customize the `ReadWriter` of your data type by mapping the `ujson.Value`, like this: + +{% tabs custom-serializer class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import upickle.default._ + +case class Bar(i: Int, s: String) + +object Bar { + implicit val barReadWriter: ReadWriter[Bar] = readwriter[ujson.Value] + .bimap[Bar]( + x => ujson.Arr(x.s, x.i), + json => new Bar(json(1).num.toInt, json(0).str) + ) +} + +val bar = Bar(5, "bar") +val json = upickle.default.write(bar) +println(json) +// prints: [5, "bar"] +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import upickle.default.* + +case class Bar(i: Int, s: String) + +object Bar: + given ReadWriter[Bar] = readwriter[ujson.Value] + .bimap[Bar]( + x => ujson.Arr(x.s, x.i), + json => new Bar(json(1).num.toInt, json(0).str) + ) + +val bar = Bar(5, "bar") +val json = upickle.default.write(bar) +println(json) +// prints: [5, "bar"] +``` +{% endtab %} +{% endtabs %} + +Learn more about custom JSON serialization in the [uPickle documentation](https://com-lihaoyi.github.io/upickle/#Customization). + diff --git a/_overviews/toolkit/os-intro.md b/_overviews/toolkit/os-intro.md new file mode 100644 index 0000000000..d72387f045 --- /dev/null +++ b/_overviews/toolkit/os-intro.md @@ -0,0 +1,21 @@ +--- +title: Working with files and processes with OS-Lib +type: chapter +description: The introduction of the OS-lib library +num: 10 +languages: [ru] +previous-page: testing-what-else +next-page: os-read-directory +--- + +OS-Lib is a library for manipulating files and processes. It is part of the Scala Toolkit. + +OS-Lib aims to replace the `java.nio.file` and `java.lang.ProcessBuilder` APIs. You should not need to use any underlying Java APIs directly. + +OS-lib also aims to supplant the older `scala.io` and `scala.sys` APIs in the Scala standard library. + +OS-Lib has no dependencies. + +All of OS-Lib is in the `os.*` namespace. + +{% include markdown.html path="_markdown/install-os-lib.md" %} diff --git a/_overviews/toolkit/os-read-directory.md b/_overviews/toolkit/os-read-directory.md new file mode 100644 index 0000000000..b24b664a49 --- /dev/null +++ b/_overviews/toolkit/os-read-directory.md @@ -0,0 +1,59 @@ +--- +title: How to read a directory? +type: section +description: Reading a directory's contents with OS-Lib +num: 11 +previous-page: os-intro +next-page: os-read-file +--- + +{% include markdown.html path="_markdown/install-os-lib.md" %} + +## Paths + +A fundamental data type in OS-Lib is `os.Path`, representing a path +on the filesystem. An `os.Path` is always an absolute path. + +OS-Lib also provides `os.RelPath` (relative paths) and `os.SubPath` (a +relative path which cannot ascend to parent directories). + +Typical starting points for making paths are `os.pwd` (the +current working directory), `os.home` (the current user's home +directory), `os.root` (the root of the filesystem), or +`os.temp.dir()` (a new temporary directory). + +Paths have a `/` method for adding path segments. For example: + +{% tabs 'etc' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val etc: os.Path = os.root / "etc" +``` +{% endtab %} +{% endtabs %} + +## Reading a directory + +`os.list` returns the contents of a directory: + +{% tabs 'list-etc' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val entries: Seq[os.Path] = os.list(os.root / "etc") +``` +{% endtab %} +{% endtabs %} + +Or if we only want subdirectories: + +{% tabs 'subdirs' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val dirs: Seq[os.Path] = os.list(os.root / "etc").filter(os.isDir) +``` +{% endtab %} +{% endtabs %} + +To recursively descend an entire subtree, change `os.list` to +`os.walk`. To process results on the fly rather than reading them all +into memory first, substitute `os.walk.stream`. diff --git a/_overviews/toolkit/os-read-file.md b/_overviews/toolkit/os-read-file.md new file mode 100644 index 0000000000..bc23ca2588 --- /dev/null +++ b/_overviews/toolkit/os-read-file.md @@ -0,0 +1,64 @@ +--- +title: How to read a file? +type: section +description: Reading files from disk with OS-Lib +num: 12 +previous-page: os-read-directory +next-page: os-write-file +--- + +{% include markdown.html path="_markdown/install-os-lib.md" %} + +## Reading a file + +Supposing we have the path to a file: + +{% tabs 'path' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val path: os.Path = os.root / "usr" / "share" / "dict" / "words" +``` +{% endtab %} +{% endtabs %} + +Then we can slurp the entire file into a string with `os.read`: + +{% tabs slurp %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val content: String = os.read(path) +``` +{% endtab %} +{% endtabs %} + +To read the file as line at a time, substitute `os.read.lines`. + +We can find the longest word in the dictionary: + +{% tabs lines %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val lines: Seq[String] = os.read.lines(path) +println(lines.maxBy(_.size)) +// prints: antidisestablishmentarianism +``` +{% endtab %} +{% endtabs %} + +There's also `os.read.lines.stream` if you want to process the lines +on the fly rather than read them all into memory at once. For example, +if we just want to read the first line, the most efficient way is: + +{% tabs lines-stream %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val lineStream: geny.Generator[String] = os.read.lines.stream(path) +val firstLine: String = lineStream.head +println(firstLine) +// prints: A +``` +{% endtab %} +{% endtabs %} + +OS-Lib takes care of closing the file once the generator returned +by `stream` is exhausted. diff --git a/_overviews/toolkit/os-run-process.md b/_overviews/toolkit/os-run-process.md new file mode 100644 index 0000000000..7bbe4ace58 --- /dev/null +++ b/_overviews/toolkit/os-run-process.md @@ -0,0 +1,79 @@ +--- +title: How to run a process? +type: section +description: Starting external subprocesses with OS-Lib +num: 14 +previous-page: os-write-file +next-page: os-what-else +--- + +{% include markdown.html path="_markdown/install-os-lib.md" %} + +## Starting an external process + +To set up a process, use `os.proc`, then to actually start it, +`call()`: + +{% tabs 'touch' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val path: os.Path = os.pwd / "output.txt" +println(os.exists(path)) +// prints: false +val result: os.CommandResult = os.proc("touch", path).call() +println(result.exitCode) +// prints: 0 +println(os.exists(path)) +// prints: true +``` +{% endtab %} +{% endtabs %} + +Note that `proc` accepts both strings and `os.Path`s. + +## Reading the output of a process + +(The particular commands in the following examples might not exist on all +machines.) + +Above we saw that `call()` returned an `os.CommandResult`. We can +access the result's entire output with `out.text()`, or as lines +with `out.lines()`. + +For example, we could use `bc` to do some math for us: + +{% tabs 'bc' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val res: os.CommandResult = os.proc("bc", "-e", "2 + 2").call() +val text: String = res.out.text() +println(text.trim.toInt) +// prints: 4 +``` +{% endtab %} +{% endtabs %} + +Or have `cal` show us a calendar: + +{% tabs 'cal' %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:compile-only +val res: os.CommandResult = os.proc("cal", "-h", "2", "2023").call() +res.out.lines().foreach(println) +// prints: +// February 2023 +// Su Mo Tu We Th Fr Sa +// 1 2 3 4 +// ... +``` +{% endtab %} +{% endtabs %} + +## Customizing the process + +`call()` takes various optional arguments, too many to explain +individually here. For example, you can set the working directory +(`cwd = ...`), set environment variables (`env = ...`), or redirect +input and output (`stdin = ...`, `stdout = ...`, `stderr = ...`). +Find more information about the `call` method on [the README of OS-Lib](https://github.com/com-lihaoyi/os-lib#osproccall). + diff --git a/_overviews/toolkit/os-what-else.md b/_overviews/toolkit/os-what-else.md new file mode 100644 index 0000000000..886db4c81f --- /dev/null +++ b/_overviews/toolkit/os-what-else.md @@ -0,0 +1,19 @@ +--- +title: What else can OS-Lib do? +type: section +description: An incomplete list of features of OS-Lib +num: 15 +previous-page: os-run-process +next-page: json-intro +--- + +{% include markdown.html path="_markdown/install-os-lib.md" %} + +[OS-Lib on GitHub](https://github.com/com-lihaoyi/os-lib) has many additional examples of how to perform common tasks: +- creating, moving, copying, removing files and folders, +- reading filesystem metadata and permissions, +- spawning subprocesses, +- watching changes in folders, +- interoperating with `java.io.File` and `java.nio.Path`. + +See also Chapter 7 of Li Haoyi's book [_Hands-On Scala Programming_](https://www.handsonscala.com). (Li Haoyi is the author of OS-Lib.) diff --git a/_overviews/toolkit/os-write-file.md b/_overviews/toolkit/os-write-file.md new file mode 100644 index 0000000000..2bef6fff0c --- /dev/null +++ b/_overviews/toolkit/os-write-file.md @@ -0,0 +1,54 @@ +--- +title: How to write a file? +type: section +description: Writing files to disk with OS-Lib +num: 13 +previous-page: os-read-file +next-page: os-run-process +--- + +{% include markdown.html path="_markdown/install-os-lib.md" %} + +## Writing a file all at once + +`os.write` writes the supplied string to a new file: + +{% tabs write %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +val path: os.Path = os.temp.dir() / "output.txt" +os.write(path, "hello\nthere\n") +println(os.read.lines(path).size) +// prints: 2 +``` +{% endtab %} +{% endtabs %} + +## Overwriting or appending + +`os.write` throws an exception if the file already exists: + +{% tabs already-exists %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:crash +os.write(path, "this will fail") +// this exception is thrown: +// java.nio.file.FileAlreadyExistsException +``` +{% endtab %} +{% endtabs %} + +To avoid this, use `os.write.over` to replace the existing +contents. + +You can also use `os.write.append` to add more to the end: + +{% tabs append %} +{% tab 'Scala 2 and 3' %} +```scala mdoc +os.write.append(path, "two more\nlines\n") +println(os.read.lines(path).size) +// prints: 4 +``` +{% endtab %} +{% endtabs %} diff --git a/_overviews/toolkit/testing-asynchronous.md b/_overviews/toolkit/testing-asynchronous.md new file mode 100644 index 0000000000..1311af6b9b --- /dev/null +++ b/_overviews/toolkit/testing-asynchronous.md @@ -0,0 +1,88 @@ +--- +title: How to write asynchronous tests? +type: section +description: Writing asynchronous tests using MUnit +num: 7 +previous-page: testing-exceptions +next-page: testing-resources +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Asynchronous tests + +In Scala, it's common for an *asynchronous* method to return a `Future`. +MUnit offers special support for `Future`s. + +For example, consider an asynchronous variant of a `square` method: + +{% tabs 'async-1' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import scala.concurrent.{ExecutionContext, Future} + +object AsyncMathLib { + def square(x: Int)(implicit ec: ExecutionContext): Future[Int] = + Future(x * x) +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import scala.concurrent.{ExecutionContext, Future} + +object AsyncMathLib: + def square(x: Int)(using ExecutionContext): Future[Int] = + Future(x * x) +``` +{% endtab %} +{% endtabs %} + +A test itself can return a `Future[Unit]`. +MUnit will wait behind the scenes for the resulting `Future` to complete, failing the test if any assertion fails. + +You can therefore write the test as follows: + +{% tabs 'async-3' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +// Import the global execution context, required to call async methods +import scala.concurrent.ExecutionContext.Implicits.global + +class AsyncMathLibTests extends munit.FunSuite { + test("square") { + for { + squareOf3 <- AsyncMathLib.square(3) + squareOfMinus4 <- AsyncMathLib.square(-4) + } yield { + assertEquals(squareOf3, 9) + assertEquals(squareOfMinus4, 16) + } + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +// Import the global execution context, required to call async methods +import scala.concurrent.ExecutionContext.Implicits.global + +class AsyncMathLibTests extends munit.FunSuite: + test("square") { + for + squareOf3 <- AsyncMathLib.square(3) + squareOfMinus4 <- AsyncMathLib.square(-4) + yield + assertEquals(squareOf3, 9) + assertEquals(squareOfMinus4, 16) + } +``` +{% endtab %} +{% endtabs %} + +The test first asynchronously computes `square(3)` and `square(-4)`. +Once both computations are completed, and if they are both successful, it proceeds with the calls to `assertEquals`. +If any of the assertion fails, the resulting `Future[Unit]` fails, and MUnit will cause the test to fail. + +You may read more about asynchronous tests [in the MUnit documentation](https://scalameta.org/munit/docs/tests.html#declare-async-test). +It shows how to use other asynchronous types besides `Future`. diff --git a/_overviews/toolkit/testing-exceptions.md b/_overviews/toolkit/testing-exceptions.md new file mode 100644 index 0000000000..f2a880d68f --- /dev/null +++ b/_overviews/toolkit/testing-exceptions.md @@ -0,0 +1,67 @@ +--- +title: How to test exceptions? +type: section +description: Describe the intercept assertion +num: 6 +previous-page: testing-run-only +next-page: testing-asynchronous +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Intercepting an exception + +In a test, you can use `intercept` to check that your code throws an exception. + +{% tabs 'intercept-1' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +import java.nio.file.NoSuchFileException + +class FileTests extends munit.FunSuite { + test("read missing file") { + val missingFile = os.pwd / "missing.txt" + + intercept[NoSuchFileException] { + os.read(missingFile) + } + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.nio.file.NoSuchFileException + +class FileTests extends munit.FunSuite: + test("read missing file") { + val missingFile = os.pwd / "missing.txt" + intercept[NoSuchFileException] { + // the code that should throw an exception + os.read(missingFile) + } + } +``` +{% endtab %} +{% endtabs %} + +The type parameter of the `intercept` assertion is the expected exception. +Here it is `NoSuchFileException`. +The body of the `intercept` assertion contains the code that should throw the exception. + +The test passes if the code throws the expected exception and it fails otherwise. + +The `intercept` method returns the exception that is thrown. +You can check more assertions on it. + +{% tabs 'intercept-2' %} +{% tab 'Scala 2 and 3' %} +```scala +val exception = intercept[NoSuchFileException](os.read(missingFile)) +assert(clue(exception.getMessage).contains("missing.txt")) +``` +{% endtab %} +{% endtabs %} + +You can also use the more concise `interceptMessage` method to test the exception and its message in a single assertion. +Learn more about it in the [MUnit documentation](https://scalameta.org/munit/docs/assertions.html#interceptmessage). diff --git a/_overviews/toolkit/testing-intro.md b/_overviews/toolkit/testing-intro.md new file mode 100644 index 0000000000..4397be63b3 --- /dev/null +++ b/_overviews/toolkit/testing-intro.md @@ -0,0 +1,22 @@ +--- +title: Testing with MUnit +type: chapter +description: The introduction of the MUnit library +num: 2 +languages: [ru] +previous-page: introduction +next-page: testing-suite +--- + +MUnit is a lightweight testing library. It provides a single style for writing tests, a style that can be learned quickly. + +Despite its simplicity, MUnit has useful features such as: +- assertions to verify the behavior of the program +- fixtures to ensure that the tests have access to all the necessary resources +- asynchronous support, for testing concurrent and distributed applications. + +MUnit produces actionable error reports, with diff and source location, to help you quickly understand failures. + +Testing is essential for any software development process because it helps catch bugs early, improves code quality and facilitates collaboration. + +{% include markdown.html path="_markdown/install-munit.md" %} diff --git a/_overviews/toolkit/testing-resources.md b/_overviews/toolkit/testing-resources.md new file mode 100644 index 0000000000..50733256e7 --- /dev/null +++ b/_overviews/toolkit/testing-resources.md @@ -0,0 +1,102 @@ +--- +title: How to manage the resources of a test? +type: section +description: Describe the functional fixtures +num: 8 +previous-page: testing-asynchronous +next-page: testing-what-else +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## `FunFixture` + +In MUnit, we use functional fixtures to manage resources in a concise and safe way. +A `FunFixture` creates one resource for each test, ensuring that each test runs in isolation from the others. + +In a test suite, you can define and use a `FunFixture` as follows: + +{% tabs 'resources-1' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +class FileTests extends munit.FunSuite { + val usingTempFile: FunFixture[os.Path] = FunFixture( + setup = _ => os.temp(prefix = "file-tests"), + teardown = tempFile => os.remove(tempFile) + ) + usingTempFile.test("overwrite on file") { tempFile => + os.write.over(tempFile, "Hello, World!") + val obtained = os.read(tempFile) + assertEquals(obtained, "Hello, World!") + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +class FileTests extends munit.FunSuite: + val usingTempFile: FunFixture[os.Path] = FunFixture( + setup = _ => os.temp(prefix = "file-tests"), + teardown = tempFile => os.remove(tempFile) + ) + usingTempFile.test("overwrite on file") { tempFile => + os.write.over(tempFile, "Hello, World!") + val obtained = os.read(tempFile) + assertEquals(obtained, "Hello, World!") + } +``` +{% endtab %} +{% endtabs %} + +`usingTempFile` is a fixture of type `FunFixture[os.Path]`. +It contains two functions: + - The `setup` function, of type `TestOptions => os.Path`, creates a new temporary file. + - The `teardown` function, of type `os.Path => Unit`, deletes this temporary file. + +We use the `usingTempFile` fixture to define a test that needs a temporary file. +Notice that the body of the test takes a `tempFile`, of type `os.Path`, as parameter. +The fixture automatically creates this temporary file, calls its `setup` function, and cleans it up after the test by calling `teardown`. + +In the example, we used a fixture to manage a temporary file. +In general, fixtures can manage other kinds of resources, such as a temporary folder, a temporary table in a database, a connection to a local server, and so on. + +## Composing `FunFixture`s + +In some tests, you may need more than one resource. +You can use `FunFixture.map2` to compose two functional fixtures into one. + +{% tabs 'resources-2' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val using2TempFiles: FunFixture[(os.Path, os.Path)] = + FunFixture.map2(usingTempFile, usingTempFile) + +using2TempFiles.test("merge two files") { + (file1, file2) => + // body of the test +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +val using2TempFiles: FunFixture[(os.Path, os.Path)] = + FunFixture.map2(usingTempFile, usingTempFile) + +using2TempFiles.test("merge two files") { + (file1, file2) => + // body of the test +} +``` +{% endtab %} +{% endtabs %} + +Using `FunFixture.map2` on a `FunFixture[A]` and a `FunFixture[B]` returns a `FunFixture[(A, B)]`. + +## Other fixtures + +`FunFixture` is the recommended type of fixture because: +- it is explicit: each test declares the resource they need, +- it is safe to use: each test uses its own resource in isolation. + +For more flexibility, `MUnit` contains other types of fixtures: the reusable fixture, the ad-hoc fixture and the asynchronous fixture. +Learn more about them in the [MUnit documentation](https://scalameta.org/munit/docs/fixtures.html). diff --git a/_overviews/toolkit/testing-run-only.md b/_overviews/toolkit/testing-run-only.md new file mode 100644 index 0000000000..1469fbd577 --- /dev/null +++ b/_overviews/toolkit/testing-run-only.md @@ -0,0 +1,111 @@ +--- +title: How to run a single test? +type: section +description: About testOnly in the build tool and .only in MUnit +num: 5 +previous-page: testing-run +next-page: testing-exceptions +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Running a single test suite + +{% tabs munit-unit-test-only class=tabs-build-tool %} +{% tab 'Scala CLI' %} +To run a single `example.MyTests` suite with Scala CLI, use the `--test-only` option of the `test` command. +``` +scala-cli test example --test-only example.MyTests +``` + +{% endtab %} +{% tab 'sbt' %} +To run a single `example.MyTests` suite in sbt, use the `testOnly` task: +``` +sbt:example> testOnly example.MyTests +``` +{% endtab %} +{% tab 'Mill' %} +To run a single `example.MyTests` suite in Mill, use the `testOnly` task: +``` +./mill example.test.testOnly example.MyTests +``` +{% endtab %} +{% endtabs %} + +## Running a single test in a test suite + +Within a test suite file, you can select individual tests to run by temporarily appending `.only`, e.g. + +{% tabs 'only-demo' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc +class MathSuite extends munit.FunSuite { + test("addition") { + assert(1 + 1 == 2) + } + test("multiplication".only) { + assert(3 * 7 == 21) + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +class MathSuite extends munit.FunSuite: + test("addition") { + assert(1 + 1 == 2) + } + test("multiplication".only) { + assert(3 * 7 == 21) + } +``` +{% endtab %} +{% endtabs %} + +In the above example, only the `"multiplication"` tests will run (i.e. `"addition"` is ignored). +This is useful to quickly debug a specific test in a suite. + +## Alternative: excluding specific tests + +You can exclude specific tests from running by appending `.ignore` to the test name. +For example the following ignores the `"addition"` test, and run all the others: + +{% tabs 'ignore-demo' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:reset +class MathSuite extends munit.FunSuite { + test("addition".ignore) { + assert(1 + 1 == 2) + } + test("multiplication") { + assert(3 * 7 == 21) + } + test("remainder") { + assert(13 % 5 == 3) + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +class MathSuite extends munit.FunSuite: + test("addition".ignore) { + assert(1 + 1 == 2) + } + test("multiplication") { + assert(3 * 7 == 21) + } + test("remainder") { + assert(13 % 5 == 3) + } +``` +{% endtab %} +{% endtabs %} + +## Use tags to group tests, and run specific tags + +MUnit lets you group and run tests across suites by tags, which are textual labels. +[The MUnit docs][munit-tags] have instructions on how to do this. + +[munit-tags]: https://scalameta.org/munit/docs/filtering.html#include-and-exclude-tests-based-on-tags diff --git a/_overviews/toolkit/testing-run.md b/_overviews/toolkit/testing-run.md new file mode 100644 index 0000000000..09bc23e73a --- /dev/null +++ b/_overviews/toolkit/testing-run.md @@ -0,0 +1,92 @@ +--- +title: How to run tests? +type: section +description: Running the MUnit tests +num: 4 +previous-page: testing-suite +next-page: testing-run-only +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Running the tests + +You can run all of your test suites with a single command. + +{% tabs munit-unit-test-4 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +Using Scala CLI, the following command runs all the tests in the folder `example`: +``` +scala-cli test example +# Compiling project (test, Scala 3.2.1, JVM) +# Compiled project (test, Scala 3.2.1, JVM) +# MyTests: +# + sum of two integers 0.009s +``` +{% endtab %} +{% tab 'sbt' %} +In the sbt shell, the following command runs all the tests of the project `example`: +``` +sbt:example> test +# MyTests: +# + sum of two integers 0.006s +# [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 +# [success] Total time: 0 s, completed Nov 11, 2022 12:54:08 PM +``` +{% endtab %} +{% tab 'Mill' %} +In Mill, the following command runs all the tests of the module `example`: +``` +./mill example.test.test +# [71/71] example.test.test +# MyTests: +# + sum of two integers 0.008s +``` +{% endtab %} +{% endtabs %} + +The test report, printed in the console, shows the status of each test. +The `+` symbol before a test name shows that the test passed successfully. + +Add and run a failing test to see how a failure looks: + +{% tabs assertions-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +test("failing test") { + val obtained = 2 + 3 + val expected = 4 + assertEquals(obtained, expected) +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +test("failing test") { + val obtained = 2 + 3 + val expected = 4 + assertEquals(obtained, expected) +} +``` +{% endtab %} +{% endtabs %} + +``` +# MyTests: +# + sum of two integers 0.008s +# ==> X MyTests.failing test 0.015s munit.ComparisonFailException: ./MyTests.test.scala:13 +# 12: val expected = 4 +# 13: assertEquals(obtained, expected) +# 14: } +# values are not the same +# => Obtained +# 5 +# => Diff (- obtained, + expected) +# -5 +# +4 +# at munit.Assertions.failComparison(Assertions.scala:274) +``` + +The line starting with `==> X` indicates that the test named `failing test` fails. +The following lines show where and how it failed. +Here it shows that the obtained value is 5, where 4 was expected. diff --git a/_overviews/toolkit/testing-suite.md b/_overviews/toolkit/testing-suite.md new file mode 100644 index 0000000000..7c068edfe7 --- /dev/null +++ b/_overviews/toolkit/testing-suite.md @@ -0,0 +1,130 @@ +--- +title: How to write tests? +type: section +description: The basics of writing a test suite with MUnit +num: 3 +previous-page: testing-intro +next-page: testing-run +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Writing a test suite + +A group of tests in a single class is called a test class or test suite. + +Each test suite validates a particular component or feature of the software. +Typically we define one test suite for each source file or class that we want to test. + +{% tabs munit-unit-test-2 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +In Scala CLI, the test file can live in the same folder as the actual code, but the name of the file must end with `.test.scala`. +In the following, `MyTests.test.scala` is a test file. +``` +example/ +├── MyApp.scala +└── MyTests.test.scala +``` +Other valid structures and conventions are described in the [Scala CLI documentation](https://scala-cli.virtuslab.org/docs/commands/test/#test-sources). +{% endtab %} +{% tab 'sbt' %} +In sbt, test sources go in the `src/test/scala` folder. + +For instance, the following is the file structure of a project `example`: +``` +example +└── src + ├── main + │ └── scala + │ └── MyApp.scala + └── test + └── scala + └── MyTests.scala +``` +{% endtab %} +{% tab 'Mill' %} +In Mill, test sources go in the `test/src` folder. + +For instance, the following is the file structure of a module `example`: +``` +example +└── src +| └── MyApp.scala +└── test + └── src + └── MyTests.scala +``` +{% endtab %} +{% endtabs %} + +In the test source file, define a suite containing a single test: + +{% tabs munit-unit-test-3 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package example + +class MyTests extends munit.FunSuite { + test("sum of two integers") { + val obtained = 2 + 2 + val expected = 4 + assertEquals(obtained, expected) + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +package example + +class MyTests extends munit.FunSuite: + test("sum of two integers") { + val obtained = 2 + 2 + val expected = 4 + assertEquals(obtained, expected) + } +``` +{% endtab %} +{% endtabs %} + +A test suite is a Scala class that extends `munit.FunSuite`. +It contains one or more tests, each defined by a call to the `test` method. + +In the previous example, we have a single test `"sum of integers"` that checks that `2 + 2` equals `4`. +We use the assertion method `assertEquals` to check that two values are equal. +The test passes if all the assertions are correct and fails otherwise. + +## Assertions + +It is important to use assertions in each and every test to describe what to check. +The main assertion methods in MUnit are: +- `assertEquals` to check that what you obtain is equal to what you expected +- `assert` to check a boolean condition + +The following is an example of a test that use `assert` to check a boolean condition on a list. + +{% tabs assertions-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +test("all even numbers") { + val input: List[Int] = List(1, 2, 3, 4) + val obtainedResults: List[Int] = input.map(_ * 2) + // check that obtained values are all even numbers + assert(obtainedResults.forall(x => x % 2 == 0)) +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +test("all even numbers") { + val input: List[Int] = List(1, 2, 3, 4) + val obtainedResults: List[Int] = input.map(_ * 2) + // check that obtained values are all even numbers + assert(obtainedResults.forall(x => x % 2 == 0)) +} +``` +{% endtab %} +{% endtabs %} + +MUnit contains more assertion methods that you can discover in its [documentation](https://scalameta.org/munit/docs/assertions.html): +`assertNotEquals`, `assertNoDiff`, `fail`, and `compileErrors`. diff --git a/_overviews/toolkit/testing-what-else.md b/_overviews/toolkit/testing-what-else.md new file mode 100644 index 0000000000..4c0672af1a --- /dev/null +++ b/_overviews/toolkit/testing-what-else.md @@ -0,0 +1,85 @@ +--- +title: What else can MUnit do? +type: section +description: A incomplete list of features of MUnit +num: 9 +previous-page: testing-resources +next-page: os-intro +--- + +{% include markdown.html path="_markdown/install-munit.md" %} + +## Adding clues to get better error report + +Use `clue` inside an `assert` to a get a better error report when the assertion fails. + +{% tabs clues %} +{% tab 'Scala 2 and 3' %} +```scala +assert(clue(List(a).head) > clue(b)) +// munit.FailException: assertion failed +// Clues { +// List(a).head: Int = 1 +// b: Int = 2 +// } +``` +{% endtab %} +{% endtabs %} + +Learn more about clues in the [MUnit documentation](https://scalameta.org/munit/docs/assertions.html#assert). + +## Writing environment-specific tests + +Use `assume` to write environment-specific tests. +`assume` can contain a boolean condition. You can check the operating system, the Java version, a Java property, an environment variable, or anything else. +A test is skipped if one of its assumptions isn't met. + +{% tabs assumption class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.util.Properties + +test("home directory") { + assume(Properties.isLinux, "this test runs only on Linux") + assert(os.home.toString.startsWith("/home/")) +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import scala.util.Properties + +test("home directory") { + assume(Properties.isLinux, "this test runs only on Linux") + assert(os.home.toString.startsWith("/home/")) +} +``` +{% endtab %} +{% endtabs %} + +Learn more about filtering tests in the [MUnit documentation](https://scalameta.org/munit/docs/filtering.html). + +## Tagging flaky tests + +You can tag a test with `flaky` to mark it as being flaky. +Flaky tests can be skipped by setting the `MUNIT_FLAKY_OK` environment variable to `true`. + +{% tabs flaky class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +test("requests".flaky) { + // I/O heavy tests that sometimes fail +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +test("requests".flaky) { + // I/O heavy tests that sometimes fail +} +``` +{% endtab %} +{% endtabs %} + +Learn more about flaky tests in the [MUnit documentation](https://scalameta.org/munit/docs/tests.html#tag-flaky-tests) + diff --git a/_overviews/toolkit/web-server-cookies-and-decorators.md b/_overviews/toolkit/web-server-cookies-and-decorators.md new file mode 100644 index 0000000000..36caeac4de --- /dev/null +++ b/_overviews/toolkit/web-server-cookies-and-decorators.md @@ -0,0 +1,188 @@ +--- +title: How to use cookies and decorators? +type: section +description: Using cookies and decorators with Cask +num: 36 +previous-page: web-server-websockets +next-page: +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +## Using cookies + +Cookies are saved by adding them to the `cookies` parameter of the `cask.Response` constructor. + +In this example, we are building a rudimentary authentication service. The `getLogin` method provides a form where +the user can enter their username and password. The `postLogin` method reads the credentials. If they match the expected ones, it generates a session +identifier is generated, saves it in the application state, and sends back a cookie with the identifier. + +Cookies can be read either with a method parameter of `cask.Cookie` type or by accessing the `cask.Request` directly. +If using the former method, the names of parameters have to match the names of cookies. If a cookie with a matching name is not +found, an error response will be returned. In the `checkLogin` function, the former method is used, as the cookie is not +present before the user logs in. + +To delete a cookie, set its `expires` parameter to an instant in the past, for example `Instant.EPOCH`. + +{% tabs web-server-cookies-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +object Example extends cask.MainRoutes { + + val sessionIds = ConcurrentHashMap.newKeySet[String]() + + @cask.get("/login") + def getLogin(): cask.Response[String] = { + val html = + """<!doctype html> + |<html> + |<body> + |<form action="/login" method="post"> + | <label for="name">Username:</label><br> + | <input type="text" name="name" value=""><br> + | <label for="password">Password:</label><br> + | <input type="text" name="password" value=""><br><br> + | <input type="submit" value="Submit"> + |</form> + |</body> + |</html>""".stripMargin + + cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) + } + + @cask.postForm("/login") + def postLogin(name: String, password: String): cask.Response[String] = { + if (name == "user" && password == "password") { + val sessionId = UUID.randomUUID().toString + sessionIds.add(sessionId) + cask.Response(data = "Success!", cookies = Seq(cask.Cookie("sessionId", sessionId))) + } else { + cask.Response(data = "Authentication failed", statusCode = 401) + } + } + + @cask.get("/check") + def checkLogin(request: cask.Request): String = { + val sessionId = request.cookies.get("sessionId") + if (sessionId.exists(cookie => sessionIds.contains(cookie.value))) { + "You are logged in" + } else { + "You are not logged in" + } + } + + @cask.get("/logout") + def logout(sessionId: cask.Cookie) = { + sessionIds.remove(sessionId.value) + cask.Response(data = "Successfully logged out!", cookies = Seq(cask.Cookie("sessionId", "", expires = Instant.EPOCH))) + } + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +object Example extends cask.MainRoutes: + + val sessionIds = ConcurrentHashMap.newKeySet[String]() + + @cask.get("/login") + def getLogin(): cask.Response[String] = + val html = + """<!doctype html> + |<html> + |<body> + |<form action="/login" method="post"> + | <label for="name">Username:</label><br> + | <input type="text" name="name" value=""><br> + | <label for="password">Password:</label><br> + | <input type="text" name="password" value=""><br><br> + | <input type="submit" value="Submit"> + |</form> + |</body> + |</html>""".stripMargin + + cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) + + @cask.postForm("/login") + def postLogin(name: String, password: String): cask.Response[String] = + if name == "user" && password == "password" then + val sessionId = UUID.randomUUID().toString + sessionIds.add(sessionId) + cask.Response(data = "Success!", cookies = Seq(cask.Cookie("sessionId", sessionId))) + else + cask.Response(data = "Authentication failed", statusCode = 401) + + @cask.get("/check") + def checkLogin(request: cask.Request): String = + val sessionId = request.cookies.get("sessionId") + if sessionId.exists(cookie => sessionIds.contains(cookie.value)) then + "You are logged in" + else + "You are not logged in" + + @cask.get("/logout") + def logout(sessionId: cask.Cookie): cask.Response[String] = + sessionIds.remove(sessionId.value) + cask.Response(data = "Successfully logged out!", cookies = Seq(cask.Cookie("sessionId", "", expires = Instant.EPOCH))) + + initialize() +``` +{% endtab %} +{% endtabs %} + +## Using decorators + +Decorators can be used for extending endpoints functionality with validation or new parameters. They are defined by extending +`cask.RawDecorator` class. They are used as annotations. + +In this example, the `loggedIn` decorator is used to check if the user is logged in before accessing the `/decorated` +endpoint. + +The decorator class can pass additional arguments to the decorated endpoint using a map. The passed arguments are available +through the last argument group. Here we are passing the session identifier to an argument named `sessionId`. + +{% tabs web-server-cookies-2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +class loggedIn extends cask.RawDecorator { + override def wrapFunction(ctx: cask.Request, delegate: Delegate): Result[Raw] = { + ctx.cookies.get("sessionId") match { + case Some(cookie) if sessionIds.contains(cookie.value) => delegate(Map("sessionId" -> cookie.value)) + case _ => cask.router.Result.Success(cask.model.Response("You aren't logged in", 403)) + } + } +} + +@loggedIn() +@cask.get("/decorated") +def decorated()(sessionId: String): String = { + s"You are logged in with id: $sessionId" +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +class loggedIn extends cask.RawDecorator: + override def wrapFunction(ctx: cask.Request, delegate: Delegate): Result[Raw] = + ctx.cookies.get("sessionId") match + case Some(cookie) if sessionIds.contains(cookie.value) => + delegate(Map("sessionId" -> cookie.value)) + case _ => + cask.router.Result.Success(cask.model.Response("You aren't logged in", 403)) + + +@loggedIn() +@cask.get("/decorated") +def decorated()(sessionId: String): String = s"You are logged in with id: $sessionId" +``` +{% endtab %} +{% endtabs %} \ No newline at end of file diff --git a/_overviews/toolkit/web-server-dynamic.md b/_overviews/toolkit/web-server-dynamic.md new file mode 100644 index 0000000000..c7bc84ac97 --- /dev/null +++ b/_overviews/toolkit/web-server-dynamic.md @@ -0,0 +1,237 @@ +--- +title: How to serve a dynamic page? +type: section +description: Serving a dynamic page with Cask +num: 32 +previous-page: web-server-static +next-page: web-server-query-parameters +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +## Serving dynamically generated content + +You can create an endpoint returning dynamically generated content with the `@cask.get` annotation. + +{% tabs web-server-dynamic-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.time.ZonedDateTime + +object Example extends cask.MainRoutes { + @cask.get("/time") + def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}" + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.time.ZonedDateTime + +object Example extends cask.MainRoutes: + @cask.get("/time") + def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}" + + initialize() +``` +{% endtab %} +{% endtabs %} + +The example above creates an endpoint that returns the current date and time available at `/time`. The exact response will be +recreated every time you refresh the webpage. + +Since the endpoint method has the `String` output type, the result will be sent with the `text/plain` [content type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type). +If you want an HTML output to be interpreted by the browser, you will need to set the `Content-Type` header manually +or [use the Scalatags templating library](/toolkit/web-server-dynamic.html#using-html-templates), supported by Cask. + +### Running the example + +Run the example the same way as before, assuming you use the same project structure as described in [the static file tutorial](/toolkit/web-server-static.html). + +{% tabs web-server-dynamic-2 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +In the terminal, the following command will start the server: +``` +scala-cli run Example.scala +``` +{% endtab %} +{% tab 'sbt' %} +In the terminal, the following command will start the server: +``` +sbt example/run +``` +{% endtab %} +{% tab 'Mill' %} +In the terminal, the following command will start the server: +``` +./mill run +``` +{% endtab %} +{% endtabs %} + +Access the endpoint at [http://localhost:8080/time](http://localhost:8080/time). You should see a result similar to the one below. + +``` +Current date is: 2024-07-22T09:07:05.752534+02:00[Europe/Warsaw] +``` + +## Using path segments + +Cask gives you the ability to access segments of the URL path within the endpoint function. +Building on the example above, you can add a segment to specify that the endpoint should return the date and time +in a given city. + +{% tabs web-server-dynamic-3 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes { + + private def getZoneIdForCity(city: String): Option[ZoneId] = { + import scala.jdk.CollectionConverters._ + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + } + + @cask.get("/time/:city") + def dynamicWithCity(city: String): String = { + getZoneIdForCity(city) match { + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + } + } + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes: + + private def getZoneIdForCity(city: String): Option[ZoneId] = + import scala.jdk.CollectionConverters.* + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + + @cask.get("/time/:city") + def dynamicWithCity(city: String): String = + getZoneIdForCity(city) match + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + + initialize() +``` +{% endtab %} +{% endtabs %} + +In the example above, the `:city` segment in `/time/:city` is available through the `city` argument of the endpoint method. +The name of the argument must be identical to the segment name. The `getZoneIdForCity` helper method finds the timezone for +a given city, and then the current date and time are translated to that timezone. + +Accessing the endpoint at [http://localhost:8080/time/Paris](http://localhost:8080/time/Paris) will result in: +``` +Current date is: 2024-07-22T09:08:33.806259+02:00[Europe/Paris] +``` + +You can use more than one path segment in an endpoint by adding more arguments to the endpoint method. It's also possible to use paths +with an unspecified number of segments (for example `/path/foo/bar/baz/`) by giving the endpoint method an argument with `cask.RemainingPathSegments` type. +Consult the [documentation](https://com-lihaoyi.github.io/cask/index.html#variable-routes) for more details. + +## Using HTML templates + +To create an HTML response, you can combine Cask with the [Scalatags](https://com-lihaoyi.github.io/scalatags/) templating library. + +Import the Scalatags library: + +{% tabs web-server-dynamic-4 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +Add the Scalatags dependency in `Example.sc` file: +```scala +//> using dep com.lihaoyi::scalatags::0.13.1 +``` +{% endtab %} +{% tab 'sbt' %} +Add the Scalatags dependency in `build.sbt` file: +```scala +libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.13.1" +``` +{% endtab %} +{% tab 'Mill' %} +Add the Scalatags dependency in `build.cs` file: +```scala +ivy"com.lihaoyi::scalatags::0.13.1" +``` +{% endtab %} +{% endtabs %} + +Now the example above can be rewritten to use a template. Cask will build a response out of the `doctype` automatically, +setting the `Content-Type` header to `text/html`. + +{% tabs web-server-dynamic-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.time.{ZoneId, ZonedDateTime} +import scalatags.Text.all._ + +object Example extends cask.MainRoutes { + + private def getZoneIdForCity(city: String): Option[ZoneId] = { + import scala.jdk.CollectionConverters._ + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + } + + @cask.get("/time/:city") + def dynamicWithCity(city: String): doctype = { + val text = getZoneIdForCity(city) match { + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + } + + doctype("html")( + html( + body( + p(text) + ) + ) + ) + } + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.time.{ZoneId, ZonedDateTime} +import scalatags.Text.all.* + +object Example extends cask.MainRoutes: + + private def getZoneIdForCity(city: String): Option[ZoneId] = + import scala.jdk.CollectionConverters.* + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + + @cask.get("/time/:city") + def dynamicWithCity(city: String): doctype = + val text = getZoneIdForCity(city) match + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + doctype("html")( + html( + body( + p(text) + ) + ) + ) + + initialize() +``` +{% endtab %} +{% endtabs %} + +Here we get the text of the response and wrap it in a Scalatags template. Notice that the return type changed from `String` +to `doctype`. diff --git a/_overviews/toolkit/web-server-input.md b/_overviews/toolkit/web-server-input.md new file mode 100644 index 0000000000..8be4c659d3 --- /dev/null +++ b/_overviews/toolkit/web-server-input.md @@ -0,0 +1,243 @@ +--- +title: How to handle user input? +type: section +description: Handling user input with Cask +num: 34 +previous-page: web-server-query-parameters +next-page: web-server-websockets +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +## Handling form-encoded input + +To create an endpoint that handles the data provided in an HTML form, use the `@cask.postForm` annotation. Add arguments to the endpoint method +with names corresponding to names of fields in the form and set the form method to `post`. + +{% tabs web-server-input-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object Example extends cask.MainRoutes { + + @cask.get("/form") + def getForm(): cask.Response[String] = { + val html = + """<!doctype html> + |<html> + |<body> + |<form action="/form" method="post"> + | <label for="name">First name:</label><br> + | <input type="text" name="name" value=""><br> + | <label for="surname">Last name:</label><br> + | <input type="text" name="surname" value=""><br><br> + | <input type="submit" value="Submit"> + |</form> + |</body> + |</html>""".stripMargin + + cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) + } + + @cask.postForm("/form") + def formEndpoint(name: String, surname: String): String = + "Hello " + name + " " + surname + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Example extends cask.MainRoutes: + + @cask.get("/form") + def getForm(): cask.Response[String] = + val html = + """<!doctype html> + |<html> + |<body> + |<form action="/form" method="post"> + | <label for="name">First name:</label><br> + | <input type="text" name="name" value=""><br> + | <label for="surname">Last name:</label><br> + | <input type="text" name="surname" value=""><br><br> + | <input type="submit" value="Submit"> + |</form> + |</body> + |</html>""".stripMargin + + cask.Response(data = html, headers = Seq("Content-Type" -> "text/html")) + + @cask.postForm("/form") + def formEndpoint(name: String, surname: String): String = + "Hello " + name + " " + surname + + initialize() +``` +{% endtab %} +{% endtabs %} + +In this example we create a form asking for name and surname of a user and then redirect the user to a greeting page. Notice the +use of `cask.Response`. The `cask.Response` type allows the user to set the status code, headers and cookies. The default +content type for an endpoint method returning a `String` is `text/plain`. Set it to `text/html` in order for the browser to display the form correctly. + +The `formEndpoint` endpoint reads the form data using the `name` and `surname` parameters. The names of parameters must +be identical to the field names of the form. + +## Handling JSON-encoded input + +JSON fields are handled in the same way as form fields, except that we use the `@cask.PostJson` annotation. The fields +will be read into the endpoint method arguments. + +{% tabs web-server-input-2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object Example extends cask.MainRoutes { + + @cask.postJson("/json") + def jsonEndpoint(name: String, surname: String): String = + "Hello " + name + " " + surname + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Example extends cask.MainRoutes: + + @cask.postJson("/json") + def jsonEndpoint(name: String, surname: String): String = + "Hello " + name + " " + surname + + initialize() +``` +{% endtab %} +{% endtabs %} + +Send the POST request using `curl`: + +```shell +curl --header "Content-Type: application/json" \ + --data '{"name":"John","surname":"Smith"}' \ + http://localhost:8080/json +``` + +The response will be: +``` +Hello John Smith +``` + +The endpoint will accept JSONs that have only the fields with names specified as the endpoint method arguments. If there +are more fields than expected, some fields are missing or have an incorrect data type, an error message +will be returned with the response code 400. + +To handle the case when the fields of the JSON are not known in advance, you can use an argument with the `ujson.Value` type, +from uPickle library. + +{% tabs web-server-input-3 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object Example extends cask.MainRoutes { + + @cask.postJson("/json") + def jsonEndpoint(value: ujson.Value): String = + value.toString + + initialize() +} + +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Example extends cask.MainRoutes: + + @cask.postJson("/json") + def jsonEndpoint(value: ujson.Value): String = + value.toString + + initialize() + +``` +{% endtab %} +{% endtabs %} + +In this example the JSON is merely converted to `String`. Check the [*uPickle tutorial*](/toolkit/json-intro.html) for more information +on what can be done with the `ujson.Value` type. + +Send a POST request. +```shell +curl --header "Content-Type: application/json" \ + --data '{"value":{"name":"John","surname":"Smith"}}' \ + http://localhost:8080/json2 +``` + +The server will respond with: +``` +"{\"name\":\"John\",\"surname\":\"Smith\"}" +``` + +## Handling JSON-encoded output + +Cask endpoints can return JSON objects returned by uPickle library functions. Cask will automatically handle the `ujson.Value` +type and set the `Content-Type` header to `application/json`. + +In this example, the `TimeData` case class stores the information about the time zone and current time in a chosen +location. To serialize a case class into JSON, use type class derivation or define the serializer in its companion object in the case of Scala 2. + +{% tabs web-server-input-4 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes { + import upickle.default.{ReadWriter, macroRW, writeJs} + case class TimeData(timezone: Option[String], time: String) + object TimeData { + implicit val rw: ReadWriter[TimeData] = macroRW + } + + private def getZoneIdForCity(city: String): Option[ZoneId] = { + import scala.jdk.CollectionConverters._ + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + } + + @cask.get("/time_json/:city") + def timeJSON(city: String): ujson.Value = { + val timezone = getZoneIdForCity(city) + val time = timezone match { + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + } + writeJs(TimeData(timezone.map(_.toString), time)) + } + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes: + import upickle.default.{ReadWriter, writeJs} + case class TimeData(timezone: Option[String], time: String) derives ReadWriter + + private def getZoneIdForCity(city: String): Option[ZoneId] = + import scala.jdk.CollectionConverters.* + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + + @cask.get("/time_json/:city") + def timeJSON(city: String): ujson.Value = + val timezone = getZoneIdForCity(city) + val time = timezone match + case Some(zoneId)=> s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + writeJs(TimeData(timezone.map(_.toString), time)) + + initialize() +``` +{% endtab %} +{% endtabs %} \ No newline at end of file diff --git a/_overviews/toolkit/web-server-intro.md b/_overviews/toolkit/web-server-intro.md new file mode 100644 index 0000000000..bf2db8a537 --- /dev/null +++ b/_overviews/toolkit/web-server-intro.md @@ -0,0 +1,24 @@ +--- +title: Building web servers with Cask +type: chapter +description: The introduction of the Cask library +num: 30 +languages: [ru] +previous-page: http-client-what-else +next-page: web-server-static +--- + +Cask is an HTTP micro-framework, providing a simple and flexible way to build web applications. + +Its main focus is on the ease of use, which makes it ideal for newcomers, at the cost of eschewing some features other +frameworks provide, like asynchronicity. + +To define an endpoint it's enough to annotate a function with an annotation specifying the request path. +Cask allows for building the response manually using tools that the library provides, specifying the content, headers, +status code, etc. An endpoint function can also return a string, a [uPickle](https://com-lihaoyi.github.io/upickle/) JSON type, or a [Scalatags](https://com-lihaoyi.github.io/scalatags/) +template. In that case, Cask will automatically create a response with the appropriate headers. + +Cask comes bundled with the uPickle library for handling JSONs, supports WebSockets and allows for extending endpoints with +decorators, which can be used to handle authentication or rate limiting. + +{% include markdown.html path="_markdown/install-cask.md" %} diff --git a/_overviews/toolkit/web-server-query-parameters.md b/_overviews/toolkit/web-server-query-parameters.md new file mode 100644 index 0000000000..8da1dc9ccc --- /dev/null +++ b/_overviews/toolkit/web-server-query-parameters.md @@ -0,0 +1,74 @@ +--- +title: How to handle query parameters? +type: section +description: Handling query parameters with Cask +num: 33 +previous-page: web-server-dynamic +next-page: web-server-input +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +Query parameters are the key-value pairs coming after the question mark in a URL. They can be used for filtering, +sorting or limiting the results provided by the server. For example, in the `<host>/time?city=Paris` URL, the `city` part +is the name of a parameter, and `Paris` is its value. Cask allows for reading the query parameters by defining an endpoint +method with arguments matching the names of the expected parameters and not matching any of the URL segments. + +In this example, we give an `Option` type and the default value `None` to the `city` parameter. This tells Cask that it is optional. +If not provided, the time for the current timezone will be returned. + +{% tabs web-server-query-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes { + + private def getZoneIdForCity(city: String): Option[ZoneId] = { + import scala.jdk.CollectionConverters._ + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + } + + @cask.get("/time") + def dynamicWithParam(city: Option[String] = None): String = { + city match { + case Some(value) => getZoneIdForCity(value) match { + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $value" + } + case None => s"Current date is: ${ZonedDateTime.now()}" + } + } + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +import java.time.{ZoneId, ZonedDateTime} + +object Example extends cask.MainRoutes: + + private def getZoneIdForCity(city: String): Option[ZoneId] = + import scala.jdk.CollectionConverters.* + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + + @cask.get("/time") + def dynamicWithParam(city: Option[String] = None): String = + city match + case Some(value) => getZoneIdForCity(value) match + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $value" + case None => s"Current date is: ${ZonedDateTime.now()}" + + initialize() +``` +{% endtab %} +{% endtabs %} + +Run the example as before and access the endpoint at [http://localhost:8080/time?city=Paris](http://localhost:8080/time?city=Paris). +You should get a result similar to the following one. +``` +Current date is: 2024-07-22T10:08:18.218736+02:00[Europe/Paris] +``` diff --git a/_overviews/toolkit/web-server-static.md b/_overviews/toolkit/web-server-static.md new file mode 100644 index 0000000000..780941efb0 --- /dev/null +++ b/_overviews/toolkit/web-server-static.md @@ -0,0 +1,159 @@ +--- +title: How to serve a static file? +type: section +description: Serving a static file with Cask +num: 31 +previous-page: web-server-intro +next-page: web-server-dynamic +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +## Serving a static file + +An endpoint is a specific URL where a particular webpage can be accessed. In Cask, an endpoint is a function returning the +webpage data, together with an annotation describing its URL. + +To create an endpoint serving static files, we need two things: an HTML file with the page content and a function that +points to that file. + +Create a minimal HTML file named `hello.html` with the following contents. + +```html +<!doctype html> +<html> + <head> + <title>Hello World + + +

Hello world

+ + +``` + +Place it in the `resources` directory. + +{% tabs web-server-static-1 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +``` +example +├── Example.scala +└── resources + └── hello.html +``` +{% endtab %} +{% tab 'sbt' %} +``` +example +└──src + └── main + ├── resources + │ └── hello.html + └── scala + └── Example.scala +``` +{% endtab %} +{% tab 'Mill' %} +``` +example +├── src +│ └── Example.scala +└── resources + └── hello.html +``` +{% endtab %} +{% endtabs %} + +The `@cask.staticFiles` annotation specifies at which path the webpage will be available. The endpoint function returns +the location of the file. + +{% tabs web-server-static-2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object Example extends cask.MainRoutes { + @cask.staticFiles("/static") + def staticEndpoint(): String = "src/main/resources" // or "resources" if not using SBT + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Example extends cask.MainRoutes: + @cask.staticFiles("/static") + def staticEndpoint(): String = "src/main/resources" // or "resources" if not using SBT + + initialize() +``` +{% endtab %} +{% endtabs %} + +In the example above, `@cask.staticFiles` instructs the server to look for files accessed at the `/static` path in the +`src/main/resources` directory. Cask will match any subpath coming after `/static` and append it to the directory path. +If you access the `/static/hello.html` file, it will serve the file available at `src/main/resources/hello.html`. +The directory path can be any path available to the server, relative or not. If the requested file cannot be found in the +specified location, the server will return a 404 response with an error message. + +The `Example` object inherits from the `cask.MainRoutes` class. It provides the main function that starts the server. The `initialize()` +method call initializes the server routes, i.e., the association between URL paths and the code that handles them. + +### Using the resources directory + +The `@cask.staticResources` annotation works in the same way as the `@cask.staticFiles` used above, with the difference that +the path returned by the endpoint method describes the location of files _inside_ the resources directory. Since the +previous example conveniently used the resources directory, it can be simplified with `@cask.staticResources`. + +{% tabs web-server-static-3 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object Example extends cask.MainRoutes { + @cask.staticResources("/static") + def staticEndpoint(): String = "." + + initialize() +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Example extends cask.MainRoutes: + @cask.staticResources("/static") + def staticEndpoint(): String = "." + + initialize() +``` +{% endtab %} +{% endtabs %} + +In the endpoint method, the location is set to `"."`, telling the server that the files are available directly in the +resources directory. In general, you can use any nested location within the resources directory. For instance, you could opt +for placing your HTML files in the `static` directory inside the resources directory or using different directories to sort out +files used by different endpoints. + +## Running the example + +Run the example with the build tool of your choice. + +{% tabs munit-unit-test-4 class=tabs-build-tool %} +{% tab 'Scala CLI' %} +In the terminal, the following command will start the server: +``` +scala run Example.scala +``` +{% endtab %} +{% tab 'sbt' %} +In the terminal, the following command will start the server: +``` +sbt example/run +``` +{% endtab %} +{% tab 'Mill' %} +In the terminal, the following command will start the server: +``` +./mill run +``` +{% endtab %} +{% endtabs %} + +The example page will be available at [http://localhost:8080/static/hello.html](http://localhost:8080/static/hello.html). diff --git a/_overviews/toolkit/web-server-websockets.md b/_overviews/toolkit/web-server-websockets.md new file mode 100644 index 0000000000..653bd7f154 --- /dev/null +++ b/_overviews/toolkit/web-server-websockets.md @@ -0,0 +1,118 @@ +--- +title: How to use websockets? +type: section +description: Using websockets with Cask +num: 35 +previous-page: web-server-input +next-page: web-server-cookies-and-decorators +--- + +{% include markdown.html path="_markdown/install-cask.md" %} + +You can create a WebSocket endpoint with the `@cask.websocket` annotation. The endpoint method should return a +`cask.WsHandler` instance defining how the communication should take place. It can also return a `cask.Response`, which rejects the +attempt at forming a WebSocket connection. + +The connection can also be closed by sending a `cask.Ws.close()` message through the WebSocket channel. + +Create an HTML file named `websockets.html` with the following content and place it in the `resources ` directory. + +```html + + + +
+ + +
+
+ + + +``` + +The JavaScript code opens a WebSocket connection using the `ws://localhost:8080/websocket` endpoint. The `ws.onmessage` +event handler is executed when the server pushes a message to the browser and `ws.onclose` when the connection is closed. + +Create an endpoint for serving static files using the `@cask.staticResources` annotation and an endpoint for handling +the WebSocket connection. + +{% tabs web-server-websocket-1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +@cask.staticResources("/static") +def static() = "." + +private def getZoneIdForCity(city: String): Option[ZoneId] = { + import scala.jdk.CollectionConverters._ + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) +} + +@cask.websocket("/websocket") +def websocket(): cask.WsHandler = { + cask.WsHandler { channel => + cask.WsActor { + case cask.Ws.Text("") => channel.send(cask.Ws.Close()) + case cask.Ws.Text(city) => + val text = getZoneIdForCity(city) match { + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + } + channel.send(cask.Ws.Text(text)) + } + } +} + +initialize() +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +@cask.staticResources("/static") +def static() = "." + +private def getZoneIdForCity(city: String): Option[ZoneId] = + import scala.jdk.CollectionConverters.* + ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of) + +@cask.websocket("/websocket") +def websocket(): cask.WsHandler = + cask.WsHandler { channel => + cask.WsActor { + case cask.Ws.Text("") => channel.send(cask.Ws.Close()) + case cask.Ws.Text(city) => + val text = getZoneIdForCity(city) match + case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}" + case None => s"Couldn't find time zone for city $city" + channel.send(cask.Ws.Text(text)) + } + } + +initialize() +``` +{% endtab %} +{% endtabs %} + +In the `cask.WsHandler` we define a `cask.WsActor`. It reacts to events (of type `cask.util.Ws.Event`) and uses the +WebSocket channel to send messages. In this example, we receive the name of a city and return the current time there. If the server +receives an empty message, the connection is closed. \ No newline at end of file diff --git a/_overviews/tutorials/binary-compatibility-for-library-authors.md b/_overviews/tutorials/binary-compatibility-for-library-authors.md index 92b993ca24..c3fd5467a9 100644 --- a/_overviews/tutorials/binary-compatibility-for-library-authors.md +++ b/_overviews/tutorials/binary-compatibility-for-library-authors.md @@ -52,7 +52,7 @@ Similarly to the JVM, Scala.js and Scala Native have their respective equivalent However, contrary to the JVM, Scala.js and Scala Native link their respective IR files at link time, so eagerly, instead of lazily at run-time. Failure to correctly link the entire program results in linking errors reported while trying to invoke `fastOptJS`/`fullOptJS` or `nativeLink`. -Besides that difference in the timing of linkage errors, the models are extremely similar. **Unless otherwise noted, the contents of this guide apply equally to the JVM, Scala.js and Scala Native.** +Besides, that difference in the timing of linkage errors, the models are extremely similar. **Unless otherwise noted, the contents of this guide apply equally to the JVM, Scala.js and Scala Native.** Before we look at how to avoid binary incompatibility errors, let us first establish some key terminologies we will be using for the rest of the guide. @@ -67,7 +67,7 @@ Because of this, having multiple versions of the same library in the classpath i * Unexpected runtime behavior if the order of class files changes Therefore, build tools like sbt and Gradle will pick one version and **evict** the rest when resolving JARs to use for compilation and packaging. -By default they pick the latest version of each library, but it is possible to specify another version if required. +By default, they pick the latest version of each library, but it is possible to specify another version if required. ### Source Compatibility Two library versions are **Source Compatible** with each other if switching one for the other does not incur any compile errors or unintended behavioral changes (semantic errors). @@ -115,7 +115,7 @@ Our application `App` depends on library `A` and `B`. Both `A` and `B` depends o ![Initial dependency graph]({{ site.baseurl }}/resources/images/library-author-guide/before_update.png){: style="width: 50%; margin: auto; display: block;"} -Sometime later, we see `B v1.1.0` is available and upgrade its version in our build. Our code compiles and seems to work so we push it to production and go home for dinner. +Sometime later, we see `B v1.1.0` is available and upgrade its version in our build. Our code compiles and seems to work, so we push it to production and go home for dinner. Unfortunately at 2am, we get frantic calls from customers saying that our application is broken! Looking at the logs, you find lots of `NoSuchMethodError` are being thrown by some code in `A`! @@ -147,7 +147,7 @@ How can we, as library authors, spare our users of runtime errors and dependency It works by comparing the class files of two provided JARs and report any binary incompatibilities found. Both backwards and forwards binary incompatibility can be detected by swapping input order of the JARs. -By incorporating MiMa's [sbt plugin](https://github.com/lightbend/mima/wiki/sbt-plugin) into your sbt build, you can easily check whether +By incorporating MiMa's [sbt plugin](https://github.com/lightbend/mima#sbt) into your sbt build, you can easily check whether you have accidentally introduced binary incompatible changes. Detailed instruction on how to use the sbt plugin can be found in the link. We strongly encourage every library author to incorporate MiMa into their continuous integration and release workflow. @@ -171,7 +171,184 @@ in library releases: You can find detailed explanations, runnable examples and tips to maintain binary compatibility in [Binary Compatibility Code Examples & Explanation](https://github.com/jatcwang/binary-compatibility-guide). -Again, we recommend using MiMa to double check that you have not broken binary compatibility after making changes. +Again, we recommend using MiMa to double-check that you have not broken binary compatibility after making changes. + +### Changing a case class definition in a backwards-compatible manner + +Sometimes, it is desirable to change the definition of a case class (adding and/or removing fields) while still staying backwards-compatible with the existing usage of the case class, i.e. not breaking the so-called _binary compatibility_. The first question you should ask yourself is “do you need a _case_ class?” (as opposed to a regular class, which can be easier to evolve in a binary compatible way). A good reason for using a case class is when you need a structural implementation of `equals` and `hashCode`. + +To achieve that, follow this pattern: + * make the primary constructor private (this makes the `copy` method of the class private as well) + * define a private `unapply` function in the companion object (note that by doing that the case class loses the ability to be used as an extractor in match expressions) + * for all the fields, define `withXXX` methods on the case class that create a new instance with the respective field changed (you can use the private `copy` method to implement them) + * create a public constructor by defining an `apply` method in the companion object (it can use the private constructor) + * in Scala 2, you have to add the compiler option `-Xsource:3` + +Example: + +{% tabs case_class_compat_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala +// Mark the primary constructor as private +case class Person private (name: String, age: Int) { + // Create withXxx methods for every field, implemented by using the (private) copy method + def withName(name: String): Person = copy(name = name) + def withAge(age: Int): Person = copy(age = age) +} + +object Person { + // Create a public constructor (which uses the private primary constructor) + def apply(name: String, age: Int) = new Person(name, age) + // Make the extractor private + private def unapply(p: Person): Some[Person] = Some(p) +} +~~~ +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// Mark the primary constructor as private +case class Person private (name: String, age: Int): + // Create withXxx methods for every field, implemented by using the (private) copy method + def withName(name: String): Person = copy(name = name) + def withAge(age: Int): Person = copy(age = age) + +object Person: + // Create a public constructor (which uses the private primary constructor) + def apply(name: String, age: Int): Person = new Person(name, age) + // Make the extractor private + private def unapply(p: Person) = p +``` +{% endtab %} +{% endtabs %} +This class can be published in a library and used as follows: + +{% tabs case_class_compat_2 %} +{% tab 'Scala 2 and 3' %} +~~~ scala +// Create a new instance +val alice = Person("Alice", 42) +// Transform an instance +println(alice.withAge(alice.age + 1)) // Person(Alice, 43) +~~~ +{% endtab %} +{% endtabs %} + +If you try to use `Person` as an extractor in a match expression, it will fail with a message like “method unapply cannot be accessed as a member of Person.type”. Instead, you can use it as a typed pattern: + +{% tabs case_class_compat_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala +alice match { + case person: Person => person.name +} +~~~ +{% endtab %} +{% tab 'Scala 3' %} +~~~ scala +alice match + case person: Person => person.name +~~~ +{% endtab %} +{% endtabs %} + +Later in time, you can amend the original case class definition to, say, add an optional `address` field. You + * add a new field `address` and a custom `withAddress` method, + * update the public `apply` method in the companion object to initialize all the fields, + * tell MiMa to [ignore](https://github.com/lightbend/mima#filtering-binary-incompatibilities) changes to the class constructor. This step is necessary because MiMa does not yet ignore changes in private class constructor signatures (see [#738](https://github.com/lightbend/mima/issues/738)). + +{% tabs case_class_compat_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala +case class Person private (name: String, age: Int, address: Option[String]) { + ... + def withAddress(address: Option[String]) = copy(address = address) +} + +object Person { + // Update the public constructor to also initialize the address field + def apply(name: String, age: Int): Person = new Person(name, age, None) +} +~~~ +{% endtab %} +{% tab 'Scala 3' %} +```scala +case class Person private (name: String, age: Int, address: Option[String]): + ... + def withAddress(address: Option[String]) = copy(address = address) + +object Person: + // Update the public constructor to also initialize the address field + def apply(name: String, age: Int): Person = new Person(name, age, None) +``` +{% endtab %} +{% endtabs %} + +And, in your build definition: + +{% tabs case_class_compat_5 %} +{% tab 'sbt' %} +~~~ scala +import com.typesafe.tools.mima.core._ +mimaBinaryIssueFilters += ProblemFilters.exclude[DirectMissingMethodProblem]("Person.this") +~~~ +{% endtab %} +{% endtabs %} + +Otherwise, MiMa would fail with an error like “method this(java.lang.String,Int)Unit in class Person does not have a correspondent in current version”. + +> Note that an alternative solution, instead of adding a MiMa exclusion filter, consists of adding back the previous +> constructor signatures as secondary constructors: +> ~~~ scala +> case class Person private (name: String, age: Int, address: Option[String]): +> ... +> // Add back the former primary constructor signature +> private[Person] def this(name: String, age: Int) = this(name, age, None) +> ~~~ + +The original users can use the case class `Person` as before, all the methods that existed before are present unmodified after this change, thus the compatibility with the existing usage is maintained. + +The new field `address` can be used as follows: + +{% tabs case_class_compat_6 %} +{% tab 'Scala 2 and 3' %} +~~~ scala +// The public constructor sets the address to None by default. +// To set the address, we call withAddress: +val bob = Person("Bob", 21).withAddress(Some("Atlantic ocean")) +println(bob.address) +~~~ +{% endtab %} +{% endtabs %} + +A regular case class not following this pattern would break its usage, because by adding a new field changes some methods (which could be used by somebody else), for example `copy` or the constructor itself. + +Optionally, you can also add overloads of the `apply` method in the companion object to initialize more fields +in one call. In our example, we can add an overload that also initializes the `address` field: + +{% tabs case_class_compat_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} +~~~ scala +object Person { + // Original public constructor + def apply(name: String, age: Int): Person = new Person(name, age, None) + // Additional constructor that also sets the address + def apply(name: String, age: Int, address: String): Person = + new Person(name, age, Some(address)) +} +~~~ +{% endtab %} +{% tab 'Scala 3' %} +~~~ scala +object Person: + // Original public constructor + def apply(name: String, age: Int): Person = new Person(name, age, None) + // Additional constructor that also sets the address + def apply(name: String, age: Int, address: String): Person = + new Person(name, age, Some(address)) +~~~ +{% endtab %} +{% endtabs %} ## Versioning Scheme - Communicating compatibility breakages diff --git a/_overviews/tutorials/partest-guide.md b/_overviews/tutorials/partest-guide.md index 36fa8c74c7..a5542ad9a4 100644 --- a/_overviews/tutorials/partest-guide.md +++ b/_overviews/tutorials/partest-guide.md @@ -1,5 +1,4 @@ --- -layout: inner-page-no-masthead sitemap: false permalink: /tutorials/partest-guide.html redirect_to: https://github.com/scala/scala-partest diff --git a/_overviews/tutorials/scala-for-csharp-programmers.disabled.html b/_overviews/tutorials/scala-for-csharp-programmers.disabled.html index 7641a4ec65..6dfc4969ec 100644 --- a/_overviews/tutorials/scala-for-csharp-programmers.disabled.html +++ b/_overviews/tutorials/scala-for-csharp-programmers.disabled.html @@ -1,5 +1,5 @@ --- -layout: overview +layout: singlepage-overview title: A Scala Tutorial for C# Programmers --- @@ -7,10 +7,10 @@ ## Introduction -Scala is a hybrid of functional and object-oriented languages. -Its functional aspects make it very expressive when writing algorithmic -code, and play nicely with the brave new world of concurrency; its -object-oriented aspects keep it familiar and convenient when creating +Scala is a hybrid of functional and object-oriented languages. +Its functional aspects make it very expressive when writing algorithmic +code, and play nicely with the brave new world of concurrency; its +object-oriented aspects keep it familiar and convenient when creating business objects or other stateful models. ## The same concepts @@ -109,9 +109,9 @@ You can see the same principle in action for the return type of the function. `reticulate` returns a `String`. -Finally, the body of the method has been placed after an equals sign, -rather than inside braces. (Braces are only necessary when the method -body consists of multiple expressions/statements.) What's more, +Finally, the body of the method has been placed after an equals sign, +rather than inside braces. (Braces are only necessary when the method +body consists of multiple expressions/statements.) What's more, the body of the method is an expression -- that is, something with a value -- rather than a set of statements amongst which is a `return`. I'll come back to this when we look at a more realistic @@ -139,7 +139,7 @@ ### Base Types Scala's base types are pretty much the same as C#'s, except that they -are named with initial capitals, e.g. `Int` instead of `int`. (In fact +are named with initial capitals, e.g. `Int` instead of `int`. (In fact every type in Scala starts with an uppercase letter.) There are no unsigned variants, and booleans are called `Boolean` instead of `bool`. @@ -154,8 +154,8 @@ Scala has the same concept, but the function types are built into the language, rather than being library types. Function types are -spelled `(T1, T2, ...) => TR`. For example, a predicate of integers -would be type `(Int) => Boolean`. If there is only one input type, +spelled `(T1, T2, ...) => TR`. For example, a predicate of integers +would be type `(Int) => Boolean`. If there is only one input type, the parens can be left out like this: `Int => Boolean`. Effectively, Scala gets rid of all those weird custom delegate types @@ -220,7 +220,7 @@ ### Tuples -Everybody hates out parameters. We all know that code like this just +Everybody hates out parameters. We all know that code like this just isn't nice: Widget widget; @@ -229,21 +229,21 @@ widget.ReticulateSplines(); } -And once you start composing higher-level functions into the mix, it gets -positively nasty. Suppose I want to make a HTTP request. Well, that's -going to produce two outputs in itself, the success code and the response -data. But now suppose I want to time it. Writing the timing code isn't a -problem, but now I have *three* outputs, and to paraphrase *Was Not Was*, +And once you start composing higher-level functions into the mix, it gets +positively nasty. Suppose I want to make a HTTP request. Well, that's +going to produce two outputs in itself, the success code and the response +data. But now suppose I want to time it. Writing the timing code isn't a +problem, but now I have *three* outputs, and to paraphrase *Was Not Was*, I feel worse than Jamie Zawinski. -You can get around this in specific situations by creating custom types -like `DictionaryLookupResult` or `TimedHttpRequestResult`, but eventually +You can get around this in specific situations by creating custom types +like `DictionaryLookupResult` or `TimedHttpRequestResult`, but eventually the terrible allure wears off, and you just want it to work. -Enter tuples. A tuple is just a small number of values -- a single value, -a pair of values, a triplet of values, that sort of thing. You can tell -that tuples were named by mathematicians because the name only makes sense -when you look at the cases you hardly ever use (quadruple, quintuple, +Enter tuples. A tuple is just a small number of values -- a single value, +a pair of values, a triplet of values, that sort of thing. You can tell +that tuples were named by mathematicians because the name only makes sense +when you look at the cases you hardly ever use (quadruple, quintuple, sextuple, etc.). Using tuples, our timed HTTP request might look like this: public Tuple Time(Func> action) @@ -253,27 +253,27 @@ TimeSpan howLong = StopTimer(); return Tuple.Create(result.First, result.Second, howLong); } - + var result = Time(() => MakeRequest(uri)); var succeeded = result.First; var response = result.Second; var howLong = result.Third. Console.WriteLine("it took " + howLong); -The reason this keeps getting verbose on us is that C# doesn’t provide any -syntatical support for tuples. To C#, a `Tuple<>` is just another generic -type. To us, the readers, a `Tuple<>` is just another generic type with +The reason this keeps getting verbose on us is that C# doesn’t provide any +syntatical support for tuples. To C#, a `Tuple<>` is just another generic +type. To us, the readers, a `Tuple<>` is just another generic type with particularly unhelpful member names. -Really, what we're really trying to articulate by returning a `Tuple<>` is, -"this method has several outputs." So what do we want to do with those -outputs? We want to access them, for example to stash them in variables, -without having to construct and pick apart the tuple one value at a time. -That means the language has to provide some kind of syntax-level support -for tuples, instead of treating them like every other class the compiler +Really, what we're really trying to articulate by returning a `Tuple<>` is, +"this method has several outputs." So what do we want to do with those +outputs? We want to access them, for example to stash them in variables, +without having to construct and pick apart the tuple one value at a time. +That means the language has to provide some kind of syntax-level support +for tuples, instead of treating them like every other class the compiler doesn’t know about. -Many functional languages have exactly this kind of syntactical support, +Many functional languages have exactly this kind of syntactical support, and Scala is no exception. Here’s how the above pseudo-C# looks in Scala: def time(action: => (Boolean, Stream)): (Boolean, Stream, TimeSpan) = { @@ -281,23 +281,23 @@ val (succeeded, response) = action (succeeded, response, stopTimer()) } - + val (succeeded, response, timeTaken) = time(makeRequest) Console.WriteLine("it took " + timeTaken) -Notice the multiple variables on the left hand side of the time call? -Notice the `(Boolean, Stream, TimeSpan)` return type of the time method? -That return value is effectively a tuple type, but instead of having to -capture the returned tuple in a `Tuple<>` variable and extract its various -bits by hand, we are getting the Scala compiler to (in the time function) -compose the tuple implicitly for us, without us having to write the -constructor call, and (in the calling code) unpick the tuple into -meaningful, named variables for us without us having to explicitly copy +Notice the multiple variables on the left hand side of the time call? +Notice the `(Boolean, Stream, TimeSpan)` return type of the time method? +That return value is effectively a tuple type, but instead of having to +capture the returned tuple in a `Tuple<>` variable and extract its various +bits by hand, we are getting the Scala compiler to (in the time function) +compose the tuple implicitly for us, without us having to write the +constructor call, and (in the calling code) unpick the tuple into +meaningful, named variables for us without us having to explicitly copy the values one by one and by name. -(By the way, a proper implementation of the `time()` method wouldn’t be -restricted to `(Boolean, Stream)` results: we’d be looking to write a -method that could time anything. I’ve skipped that because it would +(By the way, a proper implementation of the `time()` method wouldn’t be +restricted to `(Boolean, Stream)` results: we’d be looking to write a +method that could time anything. I’ve skipped that because it would distract from the point at hand.) How would this play with the dictionary example? @@ -306,60 +306,60 @@ if (found) widget.reticulateSplines() -We don’t actually save any lines of code, what with having to now capture -the “found” value into a variable and test it separately; and it’s not as -if the original C# version was horribly unreadable anyway. So maybe this is -a matter of taste, but I find this a lot easier to read and to write: all -the outputs are on the left of the equals sign where they belong, instead -of being spread between the assignment result and the parameter list, and +We don’t actually save any lines of code, what with having to now capture +the “found” value into a variable and test it separately; and it’s not as +if the original C# version was horribly unreadable anyway. So maybe this is +a matter of taste, but I find this a lot easier to read and to write: all +the outputs are on the left of the equals sign where they belong, instead +of being spread between the assignment result and the parameter list, and we don’t have that odd Widget declaration at the top. ## New and different concepts -Scala's primary platform is the Java virtual machine, and some of the -interest in Scala comes from Java programmers' interest in features such -as type inference, comprehensions and lambdas, with which C# programmers -are already familiar. So what's left that might be of interest +Scala's primary platform is the Java virtual machine, and some of the +interest in Scala comes from Java programmers' interest in features such +as type inference, comprehensions and lambdas, with which C# programmers +are already familiar. So what's left that might be of interest specifically to C# programmers? ### Mixins and Traits #### Motivation -Interfaces in C# and Java play a dual role. -First, they are a set of capabilities that an implementer has to, well, -implement. Second, they are a feature set that a client can use. +Interfaces in C# and Java play a dual role. +First, they are a set of capabilities that an implementer has to, well, +implement. Second, they are a feature set that a client can use. -These two roles can be conflicting: The first means that interfaces want -to be minimal, so that implementers don't have to implement a whole lot -of superfluous and redundant guff. The second means that interfaces want -to be maximal, so that clients don't have to clog themselves up with +These two roles can be conflicting: The first means that interfaces want +to be minimal, so that implementers don't have to implement a whole lot +of superfluous and redundant guff. The second means that interfaces want +to be maximal, so that clients don't have to clog themselves up with boilerplate utility methods. Consider, for example, `IEnumerable` (and its sister interface `IEnumerator`). -This is a very minimal interface: implementers just need to be able to -produce values in sequence. But this minimalism means that clients of -`IEnumerable` need to write the same old boilerplate again and again and -again: foreach loops to filter, foreach loops to call a method on each -element of the sequence, foreach loops to aggregate, foreach loops to -check whether all elements meet a criterion, or to find the first member -that meets a criterion, or... - -This is frustrating because the implementations of "filter," "apply", -"aggregate," and so on are always the same. Of course, we could put -these methods into concrete types (`List` includes several), but then -those concrete types will contain duplicate code, and users who only have -an `IEnumerable` will still miss out. And yet we can't put these methods -into the interface because then every implementer of `IEnumerable` would -have to implement them -- and they'd end up writing the same boilerplate, +This is a very minimal interface: implementers just need to be able to +produce values in sequence. But this minimalism means that clients of +`IEnumerable` need to write the same old boilerplate again and again and +again: foreach loops to filter, foreach loops to call a method on each +element of the sequence, foreach loops to aggregate, foreach loops to +check whether all elements meet a criterion, or to find the first member +that meets a criterion, or... + +This is frustrating because the implementations of "filter," "apply", +"aggregate," and so on are always the same. Of course, we could put +these methods into concrete types (`List` includes several), but then +those concrete types will contain duplicate code, and users who only have +an `IEnumerable` will still miss out. And yet we can't put these methods +into the interface because then every implementer of `IEnumerable` would +have to implement them -- and they'd end up writing the same boilerplate, just now in all the zillions of `IEnumerable` classes instead of their clients. #### The C# and Scala Solutions -We could resolve this tension if we had a way for interfaces to contain -implementation: for example, if `IEnumerable` required the implementer -to provide the class-specific iteration functionality, but then provided -the standard implementations of "filter," "apply", "aggregate" and so on +We could resolve this tension if we had a way for interfaces to contain +implementation: for example, if `IEnumerable` required the implementer +to provide the class-specific iteration functionality, but then provided +the standard implementations of "filter," "apply", "aggregate" and so on automatically: public pseudo_interface IEnumerable @@ -373,24 +373,24 @@ } } -C# 3 addresses this using extension methods: the methods mentioned above -are all in fact included as extension methods on `IEnumerable` as -part of LINQ. +C# 3 addresses this using extension methods: the methods mentioned above +are all in fact included as extension methods on `IEnumerable` as +part of LINQ. -This has some advantages over the approach described above: specifically, -the "standard methods" aren't bound up in the interface, so you can add -your own methods instead of being limited to the ones that the interface +This has some advantages over the approach described above: specifically, +the "standard methods" aren't bound up in the interface, so you can add +your own methods instead of being limited to the ones that the interface author has included. -On the other hand, it means that method implementations have to be packaged +On the other hand, it means that method implementations have to be packaged in a different class from the interface, which feels less than modular. -Scala takes a different approach. A Scala trait can contain a mix of -abstract methods without implementation as well as concrete methods with -an implementation. (It can also be a pure interface, with only abstract +Scala takes a different approach. A Scala trait can contain a mix of +abstract methods without implementation as well as concrete methods with +an implementation. (It can also be a pure interface, with only abstract members.) -Here's a Scala trait that represents objects that can be compared +Here's a Scala trait that represents objects that can be compared and ordered: trait Ord { @@ -400,14 +400,14 @@ def >=(that: Any): Boolean = !(this < that) } -Orderable objects can extend `Ord`, but only need to implement the -method `<`. They then get the other operators for free, implemented +Orderable objects can extend `Ord`, but only need to implement the +method `<`. They then get the other operators for free, implemented automatically by Ord in terms of `<`. class Date extends Ord { def < (that: Any): Boolean = /* implementation */ } - + // can now write: myDate >= yourDate A similar trait, called `Ordered` already exists in Scala, so there is no @@ -415,13 +415,13 @@ #### Scala Traits vs. C# Extension Methods -Okay, so Scala has a different way of packaging standard implementations -from C#'s extension methods. It's different, but why is it interesting? -Well, there are a couple of things that you can do with Scala traits that +Okay, so Scala has a different way of packaging standard implementations +from C#'s extension methods. It's different, but why is it interesting? +Well, there are a couple of things that you can do with Scala traits that don't fall nicely out of the extension methods approach. -First, you can override the default implementations of trait members, -to take advantage of additional information or capabilities available +First, you can override the default implementations of trait members, +to take advantage of additional information or capabilities available in the implementing type. Let's look at another `IEnumerable` example, recast as a Scala trait: @@ -437,12 +437,12 @@ } } -This (ignoring style issues for now) is the only fully general -implementation we can provide for count: it will work with any `Enumerable`. -But for collections that know their sizes, such as `arrays` or `List`, -it's gruesomely inefficient. It iterates over the entire collection, -counting elements one by one, when it could just consult the `size` member -and return that. +This (ignoring style issues for now) is the only fully general +implementation we can provide for count: it will work with any `Enumerable`. +But for collections that know their sizes, such as `arrays` or `List`, +it's gruesomely inefficient. It iterates over the entire collection, +counting elements one by one, when it could just consult the `size` member +and return that. Let's fix that: @@ -452,68 +452,68 @@ override def count: Int = _size } -The `count` member of the `Enumerable` trait works like a virtual method: +The `count` member of the `Enumerable` trait works like a virtual method: it can be overridden in classes which implement/derive from `Enumerable`. -Compare this to the `Count()` extension method on `IEnumerable` in LINQ. -This achieves the same effect by trying to cast to `ICollection`, which is -fine as far as it goes but isn't extensible. +Compare this to the `Count()` extension method on `IEnumerable` in LINQ. +This achieves the same effect by trying to cast to `ICollection`, which is +fine as far as it goes but isn't extensible. -Suppose you create an enumerable class that can count itself quickly but -isn't a collection -- for example a natural numbers range object. -With a Scala trait, the `NumberRange` type could provide an efficient -override of `count`, just like any other virtual method; with C# extension -methods, `Enumerable.Count()` would have to somehow know about the +Suppose you create an enumerable class that can count itself quickly but +isn't a collection -- for example a natural numbers range object. +With a Scala trait, the `NumberRange` type could provide an efficient +override of `count`, just like any other virtual method; with C# extension +methods, `Enumerable.Count()` would have to somehow know about the `NumberRange` type in advance, or fall back on counting elements one by one. -Second, with Scala you can choose a trait implementation when you -instantiate an object, rather than having it baked in at the class level +Second, with Scala you can choose a trait implementation when you +instantiate an object, rather than having it baked in at the class level once and for all. This is called mixin-composition. -Suppose you're creating a `MyList` instance, but you want it to puff itself -up to look bigger so as to frighten other `MyList` instances off its territory. -(This example would probably work better with fish, but we're stuck with -`Enumerable`s now. Work with me here.) In C#, you'd need to create a -`PuffedUpMyList` class and override the `Count` property. +Suppose you're creating a `MyList` instance, but you want it to puff itself +up to look bigger so as to frighten other `MyList` instances off its territory. +(This example would probably work better with fish, but we're stuck with +`Enumerable`s now. Work with me here.) In C#, you'd need to create a +`PuffedUpMyList` class and override the `Count` property. In Scala, you can just mix in a `PuffedUp` version of the trait: trait PuffedUp extends Enumerable { override def count: Int = super.count + 100 } - + val normal = new MyList Console.WriteLine(normal.count) // meh val puffedUp = new MyList with PuffedUp Console.WriteLine(puffedUp.count) // eek! -As you can imagine this gives us much better granularity and composability -of traits and implementations than we get from the extension methods -approach, or indeed from single implementation inheritance type systems +As you can imagine this gives us much better granularity and composability +of traits and implementations than we get from the extension methods +approach, or indeed from single implementation inheritance type systems in general. -So Scala traits have some distinct advantages over extension methods. -The only downside appears to be the inability for clients to add their -own methods to a trait after the fact. +So Scala traits have some distinct advantages over extension methods. +The only downside appears to be the inability for clients to add their +own methods to a trait after the fact. -Fortunately, you can work around this in Scala using so-called implicit +Fortunately, you can work around this in Scala using so-called implicit conversions. They enable Scala programmers to enrich existing types with new functionality. ### Singletons -In C#, if you want to create a singleton object, you have to create a class, -then stop evildoers creating their own instances of that class, then create +In C#, if you want to create a singleton object, you have to create a class, +then stop evildoers creating their own instances of that class, then create and provide an instance of that class yourself. -While this is hardly a Burma Railway of the programming craft, it does -feel like pushing against the grain of the language. Nor is it great for -maintainers, who have to be able to recognise a singleton by its pattern -(private constructor, public static readonly field, ...), or for clients, -who have to use a slightly clumsy multipart syntax to refer to the +While this is hardly a Burma Railway of the programming craft, it does +feel like pushing against the grain of the language. Nor is it great for +maintainers, who have to be able to recognise a singleton by its pattern +(private constructor, public static readonly field, ...), or for clients, +who have to use a slightly clumsy multipart syntax to refer to the singleton (e.g. `Universe.Instance`). -What would be easier for all concerned would be if you could just declare -objects *as* singletons. That is, instead of writing class `Universe` and a +What would be easier for all concerned would be if you could just declare +objects *as* singletons. That is, instead of writing class `Universe` and a `public static readonly` instance of it, you could just write `object Universe`. And that's exactly what Scala allows you to do: @@ -521,99 +521,99 @@ object Universe { def contains(obj: Any): Boolean = true } - + val v = Universe.contains(42) -What's going on behind the scenes here? It pretty much goes without saying -that the Scala compiler is creating a new type for the singleton object. -In fact it creates two types, one for the implementation and one for the -interface. The interface looks like a .NET static class (actually, the -.NET 1.x equivalent, a sealed class with only static members). +What's going on behind the scenes here? It pretty much goes without saying +that the Scala compiler is creating a new type for the singleton object. +In fact it creates two types, one for the implementation and one for the +interface. The interface looks like a .NET static class (actually, the +.NET 1.x equivalent, a sealed class with only static members). Thus, a C# program would call the example above as `Universe.contains(42)`. -Singleton objects are first-class citizens in Scala, so they can for -example derive from classes. This is a nice way of creating special values -with custom behaviour: you don't need to create a whole new type, you just +Singleton objects are first-class citizens in Scala, so they can for +example derive from classes. This is a nice way of creating special values +with custom behaviour: you don't need to create a whole new type, you just define an instance and override methods in it: abstract class Cat { def humiliateSelf() } - + object Slats extends Cat { def humiliateSelf() { savage(this.tail) } } -Obviously this is a frivolous example, but "special singletons" turn out to -be an important part of the functional idiom, for example for bottoming out -recursion. *Scala by Example (PDF)* describes an implementation of a Set class -which is implemented as a tree-like structure ("left subset - member - right -subset"), and methods such as `contains()` work by recursing down to the -child sets. For this to work requires an `EmptySet` whose implementation -(state) and behaviour are quite different from non-empty sets -- e.g. -`contains()` just returns `false` instead of trying to delegate to -non-existent child sets. Since `EmptySet` is logically unique it is both -simpler and more efficient to represent it as a singleton: i.e. to declare +Obviously this is a frivolous example, but "special singletons" turn out to +be an important part of the functional idiom, for example for bottoming out +recursion. *Scala by Example (PDF)* describes an implementation of a Set class +which is implemented as a tree-like structure ("left subset - member - right +subset"), and methods such as `contains()` work by recursing down to the +child sets. For this to work requires an `EmptySet` whose implementation +(state) and behaviour are quite different from non-empty sets -- e.g. +`contains()` just returns `false` instead of trying to delegate to +non-existent child sets. Since `EmptySet` is logically unique it is both +simpler and more efficient to represent it as a singleton: i.e. to declare `object EmptySet` instead of `class EmptySet`. -In fact the whole thing can become alarmingly deep: *Scala by Example* -also includes a description of `Boolean` as an `abstract class`, and -`True` and `False` as singleton objects which extend `Boolean` and provide -appropriate implementations of the `ifThenElse` method. +In fact the whole thing can become alarmingly deep: *Scala by Example* +also includes a description of `Boolean` as an `abstract class`, and +`True` and `False` as singleton objects which extend `Boolean` and provide +appropriate implementations of the `ifThenElse` method. -And fans of Giuseppe Peano should definitely check out the hypothetical +And fans of Giuseppe Peano should definitely check out the hypothetical implementation of `Int`... ### Pass by Name -> You're only on chapter 3 and you're already reduced to writing about -> *calling conventions*? You suck! Do another post about chimney sweeps +> You're only on chapter 3 and you're already reduced to writing about +> *calling conventions*? You suck! Do another post about chimney sweeps > being hunted by jars of marmalade!" -Silence, cur. Pass by name is not as other calling conventions are. -Pass by name, especially in conjunction with some other rather -theoretical-sounding Scala features, is your gateway to the wonderful +Silence, cur. Pass by name is not as other calling conventions are. +Pass by name, especially in conjunction with some other rather +theoretical-sounding Scala features, is your gateway to the wonderful world of language extensibility. #### What is Passing By Name? -First, let's talk about what we mean by *calling convention*. A calling -convention describes how stuff gets passed to a method by its caller. -In the good old days, this used to mean exciting things like which -arguments got passed in registers and who was responsible for resetting -the stack pointer. Sadly, the days of being able to refer to "naked fun -calls" are consigned to history: In modern managed environments, the -runtime takes care of all this guff and the main distinction is pass -data by value or by reference. (The situation on the CLR is slightly -complicated by the need to differentiate passing values by value, values -by reference, references by value and references by reference, but I'm -not going to go into that because (a) it's irrelevant to the subject at -hand and (b) that's -[Jon Skeet](http://www.yoda.arachsys.com/csharp/parameters.html)'s turf +First, let's talk about what we mean by *calling convention*. A calling +convention describes how stuff gets passed to a method by its caller. +In the good old days, this used to mean exciting things like which +arguments got passed in registers and who was responsible for resetting +the stack pointer. Sadly, the days of being able to refer to "naked fun +calls" are consigned to history: In modern managed environments, the +runtime takes care of all this guff and the main distinction is pass +data by value or by reference. (The situation on the CLR is slightly +complicated by the need to differentiate passing values by value, values +by reference, references by value and references by reference, but I'm +not going to go into that because (a) it's irrelevant to the subject at +hand and (b) that's +[Jon Skeet](http://www.yoda.arachsys.com/csharp/parameters.html)'s turf and I don't want him to shank me. Again.) -In *pass by value*, the called method gets a copy of whatever the caller -passed in. Arguments passed by value therefore work like local variables -that are initialised before the method runs: when you do anything to them, +In *pass by value*, the called method gets a copy of whatever the caller +passed in. Arguments passed by value therefore work like local variables +that are initialised before the method runs: when you do anything to them, you're doing it to your own copy. -In *pass by reference*, the called method gets a reference to the caller's -value. When you do anything to a pass-by-reference argument, you're doing -it to the caller's data. +In *pass by reference*, the called method gets a reference to the caller's +value. When you do anything to a pass-by-reference argument, you're doing +it to the caller's data. -In *pass by name*, the called method gets... well, it's a bit messy to -explain what the called method gets. But when the called method does -anything to the argument, the argument gets evaluated and the "anything" -is done to that. Crucially, evaluation happens every time the argument +In *pass by name*, the called method gets... well, it's a bit messy to +explain what the called method gets. But when the called method does +anything to the argument, the argument gets evaluated and the "anything" +is done to that. Crucially, evaluation happens every time the argument gets mentioned, and only when the argument gets mentioned. #### Not Just Another Calling Convention -Why does this matter? It matters because there are functions you can't -implement using pass by value or pass by reference, but you can implement +Why does this matter? It matters because there are functions you can't +implement using pass by value or pass by reference, but you can implement using pass by name. -Suppose, for example, that C# didn't have the `while` keyword. +Suppose, for example, that C# didn't have the `while` keyword. You'd probably want to write a method that did the job instead: public static void While(bool condition, Action body) @@ -630,30 +630,30 @@ long x = 0; While(x < 10, () => x = x + 1); -C# evaluates the arguments to `While` and invokes the `While` method with -the arguments `true` and `() => x = x + 1`. After watching the CPU sit -on 100% for a while you might check on the value of `x` and find it's -somewhere north of a trillion. *Why?* Because the condition argument was -*passed by value*, so whenever the `While` method tests the value of -condition, it's always `true`. The `While` method doesn't know that -condition originally came from the expression `x < 10`; all `While` knows +C# evaluates the arguments to `While` and invokes the `While` method with +the arguments `true` and `() => x = x + 1`. After watching the CPU sit +on 100% for a while you might check on the value of `x` and find it's +somewhere north of a trillion. *Why?* Because the condition argument was +*passed by value*, so whenever the `While` method tests the value of +condition, it's always `true`. The `While` method doesn't know that +condition originally came from the expression `x < 10`; all `While` knows is that condition is `true`. -For the `While` method to work, we need it to re-evaluate `x < 10` every -time it hits the condition argument. While needs not the value of the -argument at the call site, nor a reference to the argument at the call -site, but the actual expression that the caller wants it to use to generate +For the `While` method to work, we need it to re-evaluate `x < 10` every +time it hits the condition argument. While needs not the value of the +argument at the call site, nor a reference to the argument at the call +site, but the actual expression that the caller wants it to use to generate a value. -Same goes for short-circuit evaluation. If you want short-circuit -evaluation in C#, your only hope if to get on the blower to Anders +Same goes for short-circuit evaluation. If you want short-circuit +evaluation in C#, your only hope if to get on the blower to Anders Hejlsberg and persuade him to bake it into the language: bool result = (a > 0 && Math.Sqrt(a) < 10); double result = (a < 0 ? Math.Sqrt(-a) : Math.Sqrt(a)); -You can't write a function like `&&` or `?:` yourself, because C# will -always try to evaluate all the arguments before calling your function. +You can't write a function like `&&` or `?:` yourself, because C# will +always try to evaluate all the arguments before calling your function. Consider a VB exile who wants to reproduce his favourite keywords in C#: @@ -675,24 +675,24 @@ bool result = AndAlso(a > 0, Math.Sqrt(a) < 10); double result = IIf(a < 0, Math.Sqrt(-a), Math.Sqrt(a)); -it would try to evaluate all the arguments at the call site, and pass the -results of those evaluations to `AndAlso` or `IIf`. There's no -short-circuiting. So the `AndAlso` call would crash if a were negative, -and the `IIf` call if a were anything other than 0. Again, what you want is -for the `condition1`, `condition2`, `ifTrue` and `ifFalse` arguments to be -evaluated by the callee if it needs them, not for the caller to evaluate +it would try to evaluate all the arguments at the call site, and pass the +results of those evaluations to `AndAlso` or `IIf`. There's no +short-circuiting. So the `AndAlso` call would crash if a were negative, +and the `IIf` call if a were anything other than 0. Again, what you want is +for the `condition1`, `condition2`, `ifTrue` and `ifFalse` arguments to be +evaluated by the callee if it needs them, not for the caller to evaluate them before making the call. -And that's what *pass by name* does. A parameter passed by name is not -evaluated when it is passed to a method. It is evaluated -- and -re-evaluated -- when the called method evaluates the parameter; -specifically when the called method requests the value of the parameter by -mentioning its name. This might sound weird and academic, but it's the key +And that's what *pass by name* does. A parameter passed by name is not +evaluated when it is passed to a method. It is evaluated -- and +re-evaluated -- when the called method evaluates the parameter; +specifically when the called method requests the value of the parameter by +mentioning its name. This might sound weird and academic, but it's the key to being able to define your own control constructs. #### Using Pass By Name in Scala -Let's see the custom while implementation again, this time with Scala +Let's see the custom while implementation again, this time with Scala *pass by name* parameters: def myWhile(condition: => Boolean)(body: => Unit): Unit = @@ -709,26 +709,26 @@ i += 1 } -Unlike the C# attempt, this prints out the numbers from 0 to 9 and then +Unlike the C# attempt, this prints out the numbers from 0 to 9 and then terminates as you'd wish. Pass by name also works for short-circuiting: import math._ - + def andAlso(condition1: => Boolean, condition2: => Boolean): Boolean = condition1 && condition2 - + val d = -1.234 val result = andAlso(d > 0, sqrt(d) < 10) -The `andAlso` call returns `false` rather than crashing, because +The `andAlso` call returns `false` rather than crashing, because `sqrt(d) < 10` never gets evaluated. -What's going on here? What's the weird colon-and-pointy-sticks syntax? +What's going on here? What's the weird colon-and-pointy-sticks syntax? What is actually getting passed to `myWhile` and `andAlso` to make this work? -The answer is a bit surprising. Nothing is going on here. This is the +The answer is a bit surprising. Nothing is going on here. This is the normal Scala function parameter syntax. There is no *pass by name* in Scala. Here's a bog-standard *pass by value* Scala function declaration: @@ -739,9 +739,9 @@ def myFunc2(f: Int => Boolean): Unit = ... -Even if you've not seen this kind of expression before, it's probably not -too hard to guess what this means. This function takes a *function from -`Int` to `Boolean`* as its argument. In C# terms, +Even if you've not seen this kind of expression before, it's probably not +too hard to guess what this means. This function takes a *function from +`Int` to `Boolean`* as its argument. In C# terms, `void MyFunc2(Func f)`. We could call this as follows: myFunc2 { (i: Int) => i > 0 } @@ -750,46 +750,46 @@ def myFunc3(f: => Boolean) : Unit = ... -Well, if `myFunc2` took an *Int-to-Boolean* function, `myFunc3` must be -taking a "blank-to-Boolean" function -- a function that takes no arguments -and returns a `Boolean`. In short, a conditional expression. So we can +Well, if `myFunc2` took an *Int-to-Boolean* function, `myFunc3` must be +taking a "blank-to-Boolean" function -- a function that takes no arguments +and returns a `Boolean`. In short, a conditional expression. So we can call `myFunc3` as follows: val j = 123 myFunc3 { j > 0 } -The squirly brackets are what we'd expect from an anonymous function, and -because the function has no arguments Scala doesn't make us write -`{ () => j > 0 }`, even though that's what it means really. The anonymous -function has no arguments because `j` is a captured local variable, not an -argument to the function. But there's more. Scala also lets us call +The squirly brackets are what we'd expect from an anonymous function, and +because the function has no arguments Scala doesn't make us write +`{ () => j > 0 }`, even though that's what it means really. The anonymous +function has no arguments because `j` is a captured local variable, not an +argument to the function. But there's more. Scala also lets us call `myFunc3` like this: val j = 123 myFunc3(j > 0) -This is normal function call syntax, but the Scala compiler realises that -`myFunc3` expects a nullary function (a function with no arguments) rather -than a `Boolean`, and therefore treats `myFunc3(j > 0)` as shorthand for -`myFunc3(() => j > 0)`. This is the same kind of logic that the C# compiler -uses when it decides whether to compile a lambda expression to a delegate +This is normal function call syntax, but the Scala compiler realises that +`myFunc3` expects a nullary function (a function with no arguments) rather +than a `Boolean`, and therefore treats `myFunc3(j > 0)` as shorthand for +`myFunc3(() => j > 0)`. This is the same kind of logic that the C# compiler +uses when it decides whether to compile a lambda expression to a delegate or an expression tree. You can probably figure out where it goes from here: def myFunc4(f1: => Boolean)(f2: => Unit): Unit = ... -This takes two functions: a conditional expression, and a function that -takes no arguments and returns no value (in .NET terms, an `Action`). -Using our powers of anticipation, we can imagine how this might be called -using some unholy combination of the two syntaxes we saw for calling +This takes two functions: a conditional expression, and a function that +takes no arguments and returns no value (in .NET terms, an `Action`). +Using our powers of anticipation, we can imagine how this might be called +using some unholy combination of the two syntaxes we saw for calling `myFunc3`: val j = 123; myFunc4(j > 0) { println(j); j -= 1; } -We can mix and match the `()` and `{}` bracketing at whim, except that we -have to use `{}` bracketing if we want to batch up multiple expressions. +We can mix and match the `()` and `{}` bracketing at whim, except that we +have to use `{}` bracketing if we want to batch up multiple expressions. For example, you could legally equally well write the following: myFunc4 { j > 0 } { println(j); j -= 1; } @@ -804,9 +804,9 @@ myFunc5(f1)(f2) } -Written like this, it's clear that `f1` is getting evaluated each time we -execute the if statement, but is getting passed (as a function) when -`myFunc5` recurses. But Scala allows us to leave the parentheses off +Written like this, it's clear that `f1` is getting evaluated each time we +execute the if statement, but is getting passed (as a function) when +`myFunc5` recurses. But Scala allows us to leave the parentheses off function calls with no arguments, so we can write the above as: def myFunc5(f1: => Boolean)(f2: => Unit): Unit = @@ -815,18 +815,18 @@ myFunc5(f1)(f2) } -Again, type inference allows Scala to distinguish the *evaluation of -`f1`* in the if statement from the *passing of `f1`* in the `myFunc5` +Again, type inference allows Scala to distinguish the *evaluation of +`f1`* in the if statement from the *passing of `f1`* in the `myFunc5` recursion. -And with a bit of renaming, that's `myWhile`. There's no separate -*pass by name* convention: just the usual closure behaviour of capturing -local variables in an anonymous method or lambda, a bit of syntactic sugar -for nullary functions (functions with no arguments), just like C#'s -syntactic sugar for property getters, and the Scala compiler's ability to +And with a bit of renaming, that's `myWhile`. There's no separate +*pass by name* convention: just the usual closure behaviour of capturing +local variables in an anonymous method or lambda, a bit of syntactic sugar +for nullary functions (functions with no arguments), just like C#'s +syntactic sugar for property getters, and the Scala compiler's ability to recognise when a closure is required instead of a value. -In fact, armed with this understanding of the Scala "syntax," we can +In fact, armed with this understanding of the Scala "syntax," we can easily map it back to C#: void While(Func condition, Action body) @@ -837,7 +837,7 @@ While(condition, body); } } - + int i = 0; While(() => i < 10, () => { @@ -845,67 +845,67 @@ ++i; }); -The implementation of the `While` method in C# is, to my eyes, a bit -clearer than the Scala version. However, the syntax for *calling* the -`While` method in C# is clearly way more complicated and less natural than -the syntax for calling `myWhile` in Scala. Calling `myWhile` in Scala was -like using a native language construct. Calling While in C# required a -great deal of clutter at the call site to prevent C# from trying to treat +The implementation of the `While` method in C# is, to my eyes, a bit +clearer than the Scala version. However, the syntax for *calling* the +`While` method in C# is clearly way more complicated and less natural than +the syntax for calling `myWhile` in Scala. Calling `myWhile` in Scala was +like using a native language construct. Calling While in C# required a +great deal of clutter at the call site to prevent C# from trying to treat `i < 10` as a once-and-for-all value, and to express the body at all. -So that's so-called "pass by name" demystified: The Scala Web site, with -crushing mundanity, demotes it to "automatic type-dependent closure -construction," which is indeed exactly how it works. As we've seen, -however, this technical-sounding feature is actually essential to -creating nice syntax for your own control constructs. We'll shortly see -how this works together with other Scala features to give you even more +So that's so-called "pass by name" demystified: The Scala Web site, with +crushing mundanity, demotes it to "automatic type-dependent closure +construction," which is indeed exactly how it works. As we've seen, +however, this technical-sounding feature is actually essential to +creating nice syntax for your own control constructs. We'll shortly see +how this works together with other Scala features to give you even more flexibility in defining your construct's syntax. ### Implicits -Scala implicits offer some features which will be familiar to the C# -programmer, but are much more general in nature and go far beyond what can +Scala implicits offer some features which will be familiar to the C# +programmer, but are much more general in nature and go far beyond what can be done in C#. #### Enriching types in C# and Scala -Scala, like C#, is statically typed: a class’ methods are compiled into the -class definition and are not open for renegotiation. You cannot, as you -might in Ruby or Python, just go ahead and declare additional methods on an +Scala, like C#, is statically typed: a class’ methods are compiled into the +class definition and are not open for renegotiation. You cannot, as you +might in Ruby or Python, just go ahead and declare additional methods on an existing class. -This is of course very inconvenient. You end up declaring a load of -`FooHelper` or `FooUtils` classes full of static methods, and having to -write verbose calling code such as `if (EnumerableUtils.IsEmpty(sequence))` +This is of course very inconvenient. You end up declaring a load of +`FooHelper` or `FooUtils` classes full of static methods, and having to +write verbose calling code such as `if (EnumerableUtils.IsEmpty(sequence))` rather than the rather more readable `if (sequence.IsEmpty())`. -C# 3 tries to address this problem by introducing extension methods. -Extension methods are static methods in a `FooHelper` or `FooUtils` kind -of class, except you’re allowed to write them using member syntax. -By defining `IsEmpty` as an extension method on `IEnumerable`, you can +C# 3 tries to address this problem by introducing extension methods. +Extension methods are static methods in a `FooHelper` or `FooUtils` kind +of class, except you’re allowed to write them using member syntax. +By defining `IsEmpty` as an extension method on `IEnumerable`, you can write `if (sequence.IsEmpty())` after all. -Scala disapproves of static classes and global methods, so it plumps for -an alternative approach. You’ll still write a `FooHelper` or `FooUtils` -kind of class, but instead of taking the `Foo` to be Helped or Utilised as -a method parameter, your class will wrap `Foo` and enrich it with -additional methods. Let’s see this in action as we try to add a method to +Scala disapproves of static classes and global methods, so it plumps for +an alternative approach. You’ll still write a `FooHelper` or `FooUtils` +kind of class, but instead of taking the `Foo` to be Helped or Utilised as +a method parameter, your class will wrap `Foo` and enrich it with +additional methods. Let’s see this in action as we try to add a method to the `Double` type: - class RicherDouble(d : Double) { + class RicherDouble(d : Double) { def toThe(exp: Double): Double = System.Math.Pow(d, exp) } -(We call the class `RicherDouble` because Scala already has a `RichDouble` +(We call the class `RicherDouble` because Scala already has a `RichDouble` class defined which provides further methods to `Double`.) -Notice that `toThe` is an instance method, and that `RicherDouble` takes a -`Double` as a constructor parameter. This seems pretty grim, because we’d +Notice that `toThe` is an instance method, and that `RicherDouble` takes a +`Double` as a constructor parameter. This seems pretty grim, because we’d normally have to access the function like this: val result = new DoubleExtensions(2.0).toThe(7.0) -Hardly readable. To make it look nice, Scala requires us to define an +Hardly readable. To make it look nice, Scala requires us to define an *implicit conversion* from `Double` to `RicherDouble`: object Implicits { @@ -920,31 +920,31 @@ val twoToTheSeven = 2.0.toThe(7.0) -and all will be well. The `Double` type has apparently been successfully +and all will be well. The `Double` type has apparently been successfully enriched with the `toThe` method. -This is, of course, just as much an illusion as the C# equivalent. -C# extension methods don’t add methods to a type, and nor do Scala -implicit conversions. What has happened here is that the Scala compiler -has looked around for implicit methods that are applicable to the type of -`2.0` (namely `Double`), and return a type that has a `toThe` method. -Our `Implicits.richerDouble` method fits the bill, so the Scala compiler -silently inserts a call to that method. At runtime, therefore, Scala calls -`Implicits.richerDouble(2.0)` and calls the `toThe` of the resulting +This is, of course, just as much an illusion as the C# equivalent. +C# extension methods don’t add methods to a type, and nor do Scala +implicit conversions. What has happened here is that the Scala compiler +has looked around for implicit methods that are applicable to the type of +`2.0` (namely `Double`), and return a type that has a `toThe` method. +Our `Implicits.richerDouble` method fits the bill, so the Scala compiler +silently inserts a call to that method. At runtime, therefore, Scala calls +`Implicits.richerDouble(2.0)` and calls the `toThe` of the resulting `RicherDouble`. -If setting this up seems a bit verbose, well, maybe. C# extension methods -are designed to be easily – one might even say implicitly – brought into -scope. That’s very important for operators like the LINQ standard query -operators, but it can result in unwanted extension methods being dragged -into scope and causing havoc. Scala requires the caller to be a bit more -explicit about implicits, which results in a slightly higher setup cost but +If setting this up seems a bit verbose, well, maybe. C# extension methods +are designed to be easily – one might even say implicitly – brought into +scope. That’s very important for operators like the LINQ standard query +operators, but it can result in unwanted extension methods being dragged +into scope and causing havoc. Scala requires the caller to be a bit more +explicit about implicits, which results in a slightly higher setup cost but gives the caller finer control over which implicit methods are considered. -But as it happens you can avoid the need for separate definitions of -`Implicits` and `RicherDouble`, and get back to a more concise -representation by using an anonymous class. (As you’d expect, Scala -anonymous classes are fully capable, like Java ones, rather than the +But as it happens you can avoid the need for separate definitions of +`Implicits` and `RicherDouble`, and get back to a more concise +representation by using an anonymous class. (As you’d expect, Scala +anonymous classes are fully capable, like Java ones, rather than the neutered C# version.) Here’s how it looks: object Implicits { @@ -953,69 +953,69 @@ } } -Well, big deal. Scala can enrich existing types with new methods just like -C#, but using a different syntax. In related news, Lisp uses a different -kind of bracket: film at eleven. Why should we be interested in Scala +Well, big deal. Scala can enrich existing types with new methods just like +C#, but using a different syntax. In related news, Lisp uses a different +kind of bracket: film at eleven. Why should we be interested in Scala implicits if they’re just another take on extension methods? #### Implicit Parameters -What we saw above was an implicit method – a method which, like a C# -implicit conversion operator, the compiler is allowed to insert a call to -without the programmer writing that call. Scala also has the idea of -implicit parameters – that is, parameters which the compiler is allowed to +What we saw above was an implicit method – a method which, like a C# +implicit conversion operator, the compiler is allowed to insert a call to +without the programmer writing that call. Scala also has the idea of +implicit parameters – that is, parameters which the compiler is allowed to insert a value for without the programmer specifying that value. -That’s just optional parameters with default values, right? Like C++ and -Visual Basic have had since “visual” meant ASCII art on a teletype, and +That’s just optional parameters with default values, right? Like C++ and +Visual Basic have had since “visual” meant ASCII art on a teletype, and like C# is about to get? Well, no. -C++, Visual Basic and C# optional parameters have fixed defaults specified +C++, Visual Basic and C# optional parameters have fixed defaults specified by the called function. For example, if you have a method like this: public void Fie(int a, int b = 123) { … } -and you call `Fie(456)`, it’s always going to be equivalent to calling +and you call `Fie(456)`, it’s always going to be equivalent to calling `Fie(456, 123)`. -A Scala implicit parameter, on the other hand, gets its value from the -calling context. That allows programmer calling the method to control the -implicit parameter value, creating an extensibility point that optional +A Scala implicit parameter, on the other hand, gets its value from the +calling context. That allows programmer calling the method to control the +implicit parameter value, creating an extensibility point that optional parameters don’t provide. -This probably all sounds a bit weird, so let’s look at an example. Consider +This probably all sounds a bit weird, so let’s look at an example. Consider the following `Concatenate` method: public T Concatenate(IEnumerable sequence, T seed, Func concatenator); -We pass this guy a sequence, a start value and a function that combines two -values into one, and it returns the result of calling that function across -the sequence. For example, you could pass a sequence of strings, a start -value of `String.Empty`, and `(s1, s2) => s1 + s2`, and it would return you +We pass this guy a sequence, a start value and a function that combines two +values into one, and it returns the result of calling that function across +the sequence. For example, you could pass a sequence of strings, a start +value of `String.Empty`, and `(s1, s2) => s1 + s2`, and it would return you all the strings concatenated together: IEnumerable sequence = new string[] { “mog”, “bites”, “man” }; string result = Concatenate(sequence, String.Empty, (s1, s2) => s1 + s2); // result is “mogbitesman” -But this is a unpleasantly verbose. We’re having to pass in `String.Empty` -and `(s1, s2) => s1 + s2` every time we want to concatenate a sequence of -strings. Not only is this tedious, it also creates the opportunity for -error when the boss decides to “help” and passes the literal -`"String.Empty"` as the seed value instead. (“Oh, and I upgraded all the -semi-colons to colons while I was in there. No, don’t thank me!”) We’d -like to just tell the Concatenate function, “Look, this is how you +But this is a unpleasantly verbose. We’re having to pass in `String.Empty` +and `(s1, s2) => s1 + s2` every time we want to concatenate a sequence of +strings. Not only is this tedious, it also creates the opportunity for +error when the boss decides to “help” and passes the literal +`"String.Empty"` as the seed value instead. (“Oh, and I upgraded all the +semi-colons to colons while I was in there. No, don’t thank me!”) We’d +like to just tell the Concatenate function, “Look, this is how you concatenate strings,” once and for all. -Let’s start out by redefining the `Concatenate` method in Scala. -I’m going to factor out the seed and the concatenator method into a trait +Let’s start out by redefining the `Concatenate` method in Scala. +I’m going to factor out the seed and the concatenator method into a trait because we’ll typically be defining them together. trait Concatenator[T] { def startValue: T def concat(x: T, y: T): T } - + object implicitParameters { def concatenate[T](xs: List[T])(c: Concatenator[T]): T = if (xs.isEmpty) c.startValue @@ -1028,7 +1028,7 @@ def startValue: String = "" def concat(x: String, y: String) = x.concat(y) } - + object implicitParameters { def main(args: Array[String]) = { val result = concatenate(List("mog", "bites", "man"))(stringConcatenator) @@ -1036,21 +1036,21 @@ } } -So far, this looks like the C# version except for the factoring out of the -`Concatenator` trait. We’re still having to pass in the +So far, this looks like the C# version except for the factoring out of the +`Concatenator` trait. We’re still having to pass in the `stringConcatenator` at the point of the call. Let’s fix that: def concatenate[T](xs: List[T])(implicit c: Concatenator[T]): T = if (xs.isEmpty) c.startValue else c.concat(xs.head, concatenate(xs.tail)) -We’ve changed two things here. First, we’ve declared c to be an *implicit -parameter*, meaning the caller can leave it out. Second, we’ve left +We’ve changed two things here. First, we’ve declared c to be an *implicit +parameter*, meaning the caller can leave it out. Second, we’ve left it out ourselves, in the recursive call to `concatenate(xs.tail)`. -Well, okay, it’s nice that `concatenate` now doesn’t have to pass the -`Concatenator` explicitly to the recursive call, but we’re still having to -pass in the `stringConcatenator` object to get things started. If only +Well, okay, it’s nice that `concatenate` now doesn’t have to pass the +`Concatenator` explicitly to the recursive call, but we’re still having to +pass in the `stringConcatenator` object to get things started. If only there were some way to make the `stringConcatenator` object itself implicit… object Implicits { @@ -1060,16 +1060,16 @@ } } -Again, we’ve done two things here. First, we’ve declared the -`stringConcatenator` object implicit. Consequently, we’ve had to move it -out of the top level, because Scala doesn’t allow implicits at the top -level (because they’d pollute the global namespace, being in scope even +Again, we’ve done two things here. First, we’ve declared the +`stringConcatenator` object implicit. Consequently, we’ve had to move it +out of the top level, because Scala doesn’t allow implicits at the top +level (because they’d pollute the global namespace, being in scope even without an explicit import statement). Now we can call `concatenate` like this: import Implicits._ - + object implicitParameters { def main(args: Array[String]) = { val result = concatenate(List("mog", "bites", "man")) @@ -1079,10 +1079,10 @@ And we’ll still get “mogbitesman” as the output. -Let’s review what’s going on here. The implicit parameter of concatenate -has been set to our `stringConcatenator`, a default value that the -`concatenate` method knew nothing about when it was compiled. This is -somewhere north of what classical optional parameters are capable of, +Let’s review what’s going on here. The implicit parameter of concatenate +has been set to our `stringConcatenator`, a default value that the +`concatenate` method knew nothing about when it was compiled. This is +somewhere north of what classical optional parameters are capable of, and we’re not finished yet. Let’s build a `listConcatenator`. object Implicits { @@ -1093,14 +1093,14 @@ implicit object stringListConcatenator extends ListConcatenator[String] { } } -This is a bit vexing. `List` in Scala is a generic type, and has a generic -concatenation method called `:::`. But we can’t create a generic object, -because an object is an instance. And implicit parameters have to be objects. -So the best we can do is build a generic `ListConcatenator` class, and then -create trivial implicit objects for each generic parameter type we might +This is a bit vexing. `List` in Scala is a generic type, and has a generic +concatenation method called `:::`. But we can’t create a generic object, +because an object is an instance. And implicit parameters have to be objects. +So the best we can do is build a generic `ListConcatenator` class, and then +create trivial implicit objects for each generic parameter type we might need. -However, let’s not worry about the implementation, and see how this is used +However, let’s not worry about the implementation, and see how this is used at the calling end: val result = concatenate(List( @@ -1108,109 +1108,109 @@ List("on", "beard") )) -This displays `List(mog, bites, man, on, beard)`; that is, it concatenates -the two `List[String]`s into one. Once again, we have not had to pass -`stringListConcatenator` explicitly: the Scala compiler has gone and found -it for us. We can use the exact same calling code to concatenate lists and +This displays `List(mog, bites, man, on, beard)`; that is, it concatenates +the two `List[String]`s into one. Once again, we have not had to pass +`stringListConcatenator` explicitly: the Scala compiler has gone and found +it for us. We can use the exact same calling code to concatenate lists and strings. #### Why Should I Care? -Isn’t this pointless? At the call site, I have access to -`stringConcatenator` and `listStringConcatenator`. I can easily pass them +Isn’t this pointless? At the call site, I have access to +`stringConcatenator` and `listStringConcatenator`. I can easily pass them in rather than relying on spooky compiler magic to do it for me. Aren’t implicit parameters just job security for compiler writers? -Yes, implicit parameters are technically unnecessary. But if we’re going -to play that game, C# is technically unnecessary. You could write all that -code in IL. Extension methods are unnecessary because you could write the -static method out longhand. Optional parameters are unnecessary because -you could read the documentation and pass them in explicitly. -Post-It notes are unnecessary because you could fire up Outlook and create +Yes, implicit parameters are technically unnecessary. But if we’re going +to play that game, C# is technically unnecessary. You could write all that +code in IL. Extension methods are unnecessary because you could write the +static method out longhand. Optional parameters are unnecessary because +you could read the documentation and pass them in explicitly. +Post-It notes are unnecessary because you could fire up Outlook and create a Note instead. -Implicit parameters are about convenience and expressiveness. Implicit -parameters give you a way of describing how a function should handle -different situations, without needing to bake those situations into the +Implicit parameters are about convenience and expressiveness. Implicit +parameters give you a way of describing how a function should handle +different situations, without needing to bake those situations into the function logic or to specify them every time you call the function. -You don’t want to have to tell the `concatenate` function whether to use -the `List` or `String` concatenator every time you call it: the compiler -knows what you’re concatenating; specifying how to concatenate it just +You don’t want to have to tell the `concatenate` function whether to use +the `List` or `String` concatenator every time you call it: the compiler +knows what you’re concatenating; specifying how to concatenate it just gives you a chance to get it wrong! -Consequently, implicit parameters – like implicit conversions – contribute -to Scala’s ability to support internal DSLs. By setting up appropriate -implicits, you can write code that reads much more naturally than if you +Consequently, implicit parameters – like implicit conversions – contribute +to Scala’s ability to support internal DSLs. By setting up appropriate +implicits, you can write code that reads much more naturally than if you had to pepper it with function objects or callbacks. #### Conclusion -Scala’s `implicit` keyword goes beyond C#’s equivalent. As in C#, it is -used for implicit conversions; unlike C#, this is the idiomatic way to add -operations to an existing type, removing the need for the separate -extension method syntax. Implicit parameters have no equivalent in C#. -They are like being able to add default values to a method: just as a C# -using statement bring implicit methods into scope, a Scala import statement -can bring default values into scope. If implicit conversions are a way of -extending classes, then implicit parameters are a way of extending methods, -creating simple, reliable shorthands for complex generic methods, and +Scala’s `implicit` keyword goes beyond C#’s equivalent. As in C#, it is +used for implicit conversions; unlike C#, this is the idiomatic way to add +operations to an existing type, removing the need for the separate +extension method syntax. Implicit parameters have no equivalent in C#. +They are like being able to add default values to a method: just as a C# +using statement bring implicit methods into scope, a Scala import statement +can bring default values into scope. If implicit conversions are a way of +extending classes, then implicit parameters are a way of extending methods, +creating simple, reliable shorthands for complex generic methods, and making up another piece of the Scala DSL jigsaw. #### Method Call Syntax -C#, like most object-oriented programming languages, is pretty strict about -how you call methods: you use the dot notation, unless the method is a -special ‘operator’ method such as `operator+`, `operator==` or a conversion -operator. The special operator methods are predefined by the compiler: you -can write your own implementation, but you can’t create your own operator -names. You can teach the `+` operator how to handle your custom type, but +C#, like most object-oriented programming languages, is pretty strict about +how you call methods: you use the dot notation, unless the method is a +special ‘operator’ method such as `operator+`, `operator==` or a conversion +operator. The special operator methods are predefined by the compiler: you +can write your own implementation, but you can’t create your own operator +names. You can teach the `+` operator how to handle your custom type, but you can’t add an exponentiation operator: int a = b ** c; -C# has three problems with this: first, it doesn’t like the method name -`**`; second, it doesn’t like that there’s no `.` before the name; and +C# has three problems with this: first, it doesn’t like the method name +`**`; second, it doesn’t like that there’s no `.` before the name; and third, it doesn’t like that there’s no brackets around the method argument. -To get around the objection to the name, let’s compromise and call it +To get around the objection to the name, let’s compromise and call it `ToThe` for now. So what C# insists on seeing is `a.ToThe(b)`. -Scala, like many functional languages, isn’t so strict. Scala allows you -to use any method with a single argument in an infix position. Before we -can see this in the exponentiation example, we will enrich the `Double` +Scala, like many functional languages, isn’t so strict. Scala allows you +to use any method with a single argument in an infix position. Before we +can see this in the exponentiation example, we will enrich the `Double` type with the `toThe` method as we learned earlier: import Implicits._ import math._ - + class RicherDouble(d: Double) { def toThe(exp: Double): Double = pow(d, exp) } - + object Implicits { implicit def richerDouble(d: Double) = new RicherDouble(d) } -Recall that this is just the Scala idiom for extension methods – it’s the -equivalent of writing -`public static ToThe(this double first, double second) { … }` in C#. -(If we were wanting to use infix notation with our own class, we wouldn’t +Recall that this is just the Scala idiom for extension methods – it’s the +equivalent of writing +`public static ToThe(this double first, double second) { … }` in C#. +(If we were wanting to use infix notation with our own class, we wouldn’t need all this malarkey.) So now we can write: val raised = 2.0.toThe(7.0) -Okay, so what do we need to do to get this to work in infix position? +Okay, so what do we need to do to get this to work in infix position? Nothing, it turns out. val raised = 2.0 toThe 8.0 // it just works -This still doesn’t look much like a built-in operator, but it turns out +This still doesn’t look much like a built-in operator, but it turns out Scala is less fussy than C# about method names too. - class DoubleExtensions(d : Double) { + class DoubleExtensions(d : Double) { def **(exp: Double): Double = Pow(d, exp) } - + val raised = 2.0 ** 9.0 // it still just works Much nicer. @@ -1220,16 +1220,15 @@ class RicherString(s: String) { def twice: String = s + s } - + val drivel = "bibble" twice -Calling methods in infix and postfix nodadion is obviously fairly simple -syntactic sugar over normal dot notation. But this seemingly minor feature -is very important in constructing DSLs, allowing Scala to do in internal -DSLs what many languages can do only using external tools. For example, -where most languages do parsing via an external file format and a tool to -translate that file format into native code (a la `lex` and `yacc`), -Scala’s parser library makes extensive use of infix and postfix methods to -provide a “traditional” syntax for describing a parser, but manages it +Calling methods in infix and postfix nodadion is obviously fairly simple +syntactic sugar over normal dot notation. But this seemingly minor feature +is very important in constructing DSLs, allowing Scala to do in internal +DSLs what many languages can do only using external tools. For example, +where most languages do parsing via an external file format and a tool to +translate that file format into native code (a la `lex` and `yacc`), +Scala’s parser library makes extensive use of infix and postfix methods to +provide a “traditional” syntax for describing a parser, but manages it entirely within the Scala language. - diff --git a/_overviews/tutorials/scala-for-java-programmers.md b/_overviews/tutorials/scala-for-java-programmers.md index ebac87fce3..9d9264b825 100644 --- a/_overviews/tutorials/scala-for-java-programmers.md +++ b/_overviews/tutorials/scala-for-java-programmers.md @@ -6,37 +6,102 @@ partof: scala-for-java-programmers languages: [es, ko, de, it, ja, zh-tw] permalink: /tutorials/:title.html + +get_started_resources: + - title: "Getting Started" + description: "Install Scala on your computer and start writing some Scala code!" + icon: "fa fa-rocket" + link: /getting-started.html + - title: Scala in the Browser + description: > + To start experimenting with Scala right away, use "Scastie" in your browser. + icon: "fa fa-cloud" + link: https://scastie.scala-lang.org/pEBYc5VMT02wAGaDrfLnyw +java_resources: + - title: Scala for Java Developers + description: A cheatsheet with a comprehensive side-by-side comparison of Java and Scala. + icon: "fa fa-coffee" + link: /scala3/book/scala-for-java-devs.html +next_resources: + - title: Scala Book + description: Learn Scala by reading a series of short lessons. + icon: "fa fa-book-open" + link: /scala3/book/introduction.html + - title: Online Courses + description: MOOCs to learn Scala, for beginners and experienced programmers. + icon: "fa fa-cloud" + link: /online-courses.html --- -By Michel Schinz and Philipp Haller +If you are coming to Scala with some Java experience already, this page should give a good overview of +the differences, and what to expect when you begin programming with Scala. For best results we suggest +to either set up a Scala toolchain on your computer, or try compiling Scala snippets in the browser with Scastie: + +{% include inner-documentation-sections.html links=page.get_started_resources %} + +## At a Glance: Why Scala? + +**Java without Semicolons:** There's a saying that Scala is Java without semicolons. +There is a lot of a truth to this statement: Scala simplifies much of the noise and boilerplate of Java, +while building upon the same foundation, sharing the same underlying types and runtime. + +**Seamless Interop:** Scala can use any Java library out of the box; including the Java standard library! +And pretty much any Java program will work the same in Scala, just by converting the syntax. + +**A Scalable Language:** the name Scala comes from Scalable Language. Scala scales not only with hardware +resources and load requirements, but also with the level of programmer's skill. If you choose, Scala +rewards you with expressive additional features, which when compared to Java, boost developer productivity and +readability of code. + +**It Grows with You:** Learning these extras are optional steps to approach at your own pace. +The most fun and effective way to learn, in our opinion, is to ensure you are productive first with what knowledge +you have from Java. And then, learn one thing at a time following the [Scala Book][scala-book]. Pick the learning pace convenient for you and ensure whatever you are learning is fun. + +**TL;DR:** You can start writing Scala as if it were Java with new syntax, then explore from there as you see fit. -## Introduction +## Next Steps -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. +### Compare Java and Scala +The remainder of this tutorial expands upon some of the key differences between Java and Scala, +with further explanations. **If you only want a quick reference** between the two, read +*Scala for Java Developers*, it comes +with many snippets which you can try out in your chosen Scala setup: -## A First Example +{% include inner-documentation-sections.html links=page.java_resources %} -As a first example, we will use the standard *Hello world* program. It +### Explore Further + +When you finish these guides, we recommend to continue your Scala journey by reading the +*Scala Book* or following a number of *online MOOCs*. + +{% include inner-documentation-sections.html links=page.next_resources %} + +## Your First Program + +### Writing Hello World + +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]): Unit = { - println("Hello, world!") - } - } +{% tabs hello-world-demo class=tabs-scala-version %} +{% tab 'Scala 2' for=hello-world-demo %} +```scala +object HelloWorld { + def main(args: Array[String]): Unit = { + 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 +it's entry-point consists of one method called `main` which takes the command +line arguments, an array of strings, as a 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. Therefore, its return type is declared as `Unit`. +return a value. Therefore, its return type is declared as `Unit` +(equivalent to `void` in Java). What is less familiar to Java programmers is the `object` declaration containing the `main` method. Such a declaration @@ -46,44 +111,102 @@ 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 +Another difference from Java is 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. +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} +```scala +@main def HelloWorld(args: String*): Unit = + println("Hello, World!") +``` +The structure of this program may not be familiar to Java programmers: +there is no method called `main`, instead the `HelloWorld` method is marked +as an entry-point by adding the `@main` annotation. + +program entry-points optionally take parameters, which are populated by the +command line arguments. Here `HelloWorld` captures all the arguments in +a variable-length sequence of strings called `args`. + +The body of the method consists of a single call to the +predefined method `println` with the friendly greeting as argument. +The `HelloWorld` method does not +return a value. Therefore, its return type is declared as `Unit` +(equivalent to `void` in Java). + +Even less familiar to Java programmers is that `HelloWorld` +does not need to be wrapped in a class definition. Scala 3 +supports top-level method definitions, which are ideal for +program entry-points. + +The method also does not need to be declared as `static`. +This is because static members (methods or fields) do not exist in Scala. +Instead, top-level methods and fields are members of their enclosing +package, so can be accessed from anywhere in a program. + +> **Implementation detail**: so that the JVM can execute the program, +> the `@main` annotation generates a class `HelloWorld` with a +> static `main` method which calls the `HelloWorld` method with the +> command line arguments. +> This class is only visible at runtime. +{% endtab %} + +{% endtabs %} + +### Running Hello World + +> **Note:** The following assumes you are using Scala on the command line If we save the above program in a file called -`HelloWorld.scala`, we can compile it by issuing the following +`HelloWorld.scala`, we can run it by issuing the following command (the greater-than sign `>` represents the shell prompt and should not be typed): - > scalac HelloWorld.scala +```shell +> scala run HelloWorld.scala +``` + +The program will be automatically compiled (with compiled classes somewhere in the newly created `.scala-build` directory) +and executed, producing an output similar to: +``` +Compiling project (Scala {{site.scala-3-version}}, JVM (20)) +Compiled project (Scala {{site.scala-3-version}}, JVM (20)) +Hello, World! +``` -This will generate a few class files in the current directory. One of +#### Compiling From the Command Line + +To compile the example, we use `scala compile` command, which will invoke the Scala compiler, `scalac`. `scalac` +works like most compilers: it takes a source file as argument, maybe +some options, and produces one or several output files. The outputs +it produces are standard Java class files. + +```shell +> scala compile HelloWorld.scala -d . +``` + +This will generate a few class files in the current directory (`-d` option sets the compilation output 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 +#### Running From the Command Line -Once compiled, a Scala program can be run using the `scala` command. +Once compiled, the program can be run using the `scala run` 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 +programs, and accepts similar options. The above example can be executed using the following command, which produces the expected output: - > scala -classpath . HelloWorld - - Hello, world! +```shell +> scala run --main-class HelloWorld -classpath . +Hello, World! +``` -## Interaction with Java +## Using Java Libraries 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 @@ -95,57 +218,83 @@ 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 +`LocalDate` and `DateTimeFormatter`. Since Scala interoperates seamlessly with Java, there is no need to implement equivalent -classes in the Scala class library--we can simply import the classes +classes in the Scala class library; instead, we can import the classes of the corresponding Java packages: - import java.util.{Date, Locale} - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]): Unit = { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - +{% tabs date-time-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=date-time-demo %} +```scala +import java.time.format.{DateTimeFormatter, FormatStyle} +import java.time.LocalDate +import java.util.Locale._ + +object FrenchDate { + def main(args: Array[String]): Unit = { + val now = LocalDate.now + val df = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(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. +package or class, in Scala 2 we use the underscore character (`_`) instead +of the asterisk (`*`). +{% endtab %} + +{% tab 'Scala 3' for=date-time-demo %} +```scala +import java.time.format.{DateTimeFormatter, FormatStyle} +import java.time.LocalDate +import java.util.Locale.* + +@main def FrenchDate: Unit = + val now = LocalDate.now + val df = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(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. Like with Java, in Scala 3 we use the asterisk (`*`) to import all +the names of a package or class. +{% endtab %} + +{% endtabs %} -The import statement on the second line therefore imports all members -of the `DateFormat` class. This makes the static method -`getDateInstance` and the static field `LONG` directly +The import statement on the third line therefore imports all members +of the `Locale` enum. This makes the static field `FRANCE` 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 +Inside the entry-point method we first create an instance of Java's +`DateTime` class, containing today's date. Next, we +define a date format using the `DateTimeFormatter.ofLocalizedDate` method, +passing the `LONG` format style, then further passing the `FRANCE` locale 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. +formatted according to the localized `DateTimeFormatter` instance. 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. +### Sidepoint: Third-Party Libraries + +Usually the standard library is not enough. As a Java programmer, you might already know a lot of Java libraries +that you'd like to use in Scala. The good news is that, as with Java, Scala's library ecosystem is built upon Maven coordinates. + +**Most Scala projects are built with sbt:** Adding third party libraries is usually managed by a build tool. +Coming from Java you may be familiar with Maven, Gradle and other such tools. +It's still possible to [use these][maven-setup] to build Scala projects, however it's common to use sbt. +See [setup a Scala Project with sbt][sbt-setup] for a guide on how +to build a project with sbt and add some dependencies. + + ## Everything is an Object Scala is a pure object-oriented language in the sense that @@ -159,85 +308,133 @@ types. Since numbers are objects, they also have methods. And in fact, an arithmetic expression like the following: - 1 + 2 * 3 / x +{% tabs math-expression-inline %} +{% tab 'Scala 2 and 3' for=math-expression-inline %} +```scala +1 + 2 * 3 / x +``` +{% endtab %} +{% endtabs %} consists exclusively of method calls, because it is equivalent to the following expression, as we saw in the previous section: - 1.+(2.*(3)./(x)) +{% tabs math-expression-explicit %} +{% tab 'Scala 2 and 3' for=math-expression-explicit %} +```scala +1.+(2.*(3)./(x)) +``` +{% endtab %} +{% endtabs %} -This also means that `+`, `*`, etc. are valid identifiers +This also means that `+`, `*`, etc. are valid identifiers for fields/methods/etc in Scala. ### Functions are objects -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. +True to _everything_ being an object, in Scala even functions are objects, going beyond Java's support for +lambda expressions. + +Compared to Java, there is very little difference between function objects and methods: you can pass methods as +arguments, store them in variables, and return them from other functions, all without special syntax. +This ability to manipulate functions as values is one of the cornerstones of a very +interesting programming paradigm called *functional programming*. + +To demonstrate, consider a timer function which +performs some action every second. The action to be performed is supplied by the +caller as a function value. 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 +of all functions which take no arguments and return no useful value +(as before, the type `Unit` is similar to `void` in Java). + +The entry-point of this program calls `oncePerSecond` by directly passing +the `timeFlies` method. + +In the end this program will infitely print the sentence `time flies like an arrow` every second. - object Timer { - def oncePerSecond(callback: () => Unit): Unit = { - while (true) { callback(); Thread sleep 1000 } - } - def timeFlies(): Unit = { - println("time flies like an arrow...") - } - def main(args: Array[String]): Unit = { - oncePerSecond(timeFlies) - } - } +{% tabs callback-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=callback-demo %} +```scala +object Timer { + def oncePerSecond(callback: () => Unit): Unit = { + while (true) { callback(); Thread.sleep(1000) } + } + def timeFlies(): Unit = { + println("time flies like an arrow...") + } + def main(args: Array[String]): Unit = { + oncePerSecond(timeFlies) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=callback-demo %} +```scala +def oncePerSecond(callback: () => Unit): Unit = + while true do { callback(); Thread.sleep(1000) } + +def timeFlies(): Unit = + println("time flies like an arrow...") + +@main def Timer: Unit = + oncePerSecond(timeFlies) +``` +{% endtab %} + +{% endtabs %} 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): Unit = { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]): Unit = { - oncePerSecond(() => - println("time flies like an arrow...")) - } - } +In Scala, lambda expressions are known as anonymous functions. +They are useful when functions are so short it is perhaps unneccesary +to give them a name. + +Here is a revised version of the timer +program, passing an anonymous function to `oncePerSecond` instead of `timeFlies`: + +{% tabs callback-demo-refined class=tabs-scala-version %} + +{% tab 'Scala 2' for=callback-demo-refined %} +```scala +object TimerAnonymous { + def oncePerSecond(callback: () => Unit): Unit = { + while (true) { callback(); Thread.sleep(1000) } + } + def main(args: Array[String]): Unit = { + oncePerSecond(() => + println("time flies like an arrow...")) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=callback-demo-refined %} +```scala +def oncePerSecond(callback: () => Unit): Unit = + while true do { callback(); Thread.sleep(1000) } + +@main def TimerAnonymous: Unit = + oncePerSecond(() => + println("time flies like an arrow...")) +``` +{% endtab %} + +{% endtabs %} 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 right arrow (`=>`), different from Java's thin arrow (`->`), which +separates the function's argument list from its body. +In this example, the argument list is empty, so we put empty parentheses +on the left of the arrow. The body of the function is the same as the one of `timeFlies` above. @@ -252,44 +449,88 @@ 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 - } +{% tabs class-demo class=tabs-scala-version %} +{% tab 'Scala 2' for=class-demo %} +```scala +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 number. 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` +creating an instance of class `Complex`, as follows: +```scala +new Complex(1.5, 2.3) +``` +The class contains two methods, called `re` and `im`, which give access to these two parts. +{% endtab %} + +{% tab 'Scala 3' for=class-demo %} +```scala +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 number. These arguments must be passed when +creating an instance of class `Complex`, as follows: +```scala +new Complex(1.5, 2.3) +``` +where `new` is optional. +The class contains two methods, called `re` +and `im`, which give access to these two parts. +{% endtab %} + +{% endtabs %} 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 -able to. 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. +> **Important:** The inferred result type of a method can change +> in subtle ways if the implementation changes, which could have a +> knock-on effect. Hence it is a best practise to put explicit +> result types for public members of classes. + +For local values in methods, it is encouraged to infer result types. +Try to experiment by omitting type declarations when they 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 +order to call them, one has to put an empty pair of parentheses after their name, as the following example shows: - object ComplexNumbers { - def main(args: Array[String]): Unit = { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } +{% tabs method-call-with-args-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=method-call-with-args-demo %} +```scala +object ComplexNumbers { + def main(args: Array[String]): Unit = { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=method-call-with-args-demo %} +```scala +@main def ComplexNumbers: Unit = + val c = Complex(1.2, 3.4) + println("imaginary part: " + c.im()) +``` +{% endtab %} + +{% endtabs %} 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 @@ -299,10 +540,26 @@ 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 - } +{% tabs class-no-method-params-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-no-method-params-demo %} +```scala +class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary +} +``` +{% endtab %} + +{% tab 'Scala 3' for=class-no-method-params-demo %} +```scala +class Complex(real: Double, imaginary: Double): + def re = real + def im = imaginary +``` +{% endtab %} + +{% endtabs %} ### Inheritance and overriding @@ -318,27 +575,63 @@ 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" - } +{% tabs class-inheritance-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-inheritance-demo %} +```scala +class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + override def toString() = + "" + re + (if (im >= 0) "+" else "") + im + "i" +} +``` +{% endtab %} + +{% tab 'Scala 3' for=class-inheritance-demo %} +```scala +class Complex(real: Double, imaginary: Double): + def re = real + def im = imaginary + override def toString() = + "" + re + (if im >= 0 then "+" else "") + im + "i" +``` +{% endtab %} + +{% endtabs %} -We can call overridden `toString` method as below. +We can call the overridden `toString` method as below: - object ComplexNumbers { - def main(args: Array[String]): Unit = { - val c = new Complex(1.2, 3.4) - println("Overridden toString(): " + c.toString) - } - } +{% tabs class-inheritance-toString-demo class=tabs-scala-version %} -## Case Classes and Pattern Matching +{% tab 'Scala 2' for=class-inheritance-toString-demo %} +```scala +object ComplexNumbers { + def main(args: Array[String]): Unit = { + val c = new Complex(1.2, 3.4) + println("Overridden toString(): " + c.toString) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=class-inheritance-toString-demo %} +```scala +@main def ComplexNumbers: Unit = + val c = Complex(1.2, 3.4) + println("Overridden toString(): " + c.toString) +``` +{% endtab %} + +{% endtabs %} + + + +## Algebraic Data Types 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 +internally as trees; JSON payloads 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 @@ -351,29 +644,39 @@ 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 +In Java, before the introduction of records, 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 +data-type (ADT) for the same purpose. + +{% tabs algebraic-data-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=algebraic-data-demo %} +Scala 2 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 +```scala +abstract class Tree +object Tree { + case class Sum(left: Tree, right: 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)`), + these classes (i.e., one can write `Tree.Const(5)` instead of + `new Tree.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`), + `Tree.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, @@ -382,6 +685,35 @@ in several respects: `x+1` prints as `Sum(Var(x),Const(1))`), - instances of these classes can be decomposed through *pattern matching* as we will see below. +{% endtab %} + +{% tab 'Scala 3' for=algebraic-data-demo %} +Scala 3 provides the concept of *enums* which can be used like Java's enum, +but also to implement ADTs. Here is how they can be used to define the type +of the trees for our example: +```scala +enum Tree: + case Sum(left: Tree, right: Tree) + case Var(n: String) + case Const(v: Int) +``` +The cases of the enum `Sum`, `Var` and `Const` are similar to standard classes, +but differ in several respects: +- 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 case + `Tree.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 enum cases can be decomposed through + *pattern matching* as we will see below. +{% endtab %} + +{% endtabs %} Now that we have defined the data-type to represent our arithmetic expressions, we can start defining operations to manipulate them. We @@ -395,50 +727,69 @@ 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 +environment `{ x -> 5 }` given above can be written as follows in Scala: - { case "x" => 5 } +{% tabs env-definition %} +{% tab 'Scala 2 and 3' for=env-definition %} +```scala +type Environment = String => Int +val ev: Environment = { case "x" => 5 } +``` +{% endtab %} +{% endtabs %} 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: +Above we defined a _type alias_ called `Environment` which is more +readable than the plain function type `String => Int`, and makes +future changes easier. + +We can now give the definition of the evaluation function. Here is +a brief specification: the value of a `Sum` is the addition of the +evaluations of its two inner expressions; the value of a `Var` is obtained +by lookup of its inner name in the environment; and the value of a +`Const` is its inner value itself. This specification translates exactly into +Scala as follows, using a pattern match on a tree value `t`: - type Environment = String => Int +{% tabs patt-match-demo class=tabs-scala-version %} -From then on, the type `Environment` can be used as an alias of -the type of functions from `String` to `Int`. +{% tab 'Scala 2' for=patt-match-demo %} +```scala +import Tree._ -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, ev: Environment): Int = t match { + case Sum(left, right) => eval(left, ev) + eval(right, ev) + case Var(n) => ev(n) + case Const(v) => v +} +``` +{% endtab %} - 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 - } +{% tab 'Scala 3' for=patt-match-demo %} +```scala +import Tree.* -This evaluation function works by performing *pattern matching* -on the tree `t`. Intuitively, the meaning of the above definition -should be clear: +def eval(t: Tree, ev: Environment): Int = t match + case Sum(left, right) => eval(left, ev) + eval(right, ev) + case Var(n) => ev(n) + case Const(v) => v +``` +{% endtab %} + +{% endtabs %} + +You can understand the precise meaning of the pattern match as follows: 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 + is, it binds the left sub-tree to a new variable called `left` and + the right sub-tree to a variable called `right`, 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`, + pattern appearing on the left of the arrow, i.e., `left` and + `right`, 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 @@ -456,23 +807,30 @@ 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`, +### Comparison to OOP + +A programmer familiar with the object-oriented paradigm +might wonder why define a single function for `eval` outside +the scope of `Tree`, and not make `eval` an abstract method in +`Tree`, providing overrides in each subclass of `Tree`. + +We could have done it actually, it is a choice to make, which has +important implications on extensibility: + +- when using method overriding, adding a new operation to + manipulate the tree implies far-reaching changes to the code, + as it requires to add the method in all sub-classes of `Tree`, + however, adding a new subclass only requires implementing the + operations in one place. This design favours a few core operations + and many growing subclasses, - 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. + the other hand, adding a new operation only requires defining the function + in one place. If your data structure has a stable set of nodes, + it favours the ADT and pattern matching design. + +### Adding a New Operation To explore pattern matching further, let us define another operation on arithmetic expressions: symbolic derivation. The reader might @@ -487,11 +845,32 @@ remember the following rules regarding this operation: 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) - } +{% tabs derivation-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=derivation-demo %} +```scala +import Tree._ + +def derive(t: Tree, v: String): Tree = t match { + case Sum(left, right) => Sum(derive(left, v), derive(right, v)) + case Var(n) if v == n => Const(1) + case _ => Const(0) +} +``` +{% endtab %} + +{% tab 'Scala 3' for=derivation-demo %} +```scala +import Tree.* + +def derive(t: Tree, v: String): Tree = t match + case Sum(left, right) => Sum(derive(left, v), derive(right, v)) + case Var(n) if v == n => Const(1) + case _ => Const(0) +``` +{% endtab %} + +{% endtabs %} This function introduces two new concepts related to pattern matching. First of all, the `case` expression for variables has a @@ -511,25 +890,54 @@ 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]): Unit = { - 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")) - } - -You will need to wrap the `Environment` type and `eval`, `derive`, and -`main` methods in a `Calc` object before compiling. 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))) +{% tabs calc-main class=tabs-scala-version %} + +{% tab 'Scala 2' for=calc-main %} +```scala +import Tree._ + +object Calc { + type Environment = String => Int + def eval(t: Tree, ev: Environment): Int = ... + def derive(t: Tree, v: String): Tree = ... + + def main(args: Array[String]): Unit = { + 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")) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=calc-main %} +```scala +import Tree.* + +@main def Calc: Unit = + 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")) +``` +{% endtab %} + +{% endtabs %} + +Executing this program, we should get the following 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 @@ -565,12 +973,30 @@ 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) - } +{% tabs ord-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=ord-definition %} +```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) +} +``` +{% endtab %} + +{% tab 'Scala 3' for=ord-definition %} +```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) +``` +{% endtab %} + +{% endtabs %} This definition both creates a new type called `Ord`, which plays the same role as Java's `Comparable` interface, and @@ -591,11 +1017,35 @@ 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 = s"$year-$month-$day" +{% tabs date-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=date-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + override def toString(): String = s"$year-$month-$day" + + // rest of implementation will go here +} +``` +{% endtab %} + +{% tab 'Scala 3' for=date-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord: + def year = y + def month = m + def day = d + override def toString(): String = s"$year-$month-$day" + + // rest of implementation will go here +end Date +``` +{% endtab %} + +{% endtabs %} The important part here is the `extends Ord` declaration which follows the class name and parameters. It declares that the @@ -604,37 +1054,87 @@ follows the class name and parameters. It declares that the 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 +usable, because as in Java it compares objects by their identity. 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 method, +{% tabs equals-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=equals-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord { + // previous decls here + + override def equals(that: Any): Boolean = that match { + case d: Date => d.day == day && d.month == month && d.year == year + case _ => false + } + + // rest of implementation will go here +} +``` +{% endtab %} + +{% tab 'Scala 3' for=equals-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord: + // previous decls here + + override def equals(that: Any): Boolean = that match + case d: Date => d.day == day && d.month == month && d.year == year + case _ => false + + // rest of implementation will go here +end Date +``` +{% endtab %} + +{% endtabs %} + +While in Java (pre 16) you might use the `instanceof` operator followed by a cast +(equivalent to calling `that.isInstanceOf[Date]` and `that.asInstanceOf[Date]` in Scala); +in Scala it is more idiomatic to use a _type pattern_, shown in the example above which checks if `that` is an +instance of `Date`, and binds it to a new variable `d`, which is then used in the right hand side of the `case`. + +Finally, the last method to define is the `<` test, as follows. It makes use of another method, `error` from the package object `scala.sys`, which throws an exception with the given error message. - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - sys.error("cannot compare " + that + " and a Date") +{% tabs lt-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=lt-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord { + // previous decls here + + def <(that: Any): Boolean = that match { + case d: Date => + (year < d.year) || + (year == d.year && (month < d.month || + (month == d.month && day < d.day))) + + case _ => sys.error("cannot compare " + that + " and a Date") + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=lt-definition %} +```scala +class Date(y: Int, m: Int, d: Int) extends Ord: + // previous decls here + + def <(that: Any): Boolean = that match + case d: Date => + (year < d.year) || + (year == d.year && (month < d.month || + (month == d.month && day < d.day))) + + case _ => sys.error("cannot compare " + that + " and a Date") + end < +end Date +``` +{% endtab %} - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } +{% endtabs %} This completes the definition of the `Date` class. Instances of this class can be seen either as dates or as comparable objects. @@ -650,11 +1150,7 @@ 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 +genericity. 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 @@ -673,12 +1169,16 @@ 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): Unit = { contents = value } - def get: T = contents - } +{% tabs reference-definition class=tabs-scala-version %} +{% tab 'Scala 2' for=reference-definition %} +```scala +class Reference[T] { + private var contents: T = _ + def set(value: T): Unit = { 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 @@ -687,22 +1187,64 @@ 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, +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. +{% endtab %} + +{% tab 'Scala 3' for=reference-definition %} +```scala +import compiletime.uninitialized + +class Reference[T]: + private var contents: T = uninitialized + def set(value: T): Unit = 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 `uninitialized`, 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. +{% endtab %} + +{% endtabs %} 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]): Unit = { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } +{% tabs reference-usage class=tabs-scala-version %} + +{% tab 'Scala 2' for=reference-usage %} +```scala +object IntegerReference { + def main(args: Array[String]): Unit = { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=reference-usage %} +```scala +@main def IntegerReference: Unit = + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) +``` +{% endtab %} + +{% endtabs %} 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 @@ -716,3 +1258,7 @@ presented some basic examples. The interested reader can go on, for example, by reading the *[Tour of Scala](https://docs.scala-lang.org/tour/tour-of-scala.html)*, which contains more explanations and examples, and consult the *Scala Language Specification* when needed. + +[scala-book]: {% link _overviews/scala3-book/introduction.md %} +[maven-setup]: {% link _overviews/tutorials/scala-with-maven.md %} +[sbt-setup]: {% link _overviews/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md %}#adding-a-dependency diff --git a/_overviews/tutorials/scala-on-android.md b/_overviews/tutorials/scala-on-android.md index a6299e6bb3..370eb72b39 100644 --- a/_overviews/tutorials/scala-on-android.md +++ b/_overviews/tutorials/scala-on-android.md @@ -47,7 +47,7 @@ gu install native-image You will need `adb`, “Android Debug Bridge”, to connect to your Android device and install the app on it. [Here you can find more on how to do it](https://www.fosslinux.com/25170/how-to-install-and-setup-adb-tools-on-linux.htm). -Make sure your `gcc` is at least version 6. [You can try following these steps](https://tuxamito.com/wiki/index.php/Installing_newer_GCC_versions_in_Ubuntu). On top of that, you will need some specific C libraries (like GTK) to build the native image and it varies from one computer to another, so I can’t tell you exactly what to do. But it shouldn’t be a big problem. Just follow error messages saying that you lack something and google how to install them. In my case this was the list: +Make sure your `gcc` is at least version 6. [You can try following these steps](https://tuxamito.com/wiki/index.php/Installing_newer_GCC_versions_in_Ubuntu). On top of that, you will need some specific C libraries (like GTK) to build the native image, and it varies from one computer to another, so I can’t tell you exactly what to do. But it shouldn’t be a big problem. Just follow error messages saying that you lack something and google how to install them. In my case this was the list: ``` libasound2-dev (for pkgConfig alsa) @@ -68,23 +68,23 @@ Make sure your `gcc` is at least version 6. [You can try following these steps]( #### The example app -if you reached this point and everything seems to work, it means you probably should be able to compile and run the example app called [HelloScala](https://github.com/makingthematrix/scalaonandroid/tree/main/helloscala). HelloScala is based on [HelloGluon](https://github.com/gluonhq/gluon-samples/tree/master/HelloGluon) from [Gluon samples](https://github.com/gluonhq/gluon-samples). Gluon is a company that maintains JavaFX and provides libraries that give us a layer of abstraction between our code and the device — be it desktop, Android, or iOS. It has some interesting implications: for example, you will see in the code that we check if we are on the desktop instead of Android, because if yes then we need to provide window size for our app. If we are on Android, we can just let the app’s window take the whole screen. If you decide to write something more complex with this tech stack, you will quickly see that you can use Gluon’s libraries and JavaFX (maybe together with [ScalaFX](http://www.scalafx.org/)) to achieve the same results other developers get by tinkering with Android SDK, while you are writing code that can be easily re-used on other platforms as well. +if you reached this point and everything seems to work, it means you probably should be able to compile and run the example app called [HelloScala](https://github.com/makingthematrix/scalaonandroid/tree/main/helloscala). HelloScala is based on [HelloGluon](https://github.com/gluonhq/gluon-samples/tree/master/HelloGluon) from [Gluon samples](https://github.com/gluonhq/gluon-samples). Gluon is a company that maintains JavaFX and provides libraries that give us a layer of abstraction between our code and the device — be it desktop, Android, or iOS. It has some interesting implications: for example, you will see in the code that we check if we are on the desktop instead of Android, because if yes then we need to provide window size for our app. If we are on Android, we can just let the app’s window take the whole screen. If you decide to write something more complex with this tech stack, you will quickly see that you can use Gluon’s libraries and JavaFX (maybe together with [ScalaFX](https://scalafx.github.io/)) to achieve the same results other developers get by tinkering with Android SDK, while you are writing code that can be easily re-used on other platforms as well. In the `pom.xml` of HelloScala you will find a list of plugins and dependencies our example app uses. Let’s take a look at some of them. - We will use Java 16 and Scala 2.13. - [A tiny Scala library](https://mvnrepository.com/artifact/org.scalameta/svm-subs) which resolves [this problem](https://github.com/scala/bug/issues/11634) in the interaction between Scala 2.13 and GraalVM Native Image. - For the GUI we will use JavaFX 16. -- We will use two Gluon libraries: [Glisten](https://docs.gluonhq.com/charm/javadoc/6.0.6/com.gluonhq.charm.glisten/module-summary.html) and [Attach](https://gluonhq.com/products/mobile/attach/). Glisten enriches JavaFX with additional functionality specifically designed for mobile applications. Attach is an abstraction layer over the underlying platform. For us it means we should be able to use it to access everything on Android from the local storage to permissions to push notifications. +- We will use two Gluon libraries: [Glisten](https://docs.gluonhq.com/charm/javadoc/6.0.6/com.gluonhq.charm.glisten/module-summary.html) and [Attach](https://gluonhq.com/products/mobile/attach/). Glisten enriches JavaFX with additional functionality specifically designed for mobile applications. Attach is an abstraction layer over the underlying platform. For us, it means we should be able to use it to access everything on Android from the local storage to permissions to push notifications. - [scala-maven-plugin](https://github.com/davidB/scala-maven-plugin) lets us use Scala in Maven builds *(well, d’oh)*. Thank you, David! -- [gluonfx-maven-plugin](https://github.com/gluonhq/gluonfx-maven-plugin) lets us compile Gluon dependencies and JavaFX code into a native image. In its configuration you will find the `attachList` with Gluon Attach modules we need: `device`, `display`, `storage`, `util`, `statusbar`, and `lifecycle`. From those we will use directly only `display` (to set the dimensions of the app's windows in case we run the app on a desktop and not in the fullscreen mode on a mobile) and `util` (to check if we run the app on a desktop or a mobile), but the others are needed by these two and by Gluon Glisten. +- [gluonfx-maven-plugin](https://github.com/gluonhq/gluonfx-maven-plugin) lets us compile Gluon dependencies and JavaFX code into a native image. In its configuration you will find the `attachList` with Gluon Attach modules we need: `device`, `display`, `storage`, `util`, `statusbar`, and `lifecycle`. From those we will use directly only `display` (to set the dimensions of the app's windows in case we run the app on a desktop and not in the full-screen mode on a mobile) and `util` (to check if we run the app on a desktop or a mobile), but the others are needed by these two and by Gluon Glisten. - [javafx-maven-plugin](https://github.com/openjfx/javafx-maven-plugin) which is a requirement for gluonfx-maven-plugin. ### The code -[HelloScala](https://github.com/makingthematrix/scalaonandroid/tree/main/helloscala) is just a simple example app — the actual Scala code only sets up a few widgets and displays them. The [`Main`](https://github.com/makingthematrix/scalaonandroid/blob/main/hellogluon/src/main/scala/hellogluon/Main.scala) class extends `MobileApplication` from the Glisten library and then construct the main view programatically, in two methods: `init()` for creating the widgets, and `postInit(Scene)` for decorating them. Since we want to test the app on our laptop before we install it on a mobile, we use `postInit` also to check on which platform the app is being run, and if it's a desktop, we set the dimensions on the app's window. In the case of a mobile it's not necessary — our app will take the whole available space on the screen. +[HelloScala](https://github.com/makingthematrix/scalaonandroid/tree/main/helloscala) is just a simple example app — the actual Scala code only sets up a few widgets and displays them. The [`Main`](https://github.com/makingthematrix/scalaonandroid/blob/main/helloscala/src/main/scala/helloscala/Main.scala) class extends `MobileApplication` from the Glisten library and then construct the main view programmatically, in two methods: `init()` for creating the widgets, and `postInit(Scene)` for decorating them. Since we want to test the app on our laptop before we install it on a mobile, we use `postInit` also to check on which platform the app is being run, and if it's a desktop, we set the dimensions on the app's window. In the case of a mobile it's not necessary — our app will take the whole available space on the screen. -Another way to set up and display widgets in JavaFX is to use a WYSIWYG editor called [Scene Builder](https://gluonhq.com/products/scene-builder/) which generates FXML files, a version of XML, that you can then load into your app. You can see how it is done in another example: [HelloFXML](https://github.com/makingthematrix/scalaonandroid/tree/main/HelloFXML). For more complex applications, you will probably mix those two approaches: FXML for more-or-less static views and programatically set up widgets in places where the UI within one view changes in reaction to events (think, for example, of a scrollable list of incoming messages). +Another way to set up and display widgets in JavaFX is to use a WYSIWYG editor called [Scene Builder](https://gluonhq.com/products/scene-builder/) which generates FXML files, a version of XML, that you can then load into your app. You can see how it is done in another example: [HelloFXML](https://github.com/makingthematrix/scalaonandroid/tree/main/HelloFXML). For more complex applications, you will probably mix those two approaches: FXML for more-or-less static views and programmatically set up widgets in places where the UI within one view changes in reaction to events (think, for example, of a scrollable list of incoming messages). ### How to run the app @@ -106,7 +106,7 @@ After all, we work on a cross-platform solution here. Unless you want to test fe mvn -Pandroid gluonfx:build gluonfx:package ``` -Successful execution of this command will create an APK file in the` target/client/aarch64-android/gvm` directory. Connect your Android phone to the computer with an USB cable, give the computer permission to send files to the phone, and type `adb devices` to check if your phone is recognized. It should display something like this in the console: +Successful execution of this command will create an APK file in the` target/client/aarch64-android/gvm` directory. Connect your Android phone to the computer with a USB cable, give the computer permission to send files to the phone, and type `adb devices` to check if your phone is recognized. It should display something like this in the console: ``` > adb devices @@ -118,15 +118,13 @@ Now you should be able to install the app on the connected device with `adb inst Installation might not work for a number of reasons, one of the most popular being that your Android simply does not allow installing apps this way. Go to Settings, find “Developers options”, and there enable “USB debugging” and “Install via USB”. -If everything works and you see the app’s screen on your device, type `adb logcat | grep GraalCompiled` to see the log output. Now you can click the button with the magnifying glass icon on the app’s screen and you should see `"log something from Scala"` printed to the console. Of course, before you write a more complex app, please look into plugins in the IDE of your choice that can display logs from `adb logcat` in a better way. For example +If everything works, and you see the app’s screen on your device, type `adb logcat | grep GraalCompiled` to see the log output. Now you can click the button with the magnifying glass icon on the app’s screen, and you should see `"log something from Scala"` printed to the console. Of course, before you write a more complex app, please look into plugins in the IDE of your choice that can display logs from `adb logcat` in a better way. For example * [Logcat in Android Studio](https://developer.android.com/studio/debug/am-logcat) * [Log Viewer for Android Studio and IntelliJ](https://plugins.jetbrains.com/plugin/10015-log-viewer) * [Logcat plugin for VSCode](https://marketplace.visualstudio.com/items?itemName=abhiagr.logcat) -Here's a [screenshot](https://github.com/makingthematrix/scalaonandroid/blob/main/helloscala/helloscala.png) of what the app looks like when you open it. - -And [from here](https://drive.google.com/file/d/1SV5waXjyEYSSlo-mfj28WF34mcMFxoG7/view?usp=sharing) you can download the already compiled APK. +Here's a [screenshot](https://github.com/makingthematrix/scalaonandroid/blob/main/helloscala/helloscala.png) of what the app looks like when you open it. ## Next Steps and Other Useful Reading @@ -138,9 +136,9 @@ If you managed to build one of the example apps and want to code something more - Install [Scene Builder](https://gluonhq.com/products/scene-builder/) and learn how to build GUI with it. Apart from the docs, you can find a lot of tutorials about it on YouTube. - Look through [Gluon’s documentation of Glisten and Attach](https://docs.gluonhq.com/) to learn how to make your app look better on a mobile device, and how to get access to your device’s features. - Download an example from [Gluon’s list of samples](https://docs.gluonhq.com/) and rewrite it to Scala. And when you do, let me know! -- Look into [ScalaFX](http://www.scalafx.org/) — a more declarative, Scala-idiomatic wrapper over JavaFX. -- Download some of the other examples from [the “Scala on Android” repository on GitHub](https://github.com/makingthematrix/scalaonandroid). Contact me, if you write an example app of your own and want me to include it. +- Look into [ScalaFX](https://scalafx.github.io/) — a more declarative, Scala-idiomatic wrapper over JavaFX. +- Download some other examples from [the “Scala on Android” repository on GitHub](https://github.com/makingthematrix/scalaonandroid). Contact me, if you write an example app of your own and want me to include it. - Join us on the official Scala discord — we have a [#scala-android channel](https://discord.gg/UuDawpq7) there. - There is also an [#android channel](https://discord.gg/XHMt6Yq4) on the “Learning Scala” discord. -- Finally, if you have any questions, [you can always find me on Twitter](https://twitter.com/makingthematrix). +- Finally, if you have any questions, [you can always find me on X](https://x.com/makingthematrix). diff --git a/_overviews/tutorials/scala-with-maven.md b/_overviews/tutorials/scala-with-maven.md index a8c6572301..0cdbfc990a 100644 --- a/_overviews/tutorials/scala-with-maven.md +++ b/_overviews/tutorials/scala-with-maven.md @@ -108,7 +108,7 @@ Example structure: Again, you can read more about the Scala Maven Plugin at its [website][22]. ### Creating a Jar -By default the jar created by the Scala Maven Plugin doesn't include a `Main-Class` attribute in the manifest. I had to add the [Maven Assembly Plugin][19] to my `pom.xml` in order to specify custom attributes in the manifest. You can check the latest version of this plugin at the [project summary][20] or at [The Central Repository][21] +By default, the jar created by the Scala Maven Plugin doesn't include a `Main-Class` attribute in the manifest. I had to add the [Maven Assembly Plugin][19] to my `pom.xml` in order to specify custom attributes in the manifest. You can check the latest version of this plugin at the [project summary][20] or at [The Central Repository][21] X.X.X @@ -163,82 +163,6 @@ After adding this, `mvn package` will also create `[artifactId]-[version]-jar-wi - `mvn clean` - `mvn package`: compile, run tests, and create jar -### Integration with Eclipse ([Scala IDE][24]) -There are instructions at the [Scala Maven Plugin FAQs][23], but I thought I'd expand a bit. The [maven-eclipse-plugin][33] is a core plugin (all plugins prefixed with "maven" are, and are available by default) to generate Eclipse configuration files. However, this plugin does not know about our new Scala source files. We'll be using the [build-helper-maven-plugin][34] to add new source folders: - - ... - - org.codehaus.mojo - build-helper-maven-plugin - - - add-source - generate-sources - - add-source - - - - src/main/scala - - - - - add-test-source - generate-sources - - add-test-source - - - - src/test/scala - - - - - - ... - -After adding that to your `pom.xml`: - -1. Run `mvn eclipse:eclipse` - this generates the Eclipse project files (which are already ignored by our archetype's `.gitignore`) -2. Run `mvn -Declipse.workspace="path/to/your/eclipse/workspace" eclipse:configure-workspace` - this adds an `M2_REPO` classpath variable to Eclipse -3. Run `mvn package` to ensure you have all the dependencies in your local Maven repo - -Unfortunately, the integration isn't perfect. Firstly, open up the generated `.classpath` file (it will be hidden by default as it's a dotfile, but it should be in your project root directory; where you ran `mvn eclipse:eclipse`). You should see something like this near the top. - - - - -Change the `*.java` to `*.scala` (or duplicate the lines and change them to `*.scala` if you also have Java sources). - -Secondly, open the `.project` eclipse file (again, in the same folder). Change `` and `` to look like this. Now Eclipse knows to use the Scala editor and it won't think that everything is a syntax error. - - - - org.scala-ide.sdt.core.scalabuilder - - - - org.scala-ide.sdt.core.scalanature - org.eclipse.jdt.core.javanature - - -Finally, in Eclipse, under "File" choose "Import..." and find your project. - -#### Using [m2eclipse-scala][35] for Eclipse integration -m2eclipse-scala is a work in progress, and their website/repository may have updated information. -It aims to ease integration between m2eclipse and Scala IDE for Eclipse. - -Under "Help -> Install New Software", enter "https://alchim31.free.fr/m2e-scala/update-site" and hit enter. -You should see "Maven Integration for Eclipse -> Maven Integration for Scala IDE". - -After it installs, go to "New -> Project -> Other" and select "Maven Project". -Search fo "scala-archetype" choose the one with the group "net.alchim31.maven". -The wizard will more or less guide you through what was done with `mvn archetype:generate` above, and you should end up with a new Scala project! - -To import an existing project, simply go to "File -> Import -> Existing Maven Project" and find the directory containing your project. - ## Adding Dependencies The first thing I do is look for "Maven" in the project page. For example, Google's [Guava] page includes [Maven Central links][28]. As you can see in the previous link, The Central Repository conveniently includes the snippet you have to add to your `pom.xml` on the left sidebar. @@ -277,7 +201,6 @@ I'm not going to explain all of Maven in this tutorial (though I hope to add mor [21]: https://search.maven.org/#search%7Cga%7C1%7Ca%3A%22maven-assembly-plugin%22 [22]: https://davidb.github.io/scala-maven-plugin [23]: https://davidb.github.io/scala-maven-plugin/faq.html -[24]: https://scala-ide.org [25]: https://scala-lang.org/api/current/index.html#scala.App [26]: https://docs.scala-lang.org/tutorials/tour/polymorphic-methods.html [27]: https://code.google.com/p/guava-libraries/ @@ -286,7 +209,5 @@ I'm not going to explain all of Maven in this tutorial (though I hope to add mor [30]: https://search.maven.org/#search%7Cga%7C1%7Cscopt [31]: https://mvnrepository.com [32]: https://docs.scala-lang.org/contribute.html -[33]: https://maven.apache.org/plugins/maven-eclipse-plugin/ [34]: https://www.mojohaus.org/build-helper-maven-plugin/ -[35]: https://github.com/sonatype/m2eclipse-scala [36]: https://github.com/scala/scala-module-dependency-sample diff --git a/_pl/tour/abstract-type-members.md b/_pl/tour/abstract-type-members.md index d47ebe86af..e79d551939 100644 --- a/_pl/tour/abstract-type-members.md +++ b/_pl/tour/abstract-type-members.md @@ -39,16 +39,14 @@ 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) -} +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]`. @@ -62,15 +60,14 @@ abstract class Buffer[+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) -} + +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](variances.html). Inaczej nie byłoby możliwe ukrycie konkretnego typu sekwencji obiektu zwracanego przez metodę `newIntSeqBuf`. diff --git a/_pl/tour/automatic-closures.md b/_pl/tour/automatic-closures.md deleted file mode 100644 index 5f28a2568a..0000000000 --- a/_pl/tour/automatic-closures.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: tour -title: Automatyczna konstrukcja domknięć -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: - -```scala mdoc -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: - -```scala mdoc -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/basics.md b/_pl/tour/basics.md index 903ecdcfe7..44ea200457 100644 --- a/_pl/tour/basics.md +++ b/_pl/tour/basics.md @@ -13,17 +13,14 @@ Na tej stronie omówimy podstawy języka Scala. ## Uruchamianie Scali w przeglądarce -Dzięki ScalaFiddle możesz uruchomić Scalę w swojej przeglądarce. +Dzięki Scastie możesz uruchomić Scalę w swojej przeglądarce. -1. Przejdź do [https://scalafiddle.io](https://scalafiddle.io). +1. Przejdź do [Scastie](https://scastie.scala-lang.org/). 2. Wklej kod `println("Hello, world!")` w polu po lewej stronie. 3. Naciśnij przycisk "Run". W panelu po prawej stronie pojawi się wynik działania programu. Jest to prosta i niewymagająca żadnej instalacji metoda do eksperymentowania z kodem w Scali. -Wiele przykładów kodu w tym przewodniku jest również zintegrowana ze ScalaFiddle, -dzięki czemu można je wypróbować wciskając po prostu przycisk "Run". - ## Wyrażenia Wyrażenia są rezultatem ewaluacji fragmentów kodu. @@ -34,14 +31,12 @@ Wyrażenia są rezultatem ewaluacji fragmentów kodu. Wyniki wyrażeń można wyświetlić za pomocą funkcji `println`. -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ### Wartości @@ -74,13 +69,11 @@ Zauważ, że deklaracja `Int` pojawia po identyfikatorze `x`, potrzebny jest ró Zmienne są podobne do wartości, ale z tym wyjątkiem, że można je ponownie przypisywać. Zmienną można zdefiniować używając słowa kluczowego `var`. -{% scalafiddle %} ```scala mdoc:nest var x = 1 + 1 x = 3 // Kompiluje się, ponieważ "x" jest zdefiniowane z użyciem "var". println(x * x) // 9 ``` -{% endscalafiddle %} Tak jak przy wartościach, można wyraźnie zdefiniować żądany typ: @@ -94,14 +87,12 @@ Wyrażenia mogą być łączone poprzez zamknięcie ich w nawiasie klamrowym`{}` Taką konstrukcję nazywamy blokiem. Wynikiem całego bloku kodu jest wynik ostatniego wyrażenia w tym bloku. -{% scalafiddle %} ```scala mdoc println({ val x = 1 + 1 x + 1 }) // 3 ``` -{% endscalafiddle %} ## Funkcje @@ -118,30 +109,24 @@ Po prawej stronie - wyrażenie wykorzystujące te parametry. Funkcje można również nazywać. -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} Funkcje mogą przyjmować wiele parametrów. -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} Mogą też wcale nie mieć parametrow. -{% scalafiddle %} ```scala mdoc val getTheAnswer = () => 42 println(getTheAnswer()) // 42 ``` -{% endscalafiddle %} ## Metody @@ -150,38 +135,31 @@ Metody wyglądają i zachowują się bardzo podobnie jak funkcje, jednak jest mi Metody są definiowane z użyciem słowa kluczowego `def`. Po `def` następuje nazwa metody, lista parametrów, zwracany typ i ciało metody. -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} Zauważ, że zwracany typ jest zadeklarowany _po_ liście parametrów i dwukropku `: Int`. Metody mogą mieć wiele list parametrów. -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} Mogą również wcale ich nie posiadać. -{% scalafiddle %} ```scala mdoc def name: String = System.getProperty("user.name") println("Hello, " + name + "!") ``` -{% endscalafiddle %} Od funkcji odróżnia je jeszcze kilka innych rzeczy, ale na razie możesz o nich myśleć jak o bardzo podobnych do funkcji. Metody mogą zawierać również wyrażenia wielowierszowe. -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -189,7 +167,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} Ostatnie wyrażenie w ciele metody jest wartością, jaką zwraca cała metoda. Scala posiada słowo kluczowe `return`, ale jest ono wykorzystywane bardzo rzadko. @@ -198,7 +175,6 @@ Scala posiada słowo kluczowe `return`, ale jest ono wykorzystywane bardzo rzadk Klasy są definiowane za pomocą słowa kluczowego `class`, po którym następuje nazwa klasy i parametry konstruktora. -{% scalafiddle %} ```scala mdoc class Greeter(prefix: String, suffix: String) { def greet(name: String): Unit = @@ -216,7 +192,6 @@ Nowe instancje klasy tworzy się za pomocą słowa kluczowego `new`. val greeter = new Greeter("Hello, ", "!") greeter.greet("Scala developer") // Hello, Scala developer! ``` -{% endscalafiddle %} Klasy zostaną szerzej omówione w [dalszej części](classes.html) tego przewodnika. @@ -226,7 +201,6 @@ W Scali istnieje spacjalny typ klasy - klasa "przypadku" (case class). Klasy przypadku są domyślnie niezmienne i porównywane przez wartości. Klasy te można definiować używająć słów kluczowych `case class`. -{% scalafiddle %} ```scala mdoc case class Point(x: Int, y: Int) ``` @@ -243,18 +217,17 @@ Są one porównywane przez wartości - _nie_ przez referencje. ```scala mdoc if (point == anotherPoint) { - println(point + " i " + anotherPoint + " są jednakowe.") + println(s"$point i $anotherPoint są jednakowe.") } else { - println(point + " i " + anotherPoint + " są inne.") + println(s"$point i $anotherPoint są inne.") } // Point(1,2) i Point(1,2) są jednakowe. if (point == yetAnotherPoint) { - println(point + " i " + yetAnotherPoint + " są jednakowe.") + println(s"$point i $yetAnotherPoint są jednakowe.") } else { - println(point + " i " + yetAnotherPoint + " są inne.") + println(s"$point i $yetAnotherPoint są inne.") } // Point(1,2) i Point(2,2) są inne. ``` -{% endscalafiddle %} Klasy przypadków to dużo szerszy temat, do zapoznania z którym bardzo zachęcamy. Jesteśmy pewni, że Ci się spodoba! Jest on dokładnie omówiony w [późniejszym rozdziale](case-classes.html). @@ -266,7 +239,6 @@ Można o nich myśleć jak o instancjach ich własnych klas - singletonach. Objekty definiuje się z użyciem słowa kluczowego `object`. -{% scalafiddle %} ```scala mdoc object IdFactory { private var counter = 0 @@ -285,7 +257,6 @@ println(newId) // 1 val newerId: Int = IdFactory.create() println(newerId) // 2 ``` -{% endscalafiddle %} Obiekty zostaną szerzej omówione [później](singleton-objects.html). @@ -304,7 +275,6 @@ trait Greeter { Cechy mogą zawierać domyślną implementację. -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -329,7 +299,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} W tym przykładzie `DefaultGreeter` rozszerza tylko jedną cechę (trait), ale równie dobrze może rozszerzać ich wiele. diff --git a/_pl/tour/generic-classes.md b/_pl/tour/generic-classes.md index e616453a84..ac011270b3 100644 --- a/_pl/tour/generic-classes.md +++ b/_pl/tour/generic-classes.md @@ -17,9 +17,9 @@ Poniższy przykład demonstruje zastosowanie parametrów generycznych: class Stack[T] { var elems: List[T] = Nil def push(x: T): Unit = - elems = x :: elems + elems = x :: elems def top: T = elems.head - def pop() { elems = elems.tail } + def pop(): Unit = { elems = elems.tail } } ``` diff --git a/_pl/tour/implicit-conversions.md b/_pl/tour/implicit-conversions.md index d859139ee8..950bbd1458 100644 --- a/_pl/tour/implicit-conversions.md +++ b/_pl/tour/implicit-conversions.md @@ -43,8 +43,8 @@ Przykładowo, kiedy wywołujemy metodę Javy, która wymaga typu `java.lang.Inte ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + 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/inner-classes.md b/_pl/tour/inner-classes.md index 2f3fef4ea1..a97e798d36 100644 --- a/_pl/tour/inner-classes.md +++ b/_pl/tour/inner-classes.md @@ -10,12 +10,12 @@ 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: - + ```scala mdoc class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -29,11 +29,11 @@ class Graph { } } ``` - + 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ą: - -```scala mdoc -object GraphTest extends App { + +```scala mdoc:nest +def graphTest: Unit = { val g = new Graph val n1 = g.newNode val n2 = g.newNode @@ -42,11 +42,11 @@ object GraphTest extends App { 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: - + ```scala mdoc:nest -object GraphTest extends App { +def graphTest: Unit = { val g: Graph = new Graph val n1: g.Node = g.newNode val n2: g.Node = g.newNode @@ -55,11 +55,11 @@ object GraphTest extends App { 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: - + ```scala mdoc:fail object IllegalGraphTest extends App { val g: Graph = new Graph @@ -71,14 +71,14 @@ object IllegalGraphTest extends App { 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: - + ```scala mdoc:nest class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -92,5 +92,5 @@ class Graph { } } ``` - + > 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/mixin-class-composition.md b/_pl/tour/mixin-class-composition.md index dc7ad8dc4e..9f346fd72c 100644 --- a/_pl/tour/mixin-class-composition.md +++ b/_pl/tour/mixin-class-composition.md @@ -11,7 +11,6 @@ previous-page: tuples Domieszka (ang. mixin) to cecha (trait), która używana jest do komponowania klas. -{% scalafiddle %} ```scala mdoc abstract class A { val message: String @@ -28,7 +27,6 @@ val d = new D println(d.message) // wyświetli "Jestem instancją klasy B" println(d.loudMessage) // wyświetli "JESTEM INSTANCJĄ KLASY B" ``` -{% endscalafiddle %} Klasa `D` posiada nadklasę `B` oraz domieszkę `C`. Klasy mogą mieć tylko jedną nadklasę, ale wiele domieszek (używając kolejno słów kluczowych `extends`, a następnie `with`). diff --git a/_pl/tour/multiple-parameter-lists.md b/_pl/tour/multiple-parameter-lists.md index 9bb9e100c5..ba88a323f8 100644 --- a/_pl/tour/multiple-parameter-lists.md +++ b/_pl/tour/multiple-parameter-lists.md @@ -23,13 +23,11 @@ Poniżej omówiony jest przykład użycia. Zaczynając od początkowej wartości 0, funkcja `foldLeft` stosuje funkcję `(m, n) => m + n` na każdym elemencie listy oraz poprzedniej zakumulowanej wartości. -{% scalafiddle %} ```scala mdoc val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(0)((m, n) => m + n) println(res) // 55 ``` -{% endscalafiddle %} Metody z wieloma listami parametrów mają bardziej dosadną składnię wywoływania, dlatego powinny być używane oszczędnie. Sugerowane przypadki użycia obejmują: diff --git a/_pl/tour/nested-functions.md b/_pl/tour/nested-functions.md index 1531854082..88553a6d8e 100644 --- a/_pl/tour/nested-functions.md +++ b/_pl/tour/nested-functions.md @@ -12,7 +12,6 @@ previous-page: higher-order-functions Scala pozwala na zagnieżdżanie definicji funkcji. Poniższy obiekt określa funkcję `factorial`, która oblicza silnię dla danej liczby: -{% scalafiddle %} ```scala mdoc def factorial(x: Int): Int = { def fact(x: Int, accumulator: Int): Int = { @@ -25,7 +24,6 @@ Poniższy obiekt określa funkcję `factorial`, która oblicza silnię dla dane println("Factorial of 2: " + factorial(2)) println("Factorial of 3: " + factorial(3)) ``` -{% endscalafiddle %} Wynik działania powyższego programu: diff --git a/_pl/tour/self-types.md b/_pl/tour/self-types.md index 1bbb74be98..d0098d352c 100644 --- a/_pl/tour/self-types.md +++ b/_pl/tour/self-types.md @@ -110,7 +110,7 @@ Należy dodać, że w tej klasie możemy utworzyć `NodeImpl`, ponieważ wiemy j Poniżej przykład zastosowania klasy `ConcreteDirectedGraph`: ```scala mdoc -object GraphTest extends App { +def graphTest: Unit = { val g: Graph = new ConcreteDirectedGraph val n1 = g.addNode val n2 = g.addNode diff --git a/_pl/tour/tour-of-scala.md b/_pl/tour/tour-of-scala.md index 05b70cc5de..76bdcc7091 100644 --- a/_pl/tour/tour-of-scala.md +++ b/_pl/tour/tour-of-scala.md @@ -12,7 +12,7 @@ next-page: basics Ten przewodnik składa się z drobnych wprowadzeń do najczęściej używanych funkcjonalności języka Scala. Jest to jedynie krótkie wprowadzenie, a nie pełny samouczek. -Jeżeli szukasz tego drugiego, rozważ jedną z [książek](/books.html) lub [inne zasoby](/learn.html). +Jeżeli szukasz tego drugiego, rozważ jedną z [książek](/books.html) lub [inne zasoby](/online-courses.html). ## Czym jest Scala? Scala jest nowoczesnym, wieloparadygmatowym językiem programowania zaprojektowanym do wyrażania powszechnych wzorców programistycznych w zwięzłym, eleganckim i bezpiecznie typowanym stylu. diff --git a/_pl/tour/traits.md b/_pl/tour/traits.md index 4751e0824d..b92057aa2a 100644 --- a/_pl/tour/traits.md +++ b/_pl/tour/traits.md @@ -38,7 +38,6 @@ Rozszerzenie cechy `trait Iterator[A]` wymaga wskazania parametru typu `A` oraz Aby rozszerzyć cechę należy użyć słowa kluczowego `extends`. Następnie wymagane jest zaimplementowanie abstrakcyjnych składników danej cechy używając słowa kluczowego `override.` -{% scalafiddle %} ```scala mdoc:nest trait Iterator[A] { def hasNext: Boolean @@ -61,7 +60,6 @@ val iterator = new IntIterator(10) println(iterator.next()) // wyświetli 0 println(iterator.next()) // wyświetli 1 ``` -{% endscalafiddle %} Klasa `IntIterator` przyjmuje parametr `to` (do) jako ograniczenie górne, oraz rozszerza `extends Iterator[Int]` - co oznacza, że metoda `next` musi zwrócić wartość typu Int. @@ -69,7 +67,6 @@ Klasa `IntIterator` przyjmuje parametr `to` (do) jako ograniczenie górne, oraz Jeżeli w jakimś miejscu wymagana jest cecha pewnego typu, to zamiast niej można użyć jej podtypu. -{% scalafiddle %} ```scala mdoc import scala.collection.mutable.ArrayBuffer @@ -88,7 +85,6 @@ animals.append(dog) animals.append(cat) animals.foreach(pet => println(pet.name)) // wyświetli Harry Sally ``` -{% endscalafiddle %} Cecha `trait Pet` posiada abstrakcyjne pole `name`, które zostaje zaimplementowane przez klasy `Cat` i `Dog` w ich konstruktorach. W ostatnim wierszu wywołujemy `pet.name` musi być ono zaimplementowane przez każdy podtyp cechy `Pet`. diff --git a/_pl/tour/tuples.md b/_pl/tour/tuples.md index b415c1a348..752b3a9d41 100644 --- a/_pl/tour/tuples.md +++ b/_pl/tour/tuples.md @@ -52,7 +52,6 @@ println(quantity) // wyświetli 25 Dekonstrukcja krotek może być bardzo przydatna w dopasowywaniu wzorców (ang. pattern matching) -{% scalafiddle %} ```scala mdoc val planetDistanceFromSun = List( ("Mercury", 57.9), @@ -69,11 +68,9 @@ planetDistanceFromSun.foreach { case _ => println("Zbyt daleko...") } ``` -{% endscalafiddle %} Ma ona też zastosowanie w wyrażeniach 'for'. -{% scalafiddle %} ```scala mdoc val numPairs = List((2, 5), (3, -7), (20, 56)) @@ -81,7 +78,6 @@ for ((a, b) <- numPairs) { println(a * b) } ``` -{% endscalafiddle %} Wartość `()` typu `Unit` jest koncepcyjnie taka sama jak wartość `()` typu `Tuple0`. Może być tylko jedna wartość tego typu, ponieważ nie zawiera żadnych elementów. diff --git a/_pl/tour/unified-types.md b/_pl/tour/unified-types.md index e93d2e893b..831b720bb8 100644 --- a/_pl/tour/unified-types.md +++ b/_pl/tour/unified-types.md @@ -35,7 +35,6 @@ Jeżeli Scala użyta jest w kontekście środowiska uruchomieniowego Javy, to `A Poniższy przykład pokazuje, że łańcuchy znakowe, liczby całkowite, znaki, wartości logiczne oraz funkcje są obiektami tak samo jak każdy inny obiekt: -{% scalafiddle %} ```scala mdoc val list: List[Any] = List( "Łancuch znaków", @@ -47,7 +46,6 @@ val list: List[Any] = List( list.foreach(element => println(element)) ``` -{% endscalafiddle %} Program deklaruje wartość `list` typu `List[Any]`. Lista jest zainicjowana elementami różnego typu, ale będącymi podtypami `scala.Any` - dlatego można je umieścić na tej liście. @@ -72,7 +70,7 @@ Dla przykładu: ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (w tym wypadku tracimy część precyzji) +val y: Float = x.toFloat // 9.8765434E8 (w tym wypadku tracimy część precyzji) val face: Char = '☺' val number: Int = face // 9786 @@ -82,7 +80,7 @@ Rzutowanie jest jednokierunkowe, następujący kod nie zadziała: ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // Błąd: Does not conform ``` @@ -93,11 +91,11 @@ Zostanie to dokładniej omówione w kolejnych rozdziałach. `Nothing` jest podtypem wszystkich typów, istnieje na samym dole hierarchii i jest nazywany typem dolnym (bottom type). Nie istnieje żadna wartość typu `Nothing`. -Częstym przykładem użycia jest zasygnalizowanie stanów nieoznaczonych np. wyrzucony wyjątek, wyjście z programu, -nieskończona pętla (ściślej mówiąc - jest to typ wyrażenia które nie ewaluuje na żadną wartość lub metoda, która nie zwraca wyniku). +Częstym przykładem użycia jest zasygnalizowanie stanów nieoznaczonych np. wyrzucony wyjątek, wyjście z programu, +nieskończona pętla (ściślej mówiąc - jest to typ wyrażenia które nie ewaluuje na żadną wartość lub metoda, która nie zwraca wyniku). `Null` jest podtypem wszystkich typów referencyjnych (wszystkich podtypów `AnyRef`). Ma pojedynczą wartosć identyfikowaną przez słowo kluczowe `null`. -`Null` przydaje się głównie do współpracy z innymi językami platformy JVM i nie powinien być praktycznie nigdy używany +`Null` przydaje się głównie do współpracy z innymi językami platformy JVM i nie powinien być praktycznie nigdy używany w kodzie w jęzku Scala. W dalszej części przewodnika omówimy alternatywy dla `null`. diff --git a/_plugins/alt-details-lib/alt-details.rb b/_plugins/alt-details-lib/alt-details.rb new file mode 100644 index 0000000000..f7bf5058a6 --- /dev/null +++ b/_plugins/alt-details-lib/alt-details.rb @@ -0,0 +1,44 @@ +require 'erb' + +module Jekyll + module AltDetails + class AltDetailsBlock < Liquid::Block + SYNTAX = /^\s*(#{Liquid::QuotedFragment})\s+(#{Liquid::QuotedFragment})(?=\s+class=(#{Liquid::QuotedFragment}))?/o + Syntax = SYNTAX + + alias_method :render_block, :render + + def unquote(string) + string.gsub(/^['"]|['"]$/, '') + end + + def initialize(block_name, markup, tokens) + super + + if markup =~ SYNTAX + @name = unquote($1) + @title = unquote($2) + @css_classes = "" + if $3 + # append $3 to @css_classes + @css_classes = "#{@css_classes} #{unquote($3)}" + end + else + raise SyntaxError.new("Block #{block_name} requires an id and a title") + end + end + + def render(context) + site = context.registers[:site] + converter = site.find_converter_instance(::Jekyll::Converters::Markdown) + altDetailsContent = converter.convert(render_block(context)) + currentDirectory = File.dirname(__FILE__) + templateFile = File.read(currentDirectory + '/template.erb') + template = ERB.new(templateFile) + template.result(binding) + end + end + end +end + +Liquid::Template.register_tag('altDetails', Jekyll::AltDetails::AltDetailsBlock) diff --git a/_plugins/alt-details-lib/alt-details/version.rb b/_plugins/alt-details-lib/alt-details/version.rb new file mode 100644 index 0000000000..147e33df9c --- /dev/null +++ b/_plugins/alt-details-lib/alt-details/version.rb @@ -0,0 +1,5 @@ +module Jekyll + module AltDetails + VERSION = "1.1.1" + end +end diff --git a/_plugins/alt-details-lib/template.erb b/_plugins/alt-details-lib/template.erb new file mode 100644 index 0000000000..95d65eb3ef --- /dev/null +++ b/_plugins/alt-details-lib/template.erb @@ -0,0 +1,11 @@ +
+
+ + +
+
+ <%= altDetailsContent %> +
+
+
+
diff --git a/_plugins/alt_details.rb b/_plugins/alt_details.rb new file mode 100644 index 0000000000..57d2420105 --- /dev/null +++ b/_plugins/alt_details.rb @@ -0,0 +1 @@ +require_relative "alt-details-lib/alt-details" diff --git a/_plugins/jekyll-tabs-lib/LICENCE b/_plugins/jekyll-tabs-lib/LICENCE new file mode 100644 index 0000000000..43ae840e30 --- /dev/null +++ b/_plugins/jekyll-tabs-lib/LICENCE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 Baptiste Bouchereau +Copyright (c) 2022 LAMP/EPFL + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/_plugins/jekyll-tabs-lib/README.md b/_plugins/jekyll-tabs-lib/README.md new file mode 100644 index 0000000000..4e84712723 --- /dev/null +++ b/_plugins/jekyll-tabs-lib/README.md @@ -0,0 +1,8 @@ +originally sourced from https://github.com/Ovski4/jekyll-tabs. + +changes: +- template.erb adapted to match the pre-existing tab html structure for docs.scala-lang.org +- `tabs` do not use secure random uuid as the id, the `tabs` name parameter is used instead. +- for the `tabs` block, add an optional second `class='foo bar'` parameter to allow custom classes for the tabs. +- for the `tab` block, reorder the parameters: the tab label comes first, followed by the name of the parent `tabs`. +- addition of a third `defaultTab` attribute for `tab`, which defaults to the final tab if not set. diff --git a/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb new file mode 100644 index 0000000000..b592fcc7f9 --- /dev/null +++ b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb @@ -0,0 +1,189 @@ +require 'erb' + +module Jekyll + module Tabs + + ScalaVersions = ['Scala 2', 'Scala 3'] + BuildTools = ['Scala CLI', 'sbt', 'Mill'] + + def self.unquote(string) + string.gsub(/^['"]|['"]$/, '') + end + + def self.asAnchor(title) + title.gsub(/[^a-zA-Z0-9\-_]/, '-').gsub(/-{2,}/, '-').downcase + end + + TabDetails = Struct.new(:label, :anchor, :defaultTab, :content, keyword_init: true) + + class TabsBlock < Liquid::Block + SYNTAX = /^\s*(#{Liquid::QuotedFragment})(?=\s+class=(#{Liquid::QuotedFragment}))?/o + Syntax = SYNTAX + + def initialize(block_name, markup, tokens) + super + + if markup =~ SYNTAX + @name = Tabs::unquote($1) + @css_classes = "" + @tab_class = "" + if $2 + css_class = Tabs::unquote($2) + css_class.strip! + @tab_class = css_class + # append $2 to @css_classes + @css_classes = " #{css_class}" + end + else + raise SyntaxError.new("Block #{block_name} requires 1 attribute") + end + end + + def render(context) + environment = context.environments.first + environment["tabs-#{@name}"] = [] # reset every time (so page translations can use the same name) + if environment["CURRENT_TABS_ENV"].nil? + environment["CURRENT_TABS_ENV"] = @name + else + raise SyntaxError.new("Nested tabs are not supported") + end + super # super call renders the internal content + environment["CURRENT_TABS_ENV"] = nil # reset after rendering + foundDefault = false + + allTabs = environment["tabs-#{@name}"] + + seenTabs = [] + + def joinTabs(tabs) + tabs.to_a.map{|item| "'#{item}'"}.join(", ") + end + + def errorInvalidTab(tab, expectedTabs) + SyntaxError.new( + "Tab label '#{tab.label}' is not valid for tabs '#{@name}' with " + + "class=#{@tab_class}. Valid tab labels are: #{joinTabs(expectedTabs)}") + end + + def errorTabWithoutClass(tab, tabClass) + SyntaxError.new( + "Tab label '#{tab.label}' is not valid for tabs '#{@name}' without " + + "class=#{tabClass}") + end + + def errorMissingTab(expectedTabs) + SyntaxError.new( + "Tabs '#{@name}' with class=#{@tab_class} must have exactly the following " + + "tab labels: #{joinTabs(expectedTabs)}") + end + + def errorDuplicateTab(tab) + SyntaxError.new("Duplicate tab label '#{tab.label}' in tabs '#{@name}'") + end + + def errorTabDefault(tab) + SyntaxError.new( + "Tab label '#{tab.label}' should not be default for tabs '#{@name}' " + + "with class=#{@tab_class}") + end + + allTabs.each do | tab | + if seenTabs.include? tab.label + raise errorDuplicateTab(tab) + end + seenTabs.push tab.label + if tab.defaultTab + foundDefault = true + end + + def checkTab(tab, tabClass, expectedTabs, raiseIfMissingClass) + isValid = expectedTabs.include? tab.label + if @tab_class == tabClass + if !isValid + raise errorInvalidTab(tab, expectedTabs) + elsif tab.defaultTab + raise errorTabDefault(tab) + end + elsif raiseIfMissingClass and isValid + raise errorTabWithoutClass(tab, tabClass) + end + end + + checkTab(tab, "tabs-scala-version", Tabs::ScalaVersions, true) + checkTab(tab, "tabs-build-tool", Tabs::BuildTools, false) + end + + def checkExhaustivity(seenTabs, tabClass, expectedTabs) + if @tab_class == tabClass and seenTabs != expectedTabs + raise errorMissingTab(expectedTabs) + end + end + + checkExhaustivity(seenTabs, "tabs-scala-version", Tabs::ScalaVersions) + checkExhaustivity(seenTabs, "tabs-build-tool", Tabs::BuildTools) + + if !foundDefault and allTabs.length > 0 + if @tab_class == "tabs-scala-version" + # set last tab to default ('Scala 3') + allTabs[-1].defaultTab = true + else + # set first tab to default + allTabs[0].defaultTab = true + end + end + + currentDirectory = File.dirname(__FILE__) + templateFile = File.read(currentDirectory + '/template.erb') + template = ERB.new(templateFile) + template.result(binding) + end + end + + class TabBlock < Liquid::Block + alias_method :render_block, :render + + SYNTAX = /^\s*(#{Liquid::QuotedFragment})\s+(?:for=(#{Liquid::QuotedFragment}))?(?:\s+(defaultTab))?/o + Syntax = SYNTAX + + def initialize(block_name, markup, tokens) + super + + if markup =~ SYNTAX + @tab = Tabs::unquote($1) + if $2 + @name = Tabs::unquote($2) + end + @anchor = Tabs::asAnchor(@tab) + if $3 + @defaultTab = true + end + else + raise SyntaxError.new("Block #{block_name} requires at least 2 attributes") + end + end + + def render(context) + site = context.registers[:site] + mdoc_converter = site.find_converter_instance(::Jekyll::MdocConverter) + converter = site.find_converter_instance(::Jekyll::Converters::Markdown) + pre_content = mdoc_converter.convert(render_block(context)) + content = converter.convert(pre_content) + tabcontent = TabDetails.new(label: @tab, anchor: @anchor, defaultTab: @defaultTab, content: content) + environment = context.environments.first + tab_env = environment["CURRENT_TABS_ENV"] + if tab_env.nil? + raise SyntaxError.new("Tab block '#{tabcontent.label}' must be inside a tabs block") + end + if !@name.nil? && tab_env != @name + raise SyntaxError.new( + "Tab block '#{@tab}' for=#{@name} does not match its enclosing tabs block #{tab_env}") + end + environment["tabs-#{tab_env}"] ||= [] + environment["tabs-#{tab_env}"] << tabcontent + end + end + end +end + +Liquid::Template.register_tag('tab', Jekyll::Tabs::TabBlock) +Liquid::Template.register_tag('tabs', Jekyll::Tabs::TabsBlock) diff --git a/_plugins/jekyll-tabs-lib/jekyll-tabs/version.rb b/_plugins/jekyll-tabs-lib/jekyll-tabs/version.rb new file mode 100644 index 0000000000..498949a13d --- /dev/null +++ b/_plugins/jekyll-tabs-lib/jekyll-tabs/version.rb @@ -0,0 +1,5 @@ +module Jekyll + module Tabs + VERSION = "1.1.1" + end +end diff --git a/_plugins/jekyll-tabs-lib/template.erb b/_plugins/jekyll-tabs-lib/template.erb new file mode 100644 index 0000000000..e9d7867102 --- /dev/null +++ b/_plugins/jekyll-tabs-lib/template.erb @@ -0,0 +1,29 @@ +
+
+ <% environment["tabs-#{@name}"].each do | tab | %> + > + <% end %> + +
+ <% environment["tabs-#{@name}"].each do | tab | %> +
+
+ <%= tab.content %> +
+
+ <% end %> +
+
+
diff --git a/_plugins/jekyll_tabs_4scala.rb b/_plugins/jekyll_tabs_4scala.rb new file mode 100644 index 0000000000..7470d622a7 --- /dev/null +++ b/_plugins/jekyll_tabs_4scala.rb @@ -0,0 +1 @@ +require_relative "jekyll-tabs-lib/jekyll-tabs-4scala" diff --git a/_plugins/mdoc_replace.rb b/_plugins/mdoc_replace.rb index 0b62828227..7cecf73aa6 100644 --- a/_plugins/mdoc_replace.rb +++ b/_plugins/mdoc_replace.rb @@ -12,9 +12,11 @@ def output_ext(ext) end def convert(content) + content = content.gsub("```scala mdoc:compile-only\n", "```scala\n") content = content.gsub("```scala mdoc:fail\n", "```scala\n") content = content.gsub("```scala mdoc:crash\n", "```scala\n") content = content.gsub("```scala mdoc:nest\n", "```scala\n") + content = content.gsub("```scala mdoc:reset:crash\n", "```scala\n") content = content.gsub("```scala mdoc:reset\n", "```scala\n") content.gsub("```scala mdoc\n", "```scala\n") end diff --git a/_pt-br/tour/abstract-type-members.md b/_pt-br/tour/abstract-type-members.md index 8a798f4468..e8dba1011c 100644 --- a/_pt-br/tour/abstract-type-members.md +++ b/_pt-br/tour/abstract-type-members.md @@ -39,16 +39,14 @@ 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) -} +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]`. diff --git a/_pt-br/tour/annotations.md b/_pt-br/tour/annotations.md index 5d0842e42c..55d49cc441 100644 --- a/_pt-br/tour/annotations.md +++ b/_pt-br/tour/annotations.md @@ -5,7 +5,7 @@ partof: scala-tour num: 31 next-page: packages-and-imports -previous-page: automatic-closures +previous-page: operators language: pt-br --- diff --git a/_pt-br/tour/automatic-closures.md b/_pt-br/tour/automatic-closures.md deleted file mode 100644 index 2f9072a59d..0000000000 --- a/_pt-br/tour/automatic-closures.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -layout: tour -title: Construção Automática de Closures de Tipo-Dependente -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: - -```scala mdoc -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: - -```scala mdoc -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/classes.md b/_pt-br/tour/classes.md index 26a49fc55e..4683128312 100644 --- a/_pt-br/tour/classes.md +++ b/_pt-br/tour/classes.md @@ -33,7 +33,7 @@ Classes são instânciadas com a primitiva `new`, por exemplo: ```scala mdoc object Classes { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val pt = new Ponto(1, 2) println(pt) pt.move(10, 10) diff --git a/_pt-br/tour/generic-classes.md b/_pt-br/tour/generic-classes.md index ac8e2de5f6..b81c21c1a3 100644 --- a/_pt-br/tour/generic-classes.md +++ b/_pt-br/tour/generic-classes.md @@ -18,7 +18,7 @@ class Stack[T] { def push(x: T): Unit = elems = x :: elems def top: T = elems.head - def pop() { elems = elems.tail } + def pop(): Unit = { elems = elems.tail } } ``` diff --git a/_pt-br/tour/higher-order-functions.md b/_pt-br/tour/higher-order-functions.md index f71baf9e67..d2549f72db 100644 --- a/_pt-br/tour/higher-order-functions.md +++ b/_pt-br/tour/higher-order-functions.md @@ -25,7 +25,7 @@ class Decorator(left: String, right: String) { } object FunTest extends App { - override def apply(f: Int => String, v: Int) = f(v) + def apply(f: Int => String, v: Int) = f(v) val decorator = new Decorator("[", "]") println(apply(decorator.layout, 7)) } diff --git a/_pt-br/tour/implicit-conversions.md b/_pt-br/tour/implicit-conversions.md index cc96f99062..61ee6cac54 100644 --- a/_pt-br/tour/implicit-conversions.md +++ b/_pt-br/tour/implicit-conversions.md @@ -44,8 +44,8 @@ Por exemplo, ao chamar um método Java que espera um `java.lang.Integer`, você ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + 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/inner-classes.md b/_pt-br/tour/inner-classes.md index aff2633035..afaed9d896 100644 --- a/_pt-br/tour/inner-classes.md +++ b/_pt-br/tour/inner-classes.md @@ -15,7 +15,7 @@ Em Scala é possível declarar classes que tenham outras classes como membros. E class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -31,9 +31,9 @@ class Graph { ``` 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: - -```scala mdoc -object GraphTest extends App { + +```scala mdoc:nest +def graphTest: Unit = { val g = new Graph val n1 = g.newNode val n2 = g.newNode @@ -44,9 +44,9 @@ object GraphTest extends App { ``` Agora melhoramos o exemplo acima com tipos, para assim declarar explicitamente qual o tipo das várias entidades definidas: - + ```scala mdoc:nest -object GraphTest extends App { +def graphTest: Unit = { val g: Graph = new Graph val n1: g.Node = g.newNode val n2: g.Node = g.newNode @@ -58,7 +58,7 @@ object GraphTest extends App { 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: - + ```scala mdoc:fail object IllegalGraphTest extends App { val g: Graph = new Graph @@ -72,12 +72,12 @@ object IllegalGraphTest extends App { ``` 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: - + ```scala mdoc:nest class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } diff --git a/_pt-br/tour/mixin-class-composition.md b/_pt-br/tour/mixin-class-composition.md index 80002a731a..e317924e0b 100644 --- a/_pt-br/tour/mixin-class-composition.md +++ b/_pt-br/tour/mixin-class-composition.md @@ -24,7 +24,7 @@ A seguir, considere a classe mixin que estende `AbsIterator` com um método `for ```scala mdoc trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next()) } + def foreach(f: T => Unit): Unit = { while (hasNext) f(next()) } } ``` @@ -43,7 +43,7 @@ Poderíamos combinar a funcionalidade de `StringIterator` e `RichIterator` em um ```scala mdoc object StringIteratorTest { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { class Iter extends StringIterator("Scala") with RichIterator val iter = new Iter iter foreach println diff --git a/_pt-br/tour/operators.md b/_pt-br/tour/operators.md index b38e0e3105..627b968e73 100644 --- a/_pt-br/tour/operators.md +++ b/_pt-br/tour/operators.md @@ -4,7 +4,7 @@ title: Operadores partof: scala-tour num: 29 -next-page: automatic-closures +next-page: annotations previous-page: type-inference language: pt-br --- diff --git a/_pt-br/tour/self-types.md b/_pt-br/tour/self-types.md index 2596a83223..de2f95ef82 100644 --- a/_pt-br/tour/self-types.md +++ b/_pt-br/tour/self-types.md @@ -108,7 +108,7 @@ Observe que nesta classe, podemos instanciar `NodeImpl` porque agora sabemos que Aqui está um exemplo de uso da classe `ConcreteDirectedGraph`: ```scala mdoc -object GraphTest extends App { +def graphTest: Unit = { val g: Graph = new ConcreteDirectedGraph val n1 = g.addNode val n2 = g.addNode diff --git a/_pt-br/tour/tour-of-scala.md b/_pt-br/tour/tour-of-scala.md index a455ddd001..970b47d0c0 100644 --- a/_pt-br/tour/tour-of-scala.md +++ b/_pt-br/tour/tour-of-scala.md @@ -38,7 +38,6 @@ Um [mecanismo de inferência de tipo local](type-inference.html) se encarrega pa 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. diff --git a/_ru/getting-started/install-scala.md b/_ru/getting-started/install-scala.md new file mode 100644 index 0000000000..44154d2dfb --- /dev/null +++ b/_ru/getting-started/install-scala.md @@ -0,0 +1,244 @@ +--- +layout: singlepage-overview +title: Начало работы +partof: getting-started +language: ru +includeTOC: true + +newcomer_resources: + - title: Вы пришли с Java? + description: Что нужно знать, чтобы ускорить работу со Scala после первоначального запуска. + icon: "fa fa-coffee" + link: /tutorials/scala-for-java-programmers.html + - title: Scala в браузере + description: > + Чтобы сразу начать экспериментировать со Scala, используйте "Scastie" в своем браузере. + icon: "fa fa-cloud" + link: https://scastie.scala-lang.org/pEBYc5VMT02wAGaDrfLnyw +--- + +Приведенные ниже инструкции охватывают как Scala 3, так и Scala 2. + +
+{% altDetails need-help-info-box 'Нужна помощь?' class=help-info %} +*Если у вас возникли проблемы с настройкой Scala, смело обращайтесь за помощью в канал `#scala-users` +[нашего Discord](https://discord.com/invite/scala).* +{% endaltDetails %} +
+ +## Ресурсы для новичков + +{% include inner-documentation-sections.html links=page.newcomer_resources %} + +## Установка Scala на компьютер + +Установка Scala означает установку различных инструментов командной строки, +таких как компилятор Scala и инструменты сборки. +Мы рекомендуем использовать инструмент установки "Coursier", +который автоматически устанавливает все зависимости. +Также возможно установить по отдельности каждый инструмент вручную. + +### Использование Scala Installer (рекомендованный путь) + +Установщик Scala — это инструмент [Coursier](https://get-coursier.io/docs/cli-overview), +основная команда которого называется `cs`. +Он гарантирует, что в системе установлены JVM и стандартные инструменты Scala. +Установите его в своей системе, следуя следующим инструкциям. + + +{% tabs install-cs-setup-tabs class=platform-os-options %} + + +{% tab macOS for=install-cs-setup-tabs %} +Запустите в терминале следующую команду, следуя инструкциям на экране: +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-brew %} +{% altDetails cs-setup-macos-nobrew "В качестве альтернативы, если вы не используете Homebrew:" %} + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-x86-64 %} +{% endaltDetails %} +{% endtab %} + + + +{% tab Linux for=install-cs-setup-tabs %} + Запустите в терминале следующую команду, следуя инструкциям на экране: + {% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-x86-64 %} +{% endtab %} + + + +{% tab Windows for=install-cs-setup-tabs %} + Загрузите и запустите [установщик Scala для Windows]({{site.data.setup-scala.windows-link}}) + на базе Coursier и следуйте инструкциям на экране. +{% endtab %} + + + +{% tab Иное for=install-cs-setup-tabs defaultTab %} + + Следуйте документации Coursier о том, + [как установить и запустить `cs setup`](https://get-coursier.io/docs/cli-installation). +{% endtab %} + + +{% endtabs %} + + + +{% altDetails testing-your-setup 'Тестирование установки' %} +Проверьте корректность установки с помощью команды `scala -version`, которая должна вывести: +```bash +$ scala -version +Scala code runner version: 1.4.3 +Scala version (default): {{site.scala-3-version}} +``` +Если сообщение не выдано, возможно, необходимо перезайти в терминал (или перезагрузиться), +чтобы изменения вступили в силу. +{% endaltDetails %} + + +Наряду с JVM `cs setup` также устанавливает полезные инструменты командной строки: + +| Commands | Description | +|---------------|--------------------------------------------------------------------------------------| +| `scalac` | компилятор Scala | +| `scala` | Scala REPL и средство запуска сценариев | +| `scala-cli` | [Scala CLI](https://scala-cli.virtuslab.org), интерактивный инструментарий для Scala | +| `sbt`, `sbtn` | Инструмент сборки [sbt](https://www.scala-sbt.org/) | +| `amm` | [Ammonite](https://ammonite.io/) — улучшенный REPL | +| `scalafmt` | [Scalafmt](https://scalameta.org/scalafmt/) - средство форматирования кода Scala | + +Дополнительная информация о cs [доступна по ссылке](https://get-coursier.io/docs/cli-overview). + +> `cs setup` по умолчанию устанавливает компилятор и исполняющую программу Scala 3 +> (команды `scalac` и `scala` соответственно). Независимо от того, собираетесь ли вы использовать Scala 2 или 3, +> обычно это не проблема, потому что в большинстве проектов используется инструмент сборки, +> который будет использовать правильную версию Scala независимо от того, какая версия установлена "глобально". +> Тем не менее, вы всегда можете запустить конкретную версию Scala, используя +> ``` +> $ cs launch scala:{{ site.scala-version }} +> $ cs launch scalac:{{ site.scala-version }} +> ``` +> Если предпочтительно, чтобы по умолчанию запускалась Scala 2, вы можете принудительно установить эту версию с помощью: +> ``` +> $ cs install scala:{{ site.scala-version }} scalac:{{ site.scala-version }} +> ``` + +### ...или вручную + +Для компиляции, запуска, тестирования и упаковки проекта Scala нужны только два инструмента: +Java 8 или 11 и sbt. +Чтобы установить их вручную: + +1. если не установлена Java 8 или 11, загрузите Java из + [Oracle Java 8](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html), [Oracle Java 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), + или [AdoptOpenJDK 8/11](https://adoptopenjdk.net/). + Подробную информацию о совместимости Scala/Java см. в разделе [Совместимость с JDK](/overviews/jdk-compatibility/overview.html). +1. установить [sbt](https://www.scala-sbt.org/download.html) + +## Создание проекта "Hello World" с помощью sbt + +В следующих разделах объясняется как создавать проект Scala после того, как установлен sbt. + +Для создания проекта можно использовать командную строку или IDE. +Мы рекомендуем командную строку, если вы с ней знакомы. + +### Использование командной строки + +sbt — это инструмент сборки для Scala. sbt компилирует, запускает и тестирует Scala код +(он также может публиковать библиотеки и выполнять множество других задач). + +Чтобы создать новый проект Scala с помощью sbt: + +1. `cd` в пустую папку. +1. Запустите команду `sbt new scala/scala3.g8`, чтобы создать проект на Scala 3, + или `sbt new scala/hello-world.g8` для создания проекта на Scala 2. + Она извлекает шаблон проекта из GitHub. + Эта команда также создает папку `target`, которую вы можете игнорировать. +1. При появлении запроса назовите приложение `hello-world`. + Это создаст проект под названием "hello-world". +1. Будет сгенерировано следующее: + +``` +- hello-world + - project (sbt использует эту папку для собственных файлов) + - build.properties + - build.sbt (файл определения сборки sbt) + - src + - main + - scala (здесь весь Scala code) + - Main.scala (точка входа в программу) <-- это все, что сейчас нужно +``` + +Дополнительную документацию по sbt можно найти в [Scala Book](/scala3/book/tools-sbt.html) +(см. [здесь](/overviews/scala-book/scala-build-tool-sbt.html) для версии Scala 2) +и в официальной [документации sbt](https://www.scala-sbt.org/1.x/docs/index.html). + +### С интегрированной средой разработки (IDE) + +Вы можете пропустить оставшуюся часть страницы и сразу перейти к [созданию проекта Scala с помощью IntelliJ и sbt](/ru/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.html). + + +## Открыть проект hello-world + +Давайте используем IDE, чтобы открыть проект. Самые популярные из них — IntelliJ и VSCode. +Оба предлагают обширные возможности, но вы по-прежнему можете использовать [множество других редакторов](https://scalameta.org/metals/docs/editors/overview.html). + + +### Использование IntelliJ + +1. Загрузите и установите [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Установите Scala plugin, следуя [инструкциям по установке плагинов IntelliJ](https://www.jetbrains.com/help/idea/managing-plugins.html) +1. Откройте файл `build.sbt`, затем выберете *Open as a project* + +### Использование VSCode с metals + +1. Загрузите [VSCode](https://code.visualstudio.com/Download) +1. Установите расширение Metals из [Marketplace](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) +1. Затем откройте каталог, содержащий файл `build.sbt` (это должен быть каталог `hello-world`, если вы следовали предыдущим инструкциям). Когда будет предложено, выберите *Import build*. + +> [Metals](https://scalameta.org/metals) — это “языковой сервер Scala”, обеспечивающий поддержку написания кода Scala в VS Code и других редакторах, +> таких как [Atom, Sublime Text и других](https://scalameta.org/metals/docs/editors/overview.html), использующих Language Server Protocol. +> +> Под капотом Metals взаимодействует со средством сборки с помощью +> [Build Server Protocol (BSP)](https://build-server-protocol.github.io/). +> Подробнее о том, как работает Metals, см. [“Написание Scala в VS Code, Vim, Emacs, Atom и Sublime Text с помощью Metals”](https://www.scala-lang.org/2019/04/16/metals.html). + +### Знакомство с исходным кодом + +Просмотрите эти два файла в своей IDE: + +- _build.sbt_ +- _src/main/scala/Main.scala_ + +При запуске проекта на следующем шаге, конфигурация в _build.sbt_ будет использована для запуска кода в _src/main/scala/Main.scala_. + +## Запуск Hello World + +Код в _Main.scala_ можно запускать из IDE, если удобно. + +Но вы также можете запустить приложение из терминала, выполнив следующие действия: + +1. `cd` в `hello-world`. +1. Запустить `sbt`. Эта команда открывает sbt-консоль. +1. В консоле введите `~run`. `~` является необязательным, но заставляет sbt повторно запускаться при каждом сохранении файла, + обеспечивая быстрый цикл редактирования/запуска/отладки. sbt также создаст директорию `target`, которую пока можно игнорировать. + +После окончания экспериментирования с проектом, нажмите `[Enter]`, чтобы прервать команду `run`. +Затем введите `exit` или нажмите `[Ctrl+D]`, чтобы выйти из sbt и вернуться в командную строку. + +## Следующие шаги + +После того как пройдете приведенные выше обучающие материалы, подумайте о том, чтобы проверить: + +* [The Scala Book](/scala3/book/introduction.html) (см. версию для Scala 2 [здесь](/overviews/scala-book/introduction.html)), которая содержит набор коротких уроков, знакомящих с основными функциями Scala. +* [The Tour of Scala](/ru/tour/tour-of-scala.html) для краткого ознакомления с функциями Scala. +* [Обучающие ресурсы](/online-courses.html), которые включают в себя интерактивные онлайн-учебники и курсы. +* [Наш список некоторых популярных книг по Scala](/books.html). +* [Руководство по миграции](/scala3/guides/migration/compatibility-intro.html) поможет перенести существующую кодовую базу Scala 2 на Scala 3. + +## Получение помощи + +Существует множество рассылок и real-time чатов на случай, если вы хотите быстро связаться с другими пользователями Scala. +Посетите страницу [нашего сообщества](https://scala-lang.org/community/), чтобы ознакомиться со списком этих ресурсов и узнать, куда можно обратиться за помощью. diff --git a/_ru/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md b/_ru/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md new file mode 100644 index 0000000000..dd13a44443 --- /dev/null +++ b/_ru/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md @@ -0,0 +1,104 @@ +--- +title: Создание проекта Scala с IntelliJ и sbt +layout: singlepage-overview +partof: building-a-scala-project-with-intellij-and-sbt +language: ru +disqus: true +previous-page: /ru/getting-started/intellij-track/getting-started-with-scala-in-intellij +next-page: /ru/testing-scala-in-intellij-with-scalatest +--- + +В этом руководстве мы увидим, как создать проект Scala с помощью [sbt](https://www.scala-sbt.org/1.x/docs/index.html). +sbt — популярный инструмент для компиляции, запуска и тестирования проектов Scala любой сложности. +Использование инструмента сборки, такого как sbt (или Maven/Gradle), становится необходимым, +если вы создаете проекты с зависимостями или несколькими файлами кода. +Мы предполагаем, что вы прочитали [первое руководство](getting-started-with-scala-in-intellij.html). + +## Создание проекта +В этом разделе мы покажем вам, как создать проект в IntelliJ. +Однако, если вы знакомы с командной строкой, мы рекомендуем вам попробовать +[Начало работы со Scala и sbt в командной строке]({{site.baseurl}}/ru/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.html) +а затем вернуться к разделу "Написание Scala кода". + +1. Если вы не создавали проект из командной строки, откройте IntelliJ и выберите "Create New Project" + * На левой панели выберите Scala, а на правой панели - sbt + * Нажмите **Next** + * Назовите проект "SbtExampleProject" +1. Если вы уже создали проект в командной строке, откройте IntelliJ, выберите *Import Project* и откройте `build.sbt` файл вашего проекта +1. Убедитесь, что ваша **JDK version** - это 1.8, а **sbt version** не ниже 0.13.13 +1. Выберите **Use auto-import**, чтобы доступные зависимости загружались автоматически. +1. Выберите **Finish** + +## Разбор структуры каталогов +sbt создает множество каталогов, которые могут быть полезны, когда вы начнете создавать более сложные проекты. +На данный момент вы можете игнорировать большинство из них, но вот объяснение, для чего все это: + +``` +- .idea (файлы IntelliJ) +- project (плагины и дополнительные настройки sbt) +- src (исходные файлы) + - main (код приложения) + - java (исходные файлы Java) + - scala (исходные файлы Scala) <-- это все, что вам сейчас нужно + - scala-2.12 (файлы, специфичные для Scala 2.12) + - test (модульные тесты) +- target (сгенерированные файлы) +- build.sbt (файл определения сборки для sbt) +``` + + +## Написание Scala-кода +1. На панели слева **Project**, разверните `SbtExampleProject` => `src` => `main` +1. Щелкните правой кнопкой мыши на `scala` и выберете **New** => **Package** +1. Назовите пакет `example` и нажмите **OK** (или просто нажмите клавишу **Enter** или **Return**). +1. Щелкните правой кнопкой мыши на пакете `example` и выберите **New** => **Scala class** +(если вы не видите эту опцию, щелкните правой кнопкой мыши на `SbtExampleProject`, кликните на **Add Frameworks Support**, выберете **Scala** и продолжите) +1. Назовите класс `Main` и измените **Kind** на `Object`. +1. Вставьте следующий код: + +``` +@main def run() = + val ages = Seq(42, 75, 29, 64) + println(s"The oldest person is ${ages.max}") +``` + +Примечание: IntelliJ имеет собственную реализацию компилятора Scala, +и иногда ваш код верен, даже если IntelliJ указывает обратное. +Вы всегда можете проверить, может ли sbt запустить ваш проект в командной строке. + +## Запуск проекта +1. В меню **Run**, выберите **Edit configurations** +1. Нажмите кнопку **+** и выберите **sbt Task**. +1. Назовите задачу `Run the program`. +1. В поле **Tasks**, введите `~run`. `~` заставляет sbt перекомпилировать +и повторно запускать проект при каждом сохранении изменений в файле проекта. +1. Нажмите **OK**. +1. В меню **Run** нажмите **Run 'Run the program'**. +1. В коде измените `75` на `61` и посмотрите на обновленные результаты в консоли. + +## Добавление зависимости +Немного меняя тему, давайте посмотрим, как использовать опубликованные библиотеки +для добавления дополнительных функций в наши приложения. +1. Откройте `build.sbt` и добавьте следующую строку: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +``` +Здесь `libraryDependencies` представляет набор зависимостей, +и с помощью `+=` мы добавляем зависимость [scala-parser-combinators](https://github.com/scala/scala-parser-combinators) +к набору зависимостей, которые sbt будет загружать при запуске. +Теперь в любом файле Scala можно импортировать классы, объекты и т.д. из `scala-parser-combinators` с помощью обычного импорта. + +Вы можете найти больше опубликованных библиотек на [Scaladex](https://index.scala-lang.org/), каталоге библиотек Scala, +где вы также можете скопировать указанную выше информацию о зависимостях для вставки в свой файл `build.sbt`. + +## Следующие шаги + +Перейдите к следующему руководству из серии _getting started with IntelliJ_ и узнайте, как [тестировать Scala в IntelliJ с помощью ScalaTest](testing-scala-in-intellij-with-scalatest.html). + +**или** + +* [The Scala Book](/scala3/book/introduction.html), содержащая набор коротких уроков, знакомящих с основными функциями Scala. +* [Тур по Scala](/ru/tour/tour-of-scala.html) для краткого ознакомления с возможностями Scala. +- Продолжайте изучать Scala в интерактивном режиме на + [Scala Exercises](https://www.scala-exercises.org/scala_tutorial). diff --git a/_ru/getting-started/intellij-track/getting-started-with-scala-in-intellij.md b/_ru/getting-started/intellij-track/getting-started-with-scala-in-intellij.md new file mode 100644 index 0000000000..65337bc058 --- /dev/null +++ b/_ru/getting-started/intellij-track/getting-started-with-scala-in-intellij.md @@ -0,0 +1,124 @@ +--- +title: Начало работы со Scala в IntelliJ +layout: singlepage-overview +partof: getting-started-with-scala-in-intellij +language: ru +disqus: true +next-page: /ru/building-a-scala-project-with-intellij-and-sbt +--- + +В этом руководстве мы увидим, как создать минимальный проект Scala с помощью IntelliJ IDE со Scala плагином. +В этом руководстве IntelliJ загрузит Scala за вас. + +## Установка + +1. Убедитесь, что у вас установлена Java 8 JDK (также известная как 1.8) + * Запустите `javac -version` в командной строке и убедитесь, что выдается + `javac 1.8.___` + * Если у вас нет версии 1.8 или выше, [установите JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +1. Затем загрузите и установите [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Затем, после запуска IntelliJ, вы можете загрузить и установить Scala плагин, следуя + [инструкции по установке плагинов IntelliJ](https://www.jetbrains.com/help/idea/installing-updating-and-uninstalling-repository-plugins.html) (найдите "Scala" в меню плагинов). + +Когда мы создадим проект, то установим последнюю версию Scala. +Примечание: Если вы хотите открыть существующий проект Scala, вы можете нажать **Open** +при запуске IntelliJ. + +## Создание проекта + +1. Откройте IntelliJ и нажмите **File** => **New** => **Project** +1. На левой панели выберите Scala. На правой панели - IDEA. +1. Назовите проект **HelloWorld** +1. Если вы впервые создаете Scala проект с помощью IntelliJ, вам необходимо установить Scala SDK. + Справа от поля Scala SDK нажмите кнопку **Create**. +1. Выберите последний номер версии (например, {{ site.scala-version }}) и нажмите **Download**. +Это может занять несколько минут, но тот же пакет SDK могут использовать последующие проекты. +1. Когда SDK будет установлен и вы вернетесь в окно "New Project", нажмите **Finish**. + +## Написание кода + +1. На левой панели **Project** щелкните правой кнопкой мыши на папке `src` и выберите +**New** => **Scala class**. Если вы не видите **Scala class**, щелкните правой кнопкой мыши на **HelloWorld** +и выберите **Add Framework Support...**, затем - **Scala** и продолжить. +Если вы видите ошибку **Error: library is not specified**, вы можете либо нажать кнопку загрузки, +либо выбрать путь к библиотеке вручную. Если вы видите только **Scala Worksheet** попробуйте развернуть папку `src` +и её подпапку `main`, а затем правой кнопкой мыши на папке `scala`. +1. Назовите класс `Hello` и измените **Kind** на `object`. +1. Вставьте следующий код: + +{% tabs hello-world-entry-point class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-entry-point %} + +``` +object Hello extends App { + println("Hello, World!") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-entry-point %} + +``` +@main def hello(): Unit = + println("Hello, World!") +``` + +В Scala 3 вы можете удалить объект `Hello` и вместо него определить метод верхнего уровня `hello` +с аннотацией `@main`. + +{% endtab %} + +{% endtabs %} + +## Запуск + +{% tabs hello-world-run class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-run %} + +* Щелкните правой кнопкой мыши на `Hello` в своем коде и выберите **Run 'Hello'**. +* Готово! + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-run %} + +* Щелкните правой кнопкой мыши на `hello` в своем коде и выберите **Run 'hello'**. +* Готово! + +{% endtab %} + +{% endtabs %} + +## Эксперименты со Скалой + +Хороший способ попробовать примеры кода — использовать Scala Worksheets. + +1. В левой панели проекта щелкните правой кнопкой мыши на +`src` и выберите **New** => **Scala Worksheet**. +2. Назовите новый Scala worksheet "Mathematician". +3. Введите следующий код в worksheet: + +{% tabs square %} +{% tab 'Scala 2 and 3' for=square %} +``` +def square(x: Int): Int = x * x + +square(2) +``` +{% endtab %} +{% endtabs %} + +После запуска кода вы заметите, что результаты его выполнения выводятся на правой панели. +Если вы не видите правую панель, щелкните правой кнопкой мыши на вашем Scala worksheet на панели "Проект" +и выберите "Evaluate Worksheet". + +## Следующие шаги + +Теперь вы знаете, как создать простой Scala проект, который можно использовать для изучения языка. +В следующем уроке мы представим важный инструмент сборки под названием sbt, +который можно использовать для простых проектов и рабочих приложений. + +Далее: [Создание проекта Scala с IntelliJ и sbt](building-a-scala-project-with-intellij-and-sbt.html) diff --git a/_ru/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md b/_ru/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md new file mode 100644 index 0000000000..7a2ffef8fe --- /dev/null +++ b/_ru/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md @@ -0,0 +1,73 @@ +--- +title: Тестирование Scala в IntelliJ с помощью ScalaTest +layout: singlepage-overview +partof: testing-scala-in-intellij-with-scalatest +language: ru +disqus: true +previous-page: /ru/building-a-scala-project-with-intellij-and-sbt +--- + +Для Scala существует множество библиотек и методологий тестирования, +но в этом руководстве мы продемонстрируем один популярный вариант из фреймворка ScalaTest +под названием [AnyFunSuite](https://www.scalatest.org/getting_started_with_fun_suite). + +Это предполагает, что вы знаете, [как создать проект в IntelliJ](building-a-scala-project-with-intellij-and-sbt.html). + +## Настройка +1. Создайте sbt проект в IntelliJ. +1. Добавьте зависимость ScalaTest: + 1. Добавьте зависимость ScalaTest в свой файл `build.sbt`: + ``` + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test + ``` + 1. Если вы получили уведомление "build.sbt was changed", выберите **auto-import**. + 1. Эти два действия заставят `sbt` подгрузить библиотеки ScalaTest. + 1. Дождитесь окончания синхронизации `sbt`; в противном случае, `AnyFunSuite` и `test()` не будет распознаны. +1. На панели проекта слева разверните `src` => `main`. +1. Щелкните правой кнопкой мыши на `scala` и выберите **New** => **Scala class**. +1. Назовите новый класс `CubeCalculator`, измените **Kind** на `object`, или дважды щелкните на `object`. +1. Вставьте следующий код: + ``` + object CubeCalculator: + def cube(x: Int) = + x * x * x + ``` + +## Создание теста +1. На панели проекта слева разверните `src` => `test`. +1. Щелкните правой кнопкой мыши на `scala` и выберите **New** => **Scala class**. +1. Назовите новый класс `CubeCalculatorTest` и нажмите **Enter** или дважды щелкните на `class`. +1. Вставьте следующий код: + ``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite: + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } + ``` +1. В исходном коде щелкните правой кнопкой мыши на `CubeCalculatorTest` и выберите + **Run 'CubeCalculatorTest'**. + +## Разбор кода + +Давайте разберем код построчно: + +* `class CubeCalculatorTest` означает, что мы тестируем `CubeCalculator` +* `extends AnyFunSuite` позволяет нам использовать функциональность класса AnyFunSuite из ScalaTest, + такую как функция `test` +* `test` это функция из библиотеки FunSuite, которая собирает результаты проверок в теле функции. +* `"CubeCalculator.cube"` - это имя для теста. Вы можете называть тест как угодно, но по соглашению используется имя — "ClassName.methodName". +* `assert` принимает логическое условие и определяет, пройден тест или нет. +* `CubeCalculator.cube(3) === 27` проверяет, действительно ли вывод функции `cube` равен 27. + `===` является частью ScalaTest и предоставляет понятные сообщения об ошибках. + +## Добавление еще одного теста +1. Добавьте еще один оператор `assert` после первого, который проверяет 0 в кубе. +1. Перезапустите тест `CubeCalculatorTest`, кликнув правой кнопкой мыши и выбрав + **Run 'CubeCalculatorTest'**. + +## Заключение +Вы видели один из способов тестирования Scala кода. +Узнать больше о AnyFunSuite от ScalaTest можно на [официальном сайте](https://www.scalatest.org/getting_started_with_fun_suite). +Вы также можете использовать другие тестовые фреймворки, такие, как [ScalaCheck](https://www.scalacheck.org/) и [Specs2](https://etorreborre.github.io/specs2/). diff --git a/_ru/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md b/_ru/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md new file mode 100644 index 0000000000..f404e3daf8 --- /dev/null +++ b/_ru/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md @@ -0,0 +1,111 @@ +--- +title: Начало работы со Scala и sbt в командной строке +layout: singlepage-overview +partof: getting-started-with-scala-and-sbt-on-the-command-line +language: ru +disqus: true +next-page: /ru/testing-scala-with-sbt-on-the-command-line +--- + +В этом руководстве вы увидите, как создавать проекты Scala из шаблона. +Это можно использовать как отправную точку для своих собственных проектов. +Мы будем использовать [sbt](https://www.scala-sbt.org/1.x/docs/index.html), де-факто инструмент сборки для Scala. +sbt компилирует, запускает и тестирует ваши проекты среди других связанных задач. +Мы предполагаем, что вы знаете, как пользоваться терминалом. + +## Установка +1. Убедитесь, что у вас установлена Java 8 JDK (также известная как 1.8) + * Запустите `javac -version` в командной строке и убедитесь, что выдается + `javac 1.8.___` + * Если у вас нет версии 1.8 или выше, [установите JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +1. Установите sbt + * [Mac](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Mac.html) + * [Windows](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Windows.html) + * [Linux](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Linux.html) + +## Создание проекта + +{% tabs sbt-welcome-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=sbt-welcome-1 %} + +1. `cd` в пустую папку. +1. Запустите следующую команду `sbt new scala/hello-world.g8`. + Она извлекает шаблон 'hello-world' из GitHub. + Она также создаст папку `target`, которую пока можно игнорировать. +1. При появлении запроса назовите приложение `hello-world`. Это создаст проект под названием "hello-world". +1. Давайте взглянем на то, что только что было сгенерировано: + +{% endtab %} +{% tab 'Scala 3' for=sbt-welcome-1 %} + +1. `cd` в пустую папку. +1. Запустите следующую команду `sbt new scala/scala3.g8`. + Она извлекает шаблон 'scala3' из GitHub. + Она также создаст папку `target`, которую пока можно игнорировать. +1. При появлении запроса назовите приложение `hello-world`. Это создаст проект под названием "hello-world". +1. Давайте взглянем на то, что только что было сгенерировано: + +{% endtab %} +{% endtabs %} + + +``` +- hello-world + - project (sbt использует эту папку для установки и настройки плагинов и зависимостей) + - build.properties + - src + - main + - scala (весь Scala код находится в этой папке) + - Main.scala (точка входа в программу) <-- это все, что вам сейчас нужно + - build.sbt (файл определения сборки для sbt) +``` + +После того как вы создадите свой проект, sbt создаст дополнительные каталоги `target` для сгенерированных файлов. +Вы можете игнорировать их. + +## Запуск проекта +1. `cd` в `hello-world`. +1. Запустите `sbt`. Эта команда запустит sbt console. +1. Запустите `~run`. `~` опциональна и заставляет sbt перекомпилировать + и повторно запускать проект при каждом сохранении изменений в файле проекта + для быстрого цикла редактирование/запуск/отладка. + sbt также сгенерит директорию `target`, которую можно игнорировать. + +## Доработка кода +1. Откройте файл `src/main/scala/Main.scala` в вашем любимом текстовом редакторе. +1. Измените "Hello, World!" на "Hello, New York!" +1. Если вы не остановили команду sbt, то должны увидеть "Hello, New York!", напечатанным в консоли. +1. Вы можете продолжить вносить изменения и видеть результаты доработки в консоли. + +## Добавление зависимости +Немного меняя тему, давайте посмотрим, как использовать опубликованные библиотеки +для добавления дополнительных функций в наши приложения. + +1. Откройте `build.sbt` и добавьте следующую строку: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +``` +Здесь `libraryDependencies` представляет набор зависимостей, +и с помощью `+=` мы добавляем зависимость [scala-parser-combinators](https://github.com/scala/scala-parser-combinators) +к набору зависимостей, которые sbt будет загружать при запуске. +Теперь в любом файле Scala можно импортировать классы, объекты и т.д. из `scala-parser-combinators` с помощью обычного импорта. + +Вы можете найти больше опубликованных библиотек на [Scaladex](https://index.scala-lang.org/), каталоге библиотек Scala, +где вы также можете скопировать указанную выше информацию о зависимостях для вставки в свой файл `build.sbt`. + +> **Примечание для Java библиотек:** Для обычной библиотеки Java следует использовать только один знак процента (`%`) +> между названием организации и именем артефакта. Двойной процент (`%%`) — это специализация Scala библиотек. +> Подробнее об этом можно узнать в [документации sbt][sbt-docs-lib-dependencies]. + +## Следующие шаги + +Перейдите к следующему учебнику из серии _getting started with sbt_ и узнайте, как [тестировать Scala c sbt и ScalaTest в командной строке](testing-scala-with-sbt-on-the-command-line.html). + +**или** + +- Продолжайте изучать Scala в интерактивном режиме на + [Scala Exercises](https://www.scala-exercises.org/scala_tutorial). +- Узнайте о возможностях Scala с помощью небольших статей, ознакомившись с нашим [туром по Scala]({{ site.baseurl }}/ru/tour/tour-of-scala.html). + +[sbt-docs-lib-dependencies]: https://www.scala-sbt.org/1.x/docs/Library-Dependencies.html#Getting+the+right+Scala+version+with diff --git a/_ru/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md b/_ru/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md new file mode 100644 index 0000000000..a74aa92a19 --- /dev/null +++ b/_ru/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md @@ -0,0 +1,105 @@ +--- +title: Тестирование Scala c sbt и ScalaTest в командной строке +layout: singlepage-overview +partof: testing-scala-with-sbt-on-the-command-line +language: ru +disqus: true +previous-page: /ru/getting-started-with-scala-and-sbt-on-the-command-line +--- + +Для Scala существует множество библиотек и методологий тестирования, +но в этом руководстве мы продемонстрируем один популярный вариант из фреймворка ScalaTest +под названием [AnyFunSuite](https://www.scalatest.org/getting_started_with_fun_suite). + +Это предполагает, что вы знаете, [как создать проект с sbt](getting-started-with-scala-and-sbt-on-the-command-line.html). + +## Настройка +1. Используя командную строку создайте новую директорию. +1. Перейдите (`cd`) в этот каталог и запустите `sbt new scala/scalatest-example.g8`. +1. Назовите проект `ScalaTestTutorial`. +1. Проект поставляется с зависимостью ScalaTest в файле `build.sbt`. +1. Перейдите (`cd`) в этот каталог и запустите `sbt test`. Это запустит набор тестов +`CubeCalculatorTest` с одним тестом под названием `CubeCalculator.cube`. + +``` +sbt test +[info] Loading global plugins from /Users/username/.sbt/0.13/plugins +[info] Loading project definition from /Users/username/workspace/sandbox/my-something-project/project +[info] Set current project to scalatest-example (in build file:/Users/username/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 +``` + +## Разбор кода +1. Откройте два файла в текстовом редакторе: + * `src/main/scala/CubeCalculator.scala` + * `src/test/scala/CubeCalculatorTest.scala` +1. В файле `CubeCalculator.scala` увидите определение функции `cube`. +1. В файле `CubeCalculatorTest.scala` тестируемый класс, названный так же как и объект. + +``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite: + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } +``` + +Давайте разберем код построчно: + +* `class CubeCalculatorTest` означает, что мы тестируем `CubeCalculator` +* `extends AnyFunSuite` позволяет нам использовать функциональность класса AnyFunSuite из ScalaTest, + такую как функция `test` +* `test` это функция из библиотеки FunSuite, которая собирает результаты проверок в теле функции. +* `"CubeCalculator.cube"` - это имя для теста. Вы можете называть тест как угодно, но по соглашению используется имя — "ClassName.methodName". +* `assert` принимает логическое условие и определяет, пройден тест или нет. +* `CubeCalculator.cube(3) === 27` проверяет, действительно ли вывод функции `cube` равен 27. + `===` является частью ScalaTest и предоставляет понятные сообщения об ошибках. + +## Добавление еще одного теста +1. Добавьте еще один тестовый блок с собственным оператором assert, который проверяет 0 в кубе. + + ``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite { + test("CubeCalculator.cube 3 should be 27") { + assert(CubeCalculator.cube(3) === 27) + } + + test("CubeCalculator.cube 0 should be 0") { + assert(CubeCalculator.cube(0) === 0) + } + } + ``` + +1. Запустите `sbt test` еще раз, чтобы увидеть результаты. + + ``` + sbt test + [info] Loading project definition from C:\projects\scalaPlayground\scalatestpractice\project + [info] Loading settings for project root from build.sbt ... + [info] Set current project to scalatest-example (in build file:/C:/projects/scalaPlayground/scalatestpractice/) + [info] Compiling 1 Scala source to C:\projects\scalaPlayground\scalatestpractice\target\scala-2.13\test-classes ... + [info] CubeCalculatorTest: + [info] - CubeCalculator.cube 3 should be 27 + [info] - CubeCalculator.cube 0 should be 0 + [info] Run completed in 257 milliseconds. + [info] Total number of tests run: 2 + [info] Suites: completed 1, aborted 0 + [info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0 + [info] All tests passed. + [success] Total time: 3 s, completed Dec 4, 2019 10:34:04 PM + ``` + +## Заключение +Вы видели один из способов тестирования Scala кода. +Узнать больше о AnyFunSuite от ScalaTest можно на [официальном сайте](https://www.scalatest.org/getting_started_with_fun_suite). +Вы также можете использовать другие тестовые фреймворки, такие, как [ScalaCheck](https://www.scalacheck.org/) и [Specs2](https://etorreborre.github.io/specs2/). diff --git a/_ru/index.md b/_ru/index.md index dcb6f28eb4..7ac8c2a455 100644 --- a/_ru/index.md +++ b/_ru/index.md @@ -1,42 +1,50 @@ --- -layout: inner-page-documentation -title: Документация +layout: landing-page +title: Изучаем Scala language: ru partof: documentation -discourse: true - -# Content masthead links more-resources-label: Дополнительные Материалы -sections: - - title: "Для новичков" +sections: + - title: "Первые шаги..." links: - - title: "Самое начало" - description: "Установка Scala на ваш компьютер и написание пробного Scala кода!" + - title: "Приступая к работе" + description: "Установите Scala на свой компьютер и начните писать код на Scala!" icon: "fa fa-rocket" - link: /getting-started.html + link: /ru/getting-started/install-scala.html - title: "Тур по Scala" description: "Вступительный обзор по основным возможностям языка." icon: "fa fa-flag" link: /ru/tour/tour-of-scala.html - - title: "для Java программистов" - description: "Быстрое знакомство со Scala для тех кто уже знает Java." - icon: "fa fa-coffee" - link: /tutorials/scala-for-java-programmers.html - more-resources: + - title: "Книга по Scala 3" + description: "Изучайте Scala используя серию коротких уроков." + icon: "fa fa-book-open" + link: /ru/scala3/book/introduction.html + - title: "Набор инструментов Scala" + description: "Отправка HTTP-запросов, запись файлов, запуск программ, обработка JSON..." + icon: "fa fa-toolbox" + link: /toolkit/introduction.html - title: Онлайн Курсы, Упражнения и Блоги - url: /learn.html + description: "Обучающие курсы по Scala от новичка до продвинутого уровня." + icon: "fa fa-cloud" + link: /online-courses.html - title: Книги - url: /books.html + description: "Напечатанные, а также электронные книги о Scala." + icon: "fa fa-book" + link: /books.html + - title: Уроки + description: "Пройдемся по серии коротких шагов по созданию Scala приложений." + icon: "fa fa-tasks" + link: /tutorials.html - title: "Для опытных" links: - title: "API" - description: "API документация для всех версий Scala." + description: "Документация по API для каждой версии Scala." icon: "fa fa-file-alt" link: /api/all.html - - title: "Обзоры" - description: "Подробная документация, охватывающая возможности Scala." + - title: "Справочники" + description: "Подробные справочники по отдельным разделам языка." icon: "fa fa-database" link: /ru/overviews/index.html - title: "Стилистика" @@ -48,18 +56,49 @@ sections: icon: "fa fa-list" link: /ru/cheatsheets/index.html - title: "Вопрос-Ответ" - description: "Список по наиболее часто задаваемых вопросов с ответами по функционалу Scala" + description: "Список по наиболее часто задаваемым вопросам с ответами по функционалу Scala." icon: "fa fa-question-circle" link: /tutorials/FAQ/index.html - - title: "Спецификация" - description: "Официальная спецификация языка Scala" + - title: "Спецификация v2.x" + description: "Официальная спецификация языка Scala 2." icon: "fa fa-book" link: https://scala-lang.org/files/archive/spec/2.13/ + - title: "Спецификация v3.x" + description: "Официальная спецификация языка Scala 3." + icon: "fa fa-book" + link: https://scala-lang.org/files/archive/spec/3.4/ + - title: "Справочник по языку Scala 3" + description: "Справочник по языку Scala 3." + icon: "fa fa-book" + link: https://docs.scala-lang.org/scala3/reference + + - title: "Исследуем Scala 3" + links: + - title: "Руководство по миграции" + description: "Руководство, которое поможет вам перейти от Scala 2 к Scala 3." + icon: "fa fa-suitcase" + link: /scala3/guides/migration/compatibility-intro.html + - title: "Новое в Scala 3" + description: "Обзор новой функциональности в Scala 3." + icon: "fa fa-star" + link: /ru/scala3/new-in-scala3.html + - title: "Новая функциональность Scaladoc для Scala 3" + description: "Ключевые особенности новой функциональности Scaladoc." + icon: "fa fa-star" + link: /ru/scala3/scaladoc.html + - title: "Выступления" + description: "Доступные онлайн выступления о Scala 3." + icon: "fa fa-play-circle" + link: /ru/scala3/talks.html - title: "Развитие Scala" links: - - title: "SIPs" - description: "Процесс улучшения Scala (Scala Improvement Process). Эволюция языка и компилятора." + - title: "Процесс улучшения Scala" + description: "Описание процесса развития языка и список всех предложений по улучшению Scala (SIP)." icon: "fa fa-cogs" link: /sips/index.html + - title: "Станьте участником развития Scala" + description: "От начала до конца: узнайте, как вы можете помочь открытой экосистеме Scala." + icon: "fa fa-code-branch" + link: /contribute/ --- diff --git a/_ru/online-courses.md b/_ru/online-courses.md new file mode 100644 index 0000000000..2ec0c26bc9 --- /dev/null +++ b/_ru/online-courses.md @@ -0,0 +1,62 @@ +--- +title: Online ресурсы +layout: singlepage-overview +language: ru +redirect-from: + - /learn.html +--- + +## Попробуй Scala в своем браузере! + +Существует несколько веб-сайтов, на которых вы можете интерактивно запускать код Scala в своем браузере! +Взгляните на [Scastie](https://scastie.scala-lang.org/). + +## Онлайн-курсы от Scala Center + +[Scala Center](https://scala.epfl.ch) стремится создавать высококачественные и бесплатные онлайн-курсы +для изучения Scala и функционального программирования. +Уровни курса варьируются от начального до продвинутого. +Более подробная информация доступна [на следующей странице]({% link scalacenter-courses.md %}). + +## Упражнения на языке Scala + +[Scala Exercises](https://www.scala-exercises.org/) — это серия уроков и упражнений, созданных [47 Degrees](https://www.47deg.com/). +Это отличный способ получить краткое представление о Scala и одновременно проверить свои знания. + +[Tour of Scala](https://tourofscala.com) шаг за шагом знакомит вас со Scala, от новичка до эксперта. + +## Лекции доктора Mark C Lewis из Trinity University + +[Dr. Mark C Lewis](https://www.cs.trinity.edu/~mlewis/) из Университета Тринити, Сан-Антонио, Техас, +преподает курсы программирования с использованием языка Scala. +Видеокурсы доступны на YouTube бесплатно. Некоторые курсы ниже. + +- [Introduction to Programming and Problem Solving Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt9MIJ9DV4ps-_trOzWtphYO) +- [Object-Orientation, Abstraction, and Data Structures Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt8JLumqKj-3BlHmEXPIfR42) + +Вы можете посетить его [YouTube канал](https://www.youtube.com/user/DrMarkCLewis/featured), +чтобы найти больше видео. + +## Сообщество обучения Scala + +[Сообщество по изучению Scala в Discord](http://sca.la/learning-community) — растущее онлайн-сообщество, +объединяющее учащихся с онлайн-ресурсами для совместного изучения Scala. + +## allaboutscala + +[allaboutscala](https://allaboutscala.com/) предоставляет подробные руководства для начинающих. + +## DevInsideYou + +[DevInsideYou](https://youtube.com/devinsideyou) — это YouTube канал с сотнями часов бесплатного контента Scala. + +## Rock the JVM + +[Rock the JVM](https://rockthejvm.com) — это учебная платформа с бесплатными и платными курсами +по языку Scala, Akka, Cats Effect, ZIO, Apache Spark и другим инструментам экосистемы Scala. +Он также содержит сотни [бесплатных видеоуроков](https://youtube.com/rockthejvm) +и [статей](https://blog.rockthejvm.com) по различным темам, связанным со Scala. + +## Visual Scala Reference + +[Visual Scala Reference](https://superruzafa.github.io/visual-scala-reference/) — руководство по визуальному изучению концепций и функций Scala. diff --git a/_ru/overviews/collections-2.13/arrays.md b/_ru/overviews/collections-2.13/arrays.md index 7e71da1027..856c08002c 100644 --- a/_ru/overviews/collections-2.13/arrays.md +++ b/_ru/overviews/collections-2.13/arrays.md @@ -1,22 +1,16 @@ --- layout: multipage-overview title: Массивы - -discourse: true - partof: collections-213 overview-name: Collections - num: 10 previous-page: concrete-mutable-collection-classes next-page: strings language: ru - --- [Массивы](https://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 массивы могут быть обобщены (_generic_). То есть вы можете описать массив как `Array[T]`, где `T` дополнительный `параметр-тип` массива или же абстрактный тип. +С одной стороны, Scala массивы соответствуют массивам из Java. Например, Scala массив `Array[Int]` реализован в виде Java `int[]`, а `Array[Double]` как Java `double[]` и `Array[String]` как Java `String[]`. С другой стороны, Scala массивы дают намного больше чем их Java аналоги. Во-первых, Scala массивы могут быть обобщены (_generic_). То есть вы можете описать массив как `Array[T]`, где `T` дополнительный `параметр-тип` массива или же абстрактный тип. Во-вторых, Scala массивы совместимы со списками (`Seq`) Scala - вы можете передавать `Array[T]` на вход туда, где требуется `Seq[T]`. Ну и наконец, Scala массивы также поддерживают все операции, которые есть у списков. Вот пример: scala> val a1 = Array(1, 2, 3) @@ -28,7 +22,7 @@ language: ru scala> a3.reverse res0: Array[Int] = Array(9, 3) -Учитывая то что Scala массивы соответствуют массивам из Java, каким же образом реализованы остальные дополнительные возможности массивов в Scala? +Учитывая то, что Scala массивы соответствуют массивам из Java, каким же образом реализованы остальные дополнительные возможности массивов в Scala? Реализация массивов в Scala постоянно использует неявные преобразования. В Scala массив не пытается _притворяться_ последовательностью. Он и не может, потому что тип данных лежащий в основе массива не является подтипом `Seq`. Вместо этого, используя "упаковывание", происходит неявное преобразование между массивами и экземплярами класса `scala.collection.mutable.ArraySeq`, который является подклассом `Seq`. Вот как это работает: scala> val seq: collection.Seq[Int] = a1 @@ -55,7 +49,7 @@ language: ru Вы видите, что вызов `reverse` на `seq`, который является `ArraySeq`, даст снова `ArraySeq`. Это логично, потому что массивы - это `Seqs`, и вызов `reverse` на любом `Seq` даст снова `Seq`. С другой стороны, вызов `reverse` на экземпляре класса `ArrayOps` даст значение `Array`, а не `Seq`. -Пример `ArrayOps`, приведенный выше искусственный и используется лишь, чтоб показать разницу с `ArraySeq`. Обычно, вы никогда не создаете экземпляры класса `ArrayOps`. Вы просто вызываете методы `Seq` на массиве: +Пример `ArrayOps`, приведенный выше искусственный и используется лишь, чтобы показать разницу с `ArraySeq`. Обычно, вы никогда не создаете экземпляры класса `ArrayOps`. Вы просто вызываете методы `Seq` на массиве: scala> a1.reverse res4: Array[Int] = Array(3, 2, 1) @@ -67,7 +61,7 @@ language: ru где `intArrayOps` - неявное преобразование, которое было вставлено ранее. В связи с этим возникает вопрос, как компилятор выбрал `intArrayOps` вместо другого неявного преобразования в `ArraySeq` в строке выше. В конце концов, оба преобразования преобразуют массив в тип, поддерживающий метод reverse. Ответ на этот вопрос заключается в том, что два неявных преобразования имеют приоритет. Преобразование `ArrayOps` имеет больший приоритет, чем преобразование `ArraySeq`. Первый определяется в объекте `Predef`, а второй - в классе `scala.LowPriorityImplicits`, который `Predef` наследует. Неявные преобразования в дочерних классах и дочерних объектах имеют более высокий приоритет над преобразованиями в базовых классах. Таким образом, если оба преобразования применимы, выбирается вариант в `Predef`. Очень похожая схема используется для строк. -Итак, теперь вы знаете, как массивы могут быть совместимы с последовательностями и как они могут поддерживать все операции последовательностей. А как же обобщения? В Java нельзя написать `T[]`, где `T` является параметром типа. Как же представлен Scala `Array[T]`? На самом деле обобщенный массив типа `Array[T]` может быть любым из восьми примитивных типов массивов Java `byte[]`, `short[]`, `char[]`, `int[] `, `long[] `, `float[]`, `double ` или может быть массивом объектов. Единственным общим типом, включающим все эти типы, является `AnyRef` (или, равнозначно `java.lang.Object`), так что это тот тип в который компилятор Scala отобразит `Array[T]`. Во время исполнения, при обращении к элементу массива типа `Array[T]`, происходит последовательность проверок типов, которые определяют тип массива, за которыми следует подходящая операция на Java-массиве. Эти проверки типов замедляют работу массивов. Можно ожидать падения скорости доступа к обобщенным массивам в три-четыре раза, по сравнению с обычными массивами или массивами объектов. Это означает, что если вам нужна максимальная производительность, вам следует выбирать конкретные массивы, вместо обобщенных. Отображать обобщенный массив еще пол беды, нам нужен еще способ создания обобщенных массивов. Это куда более сложная задача, которая требует, от вас, небольшой помощи. Чтобы проиллюстрировать проблему, рассмотрим следующую попытку написания обобщенного метода, который создает массив. +Итак, теперь вы знаете, как массивы могут быть совместимы с последовательностями и как они могут поддерживать все операции последовательностей. А как же обобщения? В Java нельзя написать `T[]`, где `T` является параметром типа. Как же представлен Scala `Array[T]`? На самом деле обобщенный массив типа `Array[T]` может быть любым из восьми примитивных типов массивов Java `byte[]`, `short[]`, `char[]`, `int[] `, `long[] `, `float[]`, `double ` или может быть массивом объектов. Единственным общим типом, включающим все эти типы, является `AnyRef` (или, равнозначно `java.lang.Object`), так что это тот тип, в который компилятор Scala отобразит `Array[T]`. Во время исполнения, при обращении к элементу массива типа `Array[T]`, происходит последовательность проверок типов, которые определяют тип массива, за которыми следует подходящая операция на Java-массиве. Эти проверки типов замедляют работу массивов. Можно ожидать падения скорости доступа к обобщенным массивам в три-четыре раза, по сравнению с обычными массивами или массивами объектов. Это означает, что если вам нужна максимальная производительность, вам следует выбирать конкретные массивы, вместо обобщенных. Отображать обобщенный массив еще полбеды, нам нужен еще способ создания обобщенных массивов. Это куда более сложная задача, которая требует от вас небольшой помощи. Чтобы проиллюстрировать проблему, рассмотрим следующую попытку написания обобщенного метода, который создает массив. // это неправильно! def evenElems[T](xs: Vector[T]): Array[T] = { diff --git a/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md b/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md index 6da8bcc3e1..e2f94e3455 100644 --- a/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md +++ b/_ru/overviews/collections-2.13/concrete-immutable-collection-classes.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Реализации Неизменяемых Коллекций - -discourse: true - partof: collections-213 overview-name: Collections - previous-page: maps next-page: concrete-mutable-collection-classes - num: 8 language: ru - --- Scala предлагает множество конечных реализаций неизменяемых коллекций. Они отличаются реализуемыми трейтами (мапы (map), множества(set), последовательности(seq)), они могут быть бесконечными, и различаются производительностью операций. Вот некоторые из наиболее распространенных неизменяемых типов коллекций, используемых в Scala. @@ -30,7 +24,7 @@ Scala предлагает множество конечных реализац scala> val lazyList = 1 #:: 2 #:: 3 #:: LazyList.empty lazyList: scala.collection.immutable.LazyList[Int] = LazyList(?) -На первом месте в этом ленивом списке - 1, а на втором - 2 и 3. Но ни один из элементов здесь не выводится, потому что список еще не вычислен! Ленивые списки задуманны обрабатываться лениво, поэтому метод `toString`, не выводит всех элементов, не заставляя производить дополнительные вычисления. +На первом месте в этом ленивом списке - 1, а на втором - 2 и 3. Но ни один из элементов здесь не выводится, потому что список еще не вычислен! Ленивые списки задуманы обрабатываться лениво, поэтому метод `toString` не выводит всех элементов, не заставляя производить дополнительные вычисления. Ниже приводится более сложный пример. Вычисления ленивого списка, содержащего последовательность Фибоначчи, которая начинается с заданных двух чисел. Последовательность Фибоначчи - это последовательность, в которой каждый элемент представляет собой сумму двух предыдущих элементов в серии. @@ -78,7 +72,7 @@ res27: scala.collection.immutable.ArraySeq[Int] = ArraySeq(1, 2, 3) Как видно из последней строки выше, вызов `updated` не влияет на исходный ArraySeq `arr`. -ArraySeqs хранят свои элементы в приватном [Массиве](arrays.html). Таким образом достигается компактное представление и обеспечивается быстрый индексированный доступ к элементам, но обновление или добавление одного элемента занимает линейное время, так как требует создания другого массива и копирования всех элементов исходного массива. +ArraySeqs хранят свои элементы в приватном [Массиве]({% link _ru/overviews/collections-2.13/arrays.md %}). Таким образом достигается компактное представление и обеспечивается быстрый индексированный доступ к элементам, но обновление или добавление одного элемента занимает линейное время, так как требует создания другого массива и копирования всех элементов исходного массива. ## Вектора (Vectors) @@ -86,7 +80,7 @@ ArraySeqs хранят свои элементы в приватном [Масс [Вектор](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) - тип коллекции, который обеспечивает хорошую производительность для всех своих операций. Вектора позволяют получить доступ к любому элементу последовательности за "практически" постоянное время. Это значит что константа больше, чем при получении переднего (`head`) элемента списка или при чтения элемента из ArraySeq, но, тем не менее, это константа. Избегайте использование векторов в алгоритмах базирующихся на активной работе с передними (`head`) элементами. Вектора могут получать доступ к элементам и изменять их в произвольных местах, что делает разработку более простой и удобной. -Вектора создаются и модифицируются также как и другие последовательности. +Вектора создаются и модифицируются так же, как и другие последовательности. scala> val vec = scala.collection.immutable.Vector.empty vec: scala.collection.immutable.Vector[Nothing] = Vector() @@ -97,10 +91,10 @@ ArraySeqs хранят свои элементы в приватном [Масс scala> vec3(0) res1: Int = 100 -Вектора представленны деревьями с высоким уровнем ветвления (уровень ветвления дерева или графа - это количество дочерних элементов у каждого узла). Каждый узел дерева содержит до 32х элементов вектора или содержит до 32х других узлов. Вектора с размером до 32х элементов могут быть представленны одним узлом. Вектора `32 * 32 = 1024` элементы могут быть представлены одним витком. +Вектора представлены деревьями с высоким уровнем ветвления (уровень ветвления дерева или графа - это количество дочерних элементов у каждого узла). Каждый узел дерева содержит до 32х элементов вектора или содержит до 32х других узлов. Вектора с размером до 32х элементов могут быть представлены одним узлом. Вектора `32 * 32 = 1024` элементы могут быть представлены одним витком. Для векторов с 215 элементами достаточно двух переходов от корня дерева до конечного элемента узла, трех переходов для векторов с 220 элементами, четырех переходов для 225 элементами и пяти переходов для 230 элементами. Таким образом, для всех векторов разумных размеров выбор элемента включает до 5 простых выборок массивов. Именно это мы подразумевали, когда писали, что доступ к элементам осуществляется с "практически постоянным временем". -Также как и доступ к элементу, операция обновления в векторах занимает "практически" постоянное время. Добавление элемента в середину вектора может быть выполнено через копирование узла содержащего этот элемент и каждого ссылающегося на него узла, начиная от корня дерева. Это означает, что процесс обновления элемента создает от одного до пяти узлов, каждый из которых содержит до 32 элементов или поддеревьев. Это, конечно, дороже, чем просто обновление элемента в изменяемом массиве, но все же намного дешевле, чем копирование вообще всего вектора. +Так же как и доступ к элементу, операция обновления в векторах занимает "практически" постоянное время. Добавление элемента в середину вектора может быть выполнено через копирование узла содержащего этот элемент и каждого ссылающегося на него узла, начиная от корня дерева. Это означает, что процесс обновления элемента создает от одного до пяти узлов, каждый из которых содержит до 32 элементов или поддеревьев. Это, конечно, дороже, чем просто обновление элемента в изменяемом массиве, но все же намного дешевле, чем копирование вообще всего вектора. Поскольку вектора обладают хорошим балансом между быстрой случайной выборкой и быстрым случайным обновлением элементов, они используются в качестве реализации неизменяемых индексированных последовательностей: @@ -157,7 +151,7 @@ ArraySeqs хранят свои элементы в приватном [Масс Хэш деревья - это стандартный способ эффективного создания неизменяемых множеств и ассоциативных массивов (мап). [Compressed Hash-Array Mapped Prefix-trees](https://github.com/msteindorfer/oopsla15-artifact/) - это специальные хэш деревья для JVM, которые улучшают локальность и обеспечивают компактную и элегантную реализацию деревьев. Они базируются на классе [immutable.HashMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html). Их представление очень похоже на реализацию векторов, которые также являются деревьями, где каждый узел имеет либо 32 элемента либо 32 поддерева. Но в данном случае ключ выбирается на основе хэш-кода. Например, чтобы найти ключ на мапе, сначала берут хэш-код ключа. Затем самые младшие 5 бит хэш-кода используются для выбора первого поддерева, за которым следуют следующие 5 бит и так далее. Выбор прекращается, когда для всех битов будут найдены ключи. -Хэш деревья пытаются предоставить разумный баланс между достаточно быстрым поиском и достаточно эффективными операциями вставки (`+`) и удаления (`-`) элементов. Именно поэтому они лежат в основе стандартных реализаций Scala неизменяемых множеств и ассоциативных массивов (мап). На самом деле, в Scala есть дополнительная оптимизацию для неизменяемых множеств и мап, которые содержат менее пяти элементов. Множества и мапы от одного до четырех элементов хранятся как обычные объекты, которые содержат только элементы (или пары ключ/значение в случае мапы) как поля. Пустое неизменяемое множество и пустая неизменяемая мапа - это всегда объект-сингэлтон - нет необходимости размножать сущности для них, потому что пустое неизменяемое множество или мапа всегда будут оставаться пустыми. +Хэш деревья пытаются предоставить разумный баланс между достаточно быстрым поиском и достаточно эффективными операциями вставки (`+`) и удаления (`-`) элементов. Именно поэтому они лежат в основе стандартных реализаций Scala неизменяемых множеств и ассоциативных массивов (мап). На самом деле, в Scala есть дополнительная оптимизация для неизменяемых множеств и мап, которые содержат менее пяти элементов. Множества и мапы от одного до четырех элементов хранятся как обычные объекты, которые содержат только элементы (или пары ключ/значение в случае мапы) как поля. Пустое неизменяемое множество и пустая неизменяемая мапа - это всегда объект-сингэлтон - нет необходимости размножать сущности для них, потому что пустое неизменяемое множество или мапа всегда будут оставаться пустыми. ## Красно-Черные Деревья (Red-Black Trees) diff --git a/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md b/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md index 7152037ce9..1506db43ce 100644 --- a/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md +++ b/_ru/overviews/collections-2.13/concrete-mutable-collection-classes.md @@ -1,25 +1,19 @@ --- layout: multipage-overview title: Реализации Изменяемых Коллекций - -discourse: true - partof: collections-213 overview-name: Collections - num: 9 previous-page: concrete-immutable-collection-classes next-page: arrays - language: ru - --- Вы уже успели увидеть наиболее часто используемые неизменяемые типы коллекции, которые есть в стандартной библиотеке Scala. Настало время посмотреть на изменяемые (mutable) типы коллекции. ## Array Buffers -[ArrayBuffer](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) - буферизированный массив в своем буфере хранит массив и его размер. Большинство операций с буферизированным массивом выполняются с той же скоростью, что и с массивом, так как операции просто обращаются и изменяют исходный массив. Кроме того он может эффективно добавлять данные к своему концу. Присоединение элемента к такому массиву занимает амортизированное константное время. Поэтому буферизированные массивы будут полезны, если вы строите большую коллекцию данных регулярно добавляя новые элементы в конец. +[ArrayBuffer](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) - буферизированный массив в своем буфере хранит массив и его размер. Большинство операций с буферизированным массивом выполняются с той же скоростью, что и с массивом, так как операции просто обращаются и изменяют исходный массив. Кроме того, он может эффективно добавлять данные к своему концу. Присоединение элемента к такому массиву занимает амортизированное константное время. Поэтому буферизированные массивы будут полезны, если вы строите большую коллекцию данных регулярно добавляя новые элементы в конец. scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() @@ -45,7 +39,7 @@ language: ru ## StringBuilders -Так же, как буферизированный массив полезен для создания массивов, а буферизированный список полезен для построения списков, [StringBuilder](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) полезен для создания строк. StringBuilders настолько широко используются, что они уже импортированы по умолчанию в стандартную область видимости. Можете создать их с помощью `new StringBuilder`, как в следующем примере: +Так же как буферизированный массив полезен для создания массивов, а буферизированный список полезен для построения списков, [StringBuilder](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) полезен для создания строк. StringBuilders настолько широко используются, что они уже импортированы по умолчанию в стандартную область видимости. Можете создать их с помощью `new StringBuilder`, как в следующем примере: scala> val buf = new StringBuilder buf: StringBuilder = @@ -108,7 +102,7 @@ Scala предоставляет не только неизменяемые, н Последовательные массивы - это изменяемые массивы со свойствами последовательности фиксированного размера, которые хранят свои элементы внутри `Array[Object]`. Они реализованы в Scala классом [ArraySeq](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html). -Вам стоит использовать `ArraySeq`, если вам нужен массив из-за его показателей производительности, но вы дополнительно хотите использовать обобщеные экземпляры последовательности, в которых вы не знаете тип элементов и у которого нет `ClassTag` который будет предоставлен непосредственно во время исполнения. Эти аспекты рассматриваются в разделе [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). +Вам стоит использовать `ArraySeq`, если вам нужен массив из-за его показателей производительности, но вы дополнительно хотите использовать обобщеные экземпляры последовательности, в которых вы не знаете тип элементов и у которого нет `ClassTag` который будет предоставлен непосредственно во время исполнения. Эти аспекты рассматриваются в разделе [arrays]({% link _ru/overviews/collections-2.13/arrays.md %}). ## Hash Tables (Хэш Таблицы) @@ -146,11 +140,11 @@ Scala предоставляет не только неизменяемые, н | `m.replace(k, old, new)` |Заменяет значение, связанное с ключом `k` на `new`, если ранее оно было равно `old`. | | `m.replace (k, v)` | Заменяет значение, связанное с ключом `k` на `v`, если ранее значение вообще существовало.| -`concurrent.Map` это трейт в библиотеке коллекций Scala. В настоящее время он реализуется двумя способами. Первый - через Java мапу `java.util.concurrent.ConcurrentMap`, который может быть автоматически преобразован в Scala мапу с помощью [стандартного механизма преобразования Java/Scala коллекций]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html). Вторая реализация через [TrieMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), которая представляет из себя не блокируемую хэш-таблицу привязанную к дереву. +`concurrent.Map` это трейт в библиотеке коллекций Scala. В настоящее время он реализуется двумя способами. Первый - через Java мапу `java.util.concurrent.ConcurrentMap`, который может быть автоматически преобразован в Scala мапу с помощью [стандартного механизма преобразования Java/Scala коллекций]({% link _ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md %}). Вторая реализация через [TrieMap](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), которая представляет из себя не блокируемую хэш-таблицу привязанную к дереву. ## Mutable Bitsets (Изменяемый Битовый Набор) -Изменяемый набор типа [mutable.BitSet](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html) практически такойже как и неизменяемый набор, за исключением того, что он при изменении сам меняется. Изменяемые битовые наборы немного эффективнее при обновлении, чем неизменяемые, так как им не нужно копировать `Long`, которые не изменились. +Изменяемый набор типа [mutable.BitSet](https://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() diff --git a/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md b/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md index 2471e0121f..0320c0c59d 100644 --- a/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md +++ b/_ru/overviews/collections-2.13/conversions-between-java-and-scala-collections.md @@ -1,17 +1,11 @@ --- layout: multipage-overview title: Преобразования между Java и Scala коллекциями - -discourse: true - partof: collections-213 overview-name: Collections - num: 17 previous-page: creating-collections-from-scratch - language: ru - --- Как и в Scala, в Java есть богатая библиотека коллекций. Между ними много общего. Например, обе библиотеки предоставляют итераторы, итерируемые сущности, множества, мапы и списки. Но есть и серьезные различия. В частности, библиотека Scala фокусируют больше внимания на неизменяемых коллекциях, предоставляя больше возможностей для преобразования исходной коллекции в новую. diff --git a/_ru/overviews/collections-2.13/creating-collections-from-scratch.md b/_ru/overviews/collections-2.13/creating-collections-from-scratch.md index 1eacea990f..5956c8e4a2 100644 --- a/_ru/overviews/collections-2.13/creating-collections-from-scratch.md +++ b/_ru/overviews/collections-2.13/creating-collections-from-scratch.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Создание коллекций с нуля - -discourse: true - partof: collections-213 overview-name: Collections - num: 16 previous-page: iterators next-page: conversions-between-java-and-scala-collections - language: ru - --- У вас есть синтаксис `List(1, 2, 3)` для создания списка из трех целых чисел и `Map('A' -> 1, 'C' -> 2)` для создания мапы с двумя элементами. На самом деле, это универсальная функциональность коллекций Scala. Можно получить любую коллекцию написав ее название и указав следом список элементов в круглых скобках. В результате получится новая коллекция с заданными элементами. Вот еще несколько примеров: @@ -55,7 +49,7 @@ language: ru | `C.empty` | Пустая коллекция. | | `C(x, y, z)` | Коллекция состоящая из элементов `x, y, z`. | | `C.concat(xs, ys, zs)` | Коллекция, полученная путем объединения элементов `xs, ys, zs`. | -| `C.fill(n){e}` | Коллекция длины `n`, где каждый элемент вычисляется выражением`e`. | +| `C.fill(n){e}` | Коллекция длины `n`, где каждый элемент вычисляется выражением `e`. | | `C.fill(m, n){e}` | Коллекция коллекций размерности `m×n`, где каждый элемент вычисляется выражением `e`. (существует и в более высоких измерениях). | | `C.tabulate(n){f}` | Коллекция длины `n`, где элемент каждого индекса i вычисляется с помощью `f(i)`. | | `C.tabulate(m, n){f}` | Коллекция коллекций измерений `m×n`, где элемент каждого индекса `(i, j)` вычисляется с помощью `f(i, j)`. (существует также в более высоких измерениях). | diff --git a/_ru/overviews/collections-2.13/equality.md b/_ru/overviews/collections-2.13/equality.md index 5908d1ca2b..e05ee5ee73 100644 --- a/_ru/overviews/collections-2.13/equality.md +++ b/_ru/overviews/collections-2.13/equality.md @@ -1,21 +1,15 @@ --- layout: multipage-overview title: Равенство - -discourse: true - partof: collections-213 overview-name: Collections - num: 13 previous-page: performance-characteristics next-page: views - language: ru - --- -Коллекций придерживаются единого подхода к определению равенства и получению хэшей. Идея состоит, во-первых, в том, чтобы разделить коллекции на группы: множества, мапы и последовательности. Коллекции в разных группах всегда различаются. Например, `Set(1,2,3)` неравнозначен списку `List(1,2,3)`, хотя они содержат одни и те же элементы. С другой стороны, в рамках одной и той же группы коллекции равны тогда и только тогда, когда они имеют одинаковые элементы (для последовательностей: одни и те же элементы в одном порядке). Например `List(1, 2, 3) == Vector(1, 2, 3)`, и `HashSet(1, 2) == TreeSet(2, 1)`. +Коллекции придерживаются единого подхода к определению равенства и получению хэшей. Идея состоит, во-первых, в том, чтобы разделить коллекции на группы: множества, мапы и последовательности. Коллекции в разных группах всегда различаются. Например, `Set(1,2,3)` неравнозначен списку `List(1,2,3)`, хотя они содержат одни и те же элементы. С другой стороны, в рамках одной и той же группы коллекции равны тогда и только тогда, когда они имеют одинаковые элементы (для последовательностей: одни и те же элементы в одном порядке). Например `List(1, 2, 3) == Vector(1, 2, 3)`, и `HashSet(1, 2) == TreeSet(2, 1)`. Для проверки на равенство не важно, является ли коллекция изменяемой или неизменяемой. Для изменяемой коллекции достаточно просто рассмотреть ее текущие элементы на момент проведения проверки на равенство. Это означает, что коллекция в разные моменты может быть эквивалентна разным коллекциям, в зависимости от того, какие элементы в неё добавляются или удаляются. В этом кроится потенциальная опасность при использовании изменяемой коллекции в качестве ключа для хэшмапы. Пример: diff --git a/_ru/overviews/collections-2.13/introduction.md b/_ru/overviews/collections-2.13/introduction.md index c965ef979e..e97c142eb8 100644 --- a/_ru/overviews/collections-2.13/introduction.md +++ b/_ru/overviews/collections-2.13/introduction.md @@ -1,17 +1,11 @@ --- layout: multipage-overview title: Введение - -discourse: true - partof: collections-213 overview-name: Collections - num: 1 next-page: overview - language: ru - --- **Martin Odersky и Lex Spoon** diff --git a/_ru/overviews/collections-2.13/iterators.md b/_ru/overviews/collections-2.13/iterators.md index ee7c02c50d..3d43d76a8f 100644 --- a/_ru/overviews/collections-2.13/iterators.md +++ b/_ru/overviews/collections-2.13/iterators.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Итераторы - -discourse: true - partof: collections-213 overview-name: Collections - num: 15 previous-page: views next-page: creating-collections-from-scratch - language: ru - --- Итератор (Iterator) - это не коллекция, а скорее способ поочередного доступа к элементам коллекции. Есть две основные операции у итератора - это `next` и `hasNext`. Вызов метода `it.next()` на итераторе `it` вернет следующий элемент и изменит его состояние. Повторный вызов `next` на том же итераторе выведит следующий элемент идущий после ранее возвращённого. Если больше нет элементов для возврата, вызов команды `next` кинет исключение `NoSuchElementException`. Вы можете узнать, есть ли еще элементы для возврата с помощью метода `hasNext` у [Итератора](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). @@ -62,8 +56,8 @@ language: ru Обратите внимание еще раз, что сам итератор`it` был изменен вызовом `dropWhile`: теперь он указывает на второе слово `number` в списке. Фактически, `it` и результат `res4` возвращаемый после`dropWhile` возвращают одну и туже последовательность элементов. -Чтоб избежать такое поведение, как вариант можно использовать `duplicate` (дублировать используемый итератор), вызывая методы на разных итераторах. -Каждый из _двух_ итераторов будет обрабатывать точно такие же элементы, как и тот из которого состоит итераторатор `it`: +Чтобы избежать такое поведение, как вариант можно использовать `duplicate` (дублировать используемый итератор), вызывая методы на разных итераторах. +Каждый из _двух_ итераторов будет обрабатывать точно такие же элементы, как и тот, из которого состоит итераторатор `it`: scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate words: Iterator[String] = @@ -79,7 +73,7 @@ language: ru Может создаться впечатление что итератор подвергается двойному обходу над элементами, но это не так, результат достигается за счет внутреннего буферизации. Как обычно, базовый итератор `it` не пригоден для прямого использования и должен быть исключен из дальнейших операций. -Обобщая вышесказанное, итераторы ведут себя как коллекции _если после вызова метода на них сам итератор больше не вызывается_. В библиотеке коллекции Scala это достигается явным образом с помощью абстракции [IterableOnce](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html), который является общим суперкласом для [Iterable](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) и [Iterator](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). У `IterableOnce[A]` только два метода: `iterator: Iterator[A]` и `knownSize: Int`. +Обобщая вышесказанное, итераторы ведут себя как коллекции, _если после вызова метода на них сам итератор больше не вызывается_. В библиотеке коллекции Scala это достигается явным образом с помощью абстракции [IterableOnce](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IterableOnce.html), который является общим суперкласом для [Iterable](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) и [Iterator](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). У `IterableOnce[A]` только два метода: `iterator: Iterator[A]` и `knownSize: Int`. Если объект `IterableOnce` является `Iterator`, то его операция `iterator` всегда возвращает себя, в своем текущем состоянии, но если он `Iterable`, то операция `iterator` всегда возвращает новый `Iterator`. Типовой вариант использования `IterableOnce` - в качестве типа аргумента для методов, которые могут принимать или итератор или коллекцию в качестве аргумента. Примером может служить метод соединения `concat` в классе `Iterable`. Он принимает `IterableOnce` параметр, поэтому вы можете соединять элементы, поступающие или из итератора или коллекции. @@ -174,7 +168,7 @@ language: ru Поэтому выражение `(1 to 10).iterator.map(println)` не выведет ничего на экран. Метод `map` в данном случае не применяет функцию в аргументе к значениям в диапазоне, вместо этого будет возвращен новый `Iterator`, который будет выполнять операции тогда когда будет запрошен их результат. Добавление `.toList` в конец этого выражения фактически вызовет вывод элементов на печать. -Как следствие такие методы как `map` или `filter` не обязательно применят функцию в аргументе ко всем входным элементам. Выражение `(1 to 10).iterator.map(println).take(5).toList` выводит только значения от `1` до `5`, поскольку это те значения, которые запрашиваются у `Iterator`, возвращаемого из `map`. +Как следствие, такие методы как `map` или `filter` не обязательно применят функцию в аргументе ко всем входным элементам. Выражение `(1 to 10).iterator.map(println).take(5).toList` выводит только значения от `1` до `5`, поскольку это те значения, которые запрашиваются у `Iterator`, возвращаемого из `map`. Это одна из причин, того почему важно использовать только чистые функции в качестве аргументов для `map`, `filter`, `fold` и подобных методов. Помните, что чистая функция не имеет побочных эффектов, поэтому `println` обычно не используется в `map`. Здесь `println` используется лишь для демонстрации "ленивости", которую с чистыми функциями не заметно. diff --git a/_ru/overviews/collections-2.13/maps.md b/_ru/overviews/collections-2.13/maps.md index 18cfc11245..a3d93834fd 100644 --- a/_ru/overviews/collections-2.13/maps.md +++ b/_ru/overviews/collections-2.13/maps.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Мапы - -discourse: true - partof: collections-213 overview-name: Collections - num: 7 previous-page: sets next-page: concrete-immutable-collection-classes - language: ru - --- [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html) это [Iterable](https://www.scala-lang.org/api/current/scala/collection/Iterable.html) состоящее из пар ключ значение (также называемых _связкой_ или _ассоциативным массивом_). diff --git a/_ru/overviews/collections-2.13/overview.md b/_ru/overviews/collections-2.13/overview.md index b0fe21b1cd..c9205aeded 100644 --- a/_ru/overviews/collections-2.13/overview.md +++ b/_ru/overviews/collections-2.13/overview.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Изменяемые и Неизменяемые Коллекции - -discourse: true - partof: collections-213 overview-name: Collections - num: 2 previous-page: introduction next-page: trait-iterable - language: ru - --- В коллекциях Scala постоянно проводят различие между неизменяемыми и изменяемыми коллекциями. _Изменяемые_ (mutable) коллекции могут быть изменены или дополнены. Это означает, что вы можете изменять, добавлять или удалять её элементы. _Неизменяемые_ (Immutable) коллекции, напротив, никогда не меняются. У них есть операции, имитирующие добавления, удаления или обновления, но эти операции каждый раз будут возвращать новую коллекцию и оставлять старую коллекцию без изменений. @@ -29,7 +23,7 @@ language: ru является _базовой_ для обоих коллекций [collection.immutable.IndexedSeq\[T\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html) и [collection.mutable.IndexedSeq\[T\]](https://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html) -Как правило, базовые коллекции пакета `scala.collection` поддерживают операции преобразования, затрагивающие всю коллекцию, неизменяемые коллекции пакета `scala.collection.immutable` обычно добавляют операции добавления или удаления отдельных элементов, а изменяемые коллекции пакета `scala.collection.mutable` обычно добавляют к базовому интерфейсу, операции модификации элементов основанные на побочных эфектах. +Как правило, базовые коллекции пакета `scala.collection` поддерживают операции преобразования, затрагивающие всю коллекцию, неизменяемые коллекции пакета `scala.collection.immutable` обычно добавляют операции добавления или удаления отдельных элементов, а изменяемые коллекции пакета `scala.collection.mutable` обычно добавляют к базовому интерфейсу операции модификации элементов, основанные на побочных эффектах. Еще одним отличием базовой коллекции от неизменяемой является то, что пользователи неизменяемой коллекции имеют гарантию, что никто не сможет изменить коллекцию, а пользователи базовой коллекции лишь обещают не менять ее самостоятельно. Даже если тип такой коллекции не предоставляет никаких операций для модификации коллекции, все равно возможно, что эта коллекция, может быть изменена какими-либо сторонними пользователями. @@ -47,7 +41,7 @@ language: ru scala.collection.immutable.List // Полное объявление scala.List // объявление через псевдоним - List // тк scala._ всегда автоматически импортируется + List // т.к. scala._ всегда автоматически импортируется // можно просто указать имя коллекции Другие псевдонимы для типов diff --git a/_ru/overviews/collections-2.13/performance-characteristics.md b/_ru/overviews/collections-2.13/performance-characteristics.md index 526a74a2b2..281280075b 100644 --- a/_ru/overviews/collections-2.13/performance-characteristics.md +++ b/_ru/overviews/collections-2.13/performance-characteristics.md @@ -1,21 +1,15 @@ --- layout: multipage-overview title: Показатели производительности - -discourse: true - partof: collections-213 overview-name: Collections - num: 12 previous-page: strings next-page: equality - language: ru - --- -Из предыдущих объяснений стало ясно, что разные типы коллекций имеют разные показатели производительности. Что зачастую является основной причиной, выбора одной коллекции вместо другой. Показатели для наиболее распространенных операций собраны в таблицах ниже. +Из предыдущих объяснений стало ясно, что разные типы коллекций имеют разные показатели производительности. Что зачастую является основной причиной выбора одной коллекции вместо другой. Показатели для наиболее распространенных операций собраны в таблицах ниже. Показатели производительности на последовательностях: diff --git a/_ru/overviews/collections-2.13/seqs.md b/_ru/overviews/collections-2.13/seqs.md index c5557bdd40..49ef6d32b6 100644 --- a/_ru/overviews/collections-2.13/seqs.md +++ b/_ru/overviews/collections-2.13/seqs.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Последовательности. Трейт Seq, IndexedSeq и LinearSeq - -discourse: true - partof: collections-213 overview-name: Collections - num: 5 previous-page: trait-iterable next-page: sets - language: ru - --- Трейт [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html) представляет из себя последовательность. Последовательность - это своего рода итерируемая сущность, у которой есть длина (`length`) и элементы с фиксированным индексом, начинающийся от `0`. @@ -79,7 +73,7 @@ language: ru | `xs distinctBy f` |Подпоследовательность `xs`, которая не содержит дублирующего элемента после применения функции преобразования `f`. Например, `List("foo", "bar", "quux").distinctBy(_.length) == List("foo", "quux")`| У трейта [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html) есть два дочерних трейта [LinearSeq](https://www.scala-lang.org/api/current/scala/collection/LinearSeq.html), и [IndexedSeq](https://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). -Они не добавляют никаких новых операций, но у каждого из них разные характеристики производительности: у LinearSeq эффективные операции `head` и `tail`, в то время как у IndexedSeq эффективные операции `apply`, `length` и (если мутабельная) `update`. Часто используемые варианты LinearSeq - это `scala.collection.immutable.List` и `scala.collection.immutable.LazyList`. А наиболее часто используемые IndexedSeq - это `scala.Array` и `scala.collection.mutable.ArrayBuffer`. Класс `Vector` представляет собой компромисс между IndexedSeq и LinearSeq. У него эффективные, как обращение по индексу, так и последовательный обход элементов. Поэтому вектора хорошая основа для смешанных моделей доступа, где используются как индексированный, так и последовательный доступ. Позже мы расскажем больше о [векторах](concrete-immutable-collection-classes.html). +Они не добавляют никаких новых операций, но у каждого из них разные характеристики производительности: у LinearSeq эффективные операции `head` и `tail`, в то время как у IndexedSeq эффективные операции `apply`, `length` и (если мутабельная) `update`. Часто используемые варианты LinearSeq - это `scala.collection.immutable.List` и `scala.collection.immutable.LazyList`. А наиболее часто используемые IndexedSeq - это `scala.Array` и `scala.collection.mutable.ArrayBuffer`. Класс `Vector` представляет собой компромисс между IndexedSeq и LinearSeq. У него эффективные как обращение по индексу, так и последовательный обход элементов. Поэтому вектора хорошая основа для смешанных моделей доступа, где используются как индексированный, так и последовательный доступ. Позже мы расскажем больше о [векторах]({% link _ru/overviews/collections-2.13/concrete-immutable-collection-classes.md %}). В мутабельном варианте `IndexedSeq` добавляет операции преобразования ее элементов в самой коллекции (в отличие от таких операций как `map` и `sort`, доступных на базовом трейте `Seq`, для которых результат - это новая коллекция). diff --git a/_ru/overviews/collections-2.13/sets.md b/_ru/overviews/collections-2.13/sets.md index 3a2bbbf98e..d57e891797 100644 --- a/_ru/overviews/collections-2.13/sets.md +++ b/_ru/overviews/collections-2.13/sets.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Множества - -discourse: true - partof: collections-213 overview-name: Collections - num: 6 previous-page: seqs next-page: maps - language: ru - --- Множества (`Set`) - это итерируемые сущности, которые не содержат дублирующих друг друга элементов. Операции с множествами описаны в таблицах ниже. Описания включают операции для базовых, неизменяемых и изменяемых множеств. Все их операции поделены на следующие категории: @@ -111,14 +105,14 @@ language: ru У изменяемых множеств также есть операции `add` и `remove` как эквиваленты для `+=` и `-=`. Разница лишь в том, что команды `add` и `remove` возвращают логический результат, показывающий, повлияла ли операция на само множество или нет. -Текущая реализация изменяемого множества по умолчанию использует хэш-таблицу для хранения элементов множества. Реализация неизменяемого множества по умолчанию использует представление, которое адаптируется к количеству элементов множества. Пустое множество представлено объектом сингэлтоном. Множества размеров до четырех представлены одним объектом, в котором все элементы хранятся в виде полей. За пределами этого размера, неизменяемые множества реализованны в виде [Сжатого Отображенния Префиксного Дерева на Ассоциативном Массиве](concrete-immutable-collection-classes.html). +Текущая реализация изменяемого множества по умолчанию использует хэш-таблицу для хранения элементов множества. Реализация неизменяемого множества по умолчанию использует представление, которое адаптируется к количеству элементов множества. Пустое множество представлено объектом сингэлтоном. Множества размеров до четырех представлены одним объектом, в котором все элементы хранятся в виде полей. За пределами этого размера, неизменяемые множества реализованны в виде [Сжатого Отображенния Префиксного Дерева на Ассоциативном Массиве]({% link _ru/overviews/collections-2.13/concrete-immutable-collection-classes.md %}). Результатом такой схемы представления является то, что неизменяемые множества малых размеров (скажем, до 4), более компактны и более эффективны, чем изменяемые. Поэтому, если вы ожидаете, что размер множества будет небольшим, постарайтесь сделать его неизменяемым. Два дочерних трейта множеств `SortedSet` и `BitSet`. ### Отсортированное Множество (SortedSet) ### -[SortedSet](https://www.scala-lang.org/api/current/scala/collection/SortedSet.html) это множество, которое отдает свои элементы (используя `iterator` или `foreach`) в заданном порядке (который можно свободно задать в момент создания множества). Стандартное представление [SortedSet](https://www.scala-lang.org/api/current/scala/collection/SortedSet.html) - это упорядоченное двоичное дерево, которое поддерживает свойство того, что все элементы левого поддерева меньше, чем все элементы правого поддерева. Таким образом, простой упорядоченный обход может вернуть все элементы дерева в возрастающем порядке. Scala класс [immutable.TreeSet](https://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) базируется на _красно-черном_ дереве, в котором сохраняется тоже свойство но при этом само дерево является _сбалансированным_ --, то есть все пути от корня дерева до листа имеют длину, которая может отличаться друг от друга максимум на еденицу. +[SortedSet](https://www.scala-lang.org/api/current/scala/collection/SortedSet.html) это множество, которое отдает свои элементы (используя `iterator` или `foreach`) в заданном порядке (который можно свободно задать в момент создания множества). Стандартное представление [SortedSet](https://www.scala-lang.org/api/current/scala/collection/SortedSet.html) - это упорядоченное двоичное дерево, которое поддерживает свойство того, что все элементы левого поддерева меньше, чем все элементы правого поддерева. Таким образом, простой упорядоченный обход может вернуть все элементы дерева в возрастающем порядке. Scala класс [immutable.TreeSet](https://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) базируется на _красно-черном_ дереве, в котором сохраняется тоже свойство но при этом само дерево является _сбалансированным_ --, то есть все пути от корня дерева до листа имеют длину, которая может отличаться друг от друга максимум на единицу. При создании пустого [TreeSet](https://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html), можно сначала указать требуемый порядок: @@ -135,7 +129,7 @@ language: ru 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) diff --git a/_ru/overviews/collections-2.13/strings.md b/_ru/overviews/collections-2.13/strings.md index 1527860415..141b537e76 100644 --- a/_ru/overviews/collections-2.13/strings.md +++ b/_ru/overviews/collections-2.13/strings.md @@ -1,21 +1,15 @@ --- layout: multipage-overview title: Строки - -discourse: true - partof: collections-213 overview-name: Collections - num: 11 previous-page: arrays next-page: performance-characteristics - language: ru - --- -Как и массивы, строки не являются непосредственно последовательностями, но могут быть преобразованы в них, а также поддерживают все операции которые есть у последовательностей. Ниже приведены некоторые примеры операций, которые можно вызывать на строках. +Как и массивы, строки не являются непосредственно последовательностями, но могут быть преобразованы в них, а также поддерживают все операции, которые есть у последовательностей. Ниже приведены некоторые примеры операций, которые можно вызывать на строках. scala> val str = "hello" str: java.lang.String = hello diff --git a/_ru/overviews/collections-2.13/trait-iterable.md b/_ru/overviews/collections-2.13/trait-iterable.md index 25cfd6ed37..3af59d11d6 100644 --- a/_ru/overviews/collections-2.13/trait-iterable.md +++ b/_ru/overviews/collections-2.13/trait-iterable.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Трейт Iterable - -discourse: true - partof: collections-213 overview-name: Collections - num: 4 previous-page: overview next-page: seqs - language: ru - --- На самом верху иерархии коллекций находится трейт `Iterable`. Все методы в этого трейта описаны как абстрактные, `iterator` - это метод, который выдает элементы коллекции один за другим. @@ -35,9 +29,9 @@ language: ru * **Свертки** `foldLeft`, `foldRight`, `reduceLeft`, `reduceRight` которые применяют двуместную операцию к последовательным элементам. * **Определённых сверток** `sum`, `product`, `min`, `max`, которые работают над коллекциями конкретных типов (числовыми или сопоставимыми). * **Строковая** операции `mkString`, `addString`, `className`, которые дают альтернативные способы преобразования коллекции в строку. -* **Отображения** - это такая коллекция, которая лениво вычисляется. Позже мы расмотрим отображения [подробнее](views.html). +* **Отображения** - это такая коллекция, которая лениво вычисляется. Позже мы расмотрим отображения [подробнее]({% link _ru/overviews/collections-2.13/views.md %}). -В `Iterable` есть два метода, которые возвращают итераторы: `grouped` и `sliding`. Правда эти итераторы возвращают не отдельные элементы, а целые подпоследовательности элементов исходной коллекции. Максимальный размер таких подпоследовательностей задается аргументом. Метод `grouped` возвращает свои элементы "нарезанные" на фиксированные части, тогда как `sliding` возвращает результат "прохода окна" (заданной длинны) над элементами. Разница между ними станет очевидной, если взглянуть на следующий результат в консоли: +В `Iterable` есть два метода, которые возвращают итераторы: `grouped` и `sliding`. Правда, эти итераторы возвращают не отдельные элементы, а целые подпоследовательности элементов исходной коллекции. Максимальный размер таких подпоследовательностей задается аргументом. Метод `grouped` возвращает свои элементы "нарезанные" на фиксированные части, тогда как `sliding` возвращает результат "прохода окна" (заданной длинны) над элементами. Разница между ними станет очевидной, если взглянуть на следующий результат в консоли: scala> val xs = List(1, 2, 3, 4, 5) xs: List[Int] = List(1, 2, 3, 4, 5) diff --git a/_ru/overviews/collections-2.13/views.md b/_ru/overviews/collections-2.13/views.md index 8a095c96a1..135cbb0b50 100644 --- a/_ru/overviews/collections-2.13/views.md +++ b/_ru/overviews/collections-2.13/views.md @@ -1,18 +1,12 @@ --- layout: multipage-overview title: Отображения - -discourse: true - partof: collections-213 overview-name: Collections - num: 14 previous-page: equality next-page: iterators - language: ru - --- У коллекций довольно много вариантов создания новых коллекций. Ну например используя операции `map`, `filter` или `++`. Мы называем такие операции *трансформерами*, потому что они берут хотя бы одну коллекцию и трансформируют её в новую коллекцию. @@ -21,11 +15,11 @@ language: ru В качестве примера не строгого трансформера рассмотрим следующую реализацию операции создания ленивой мапы: - def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { - def iterator = coll.iterator map f + def lazyMap[T, U](iter: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = iter.iterator map f } -Обратите внимание, что `lazyMap` создает новую `Iterable` , не обходя все элементы коллекции `coll`. Функция `f` применяется к элементам новой коллекции `iterator` по мере запроса ее элементов. +Обратите внимание, что `lazyMap` создает новую `Iterable` , не обходя все элементы коллекции `iter`. Функция `f` применяется к элементам новой коллекции `iterator` по мере запроса ее элементов. Коллекции Scala по умолчанию используют строгий способ во всех своих трансфмерах, за исключением `LazyList`, который реализует свои трансформеры не строгими (ленивыми). Однако существует практичный способ превратить любую коллекцию в ленивую и _наоборот_, основанный на отображении коллекции. _Отображение_ представляет собой особый вид коллекции, которое реализует все трансформеры лениво. @@ -40,7 +34,7 @@ language: ru res5: scala.collection.immutable.Vector[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) -В последней строчке выражение `v map (_ + 1)` создает новый вектор, который при втором вызове в `map (_ * 2)` превращается в третий вектор. Как правило построение промежуточного результата от первого вызова мапы расточительно. В приведенном выше примере было бы быстрее составить единую мапу, состоящую из двух функций `(_ + 1)` и `(_ * 2)`. Если у вас обе функции доступны в одном и том же выражении, вы можете объеденить их вручную. Но довольно часто последовательные преобразования структуры данных выполняются в различных модулях программы. Слияние этих преобразований отрицательно скажется на модульности. Более универсальным способом избежать промежуточных результатов - это превратить вектор сначало в отображение, затем примененить все преобразования к отображению и, наконец, преобразовать отображение в вектор: +В последней строчке выражение `v map (_ + 1)` создает новый вектор, который при втором вызове в `map (_ * 2)` превращается в третий вектор. Как правило, построение промежуточного результата от первого вызова мапы расточительно. В приведенном выше примере было бы быстрее составить единую мапу, состоящую из двух функций `(_ + 1)` и `(_ * 2)`. Если у вас обе функции доступны в одном и том же выражении, вы можете объединить их вручную. Но довольно часто последовательные преобразования структуры данных выполняются в различных модулях программы. Слияние этих преобразований отрицательно скажется на модульности. Более универсальным способом избежать промежуточных результатов - это превратить вектор сначало в отображение, затем применить все преобразования к отображению и, наконец, преобразовать отображение в вектор: scala> (v.view map (_ + 1) map (_ * 2)).to(Vector) res12: scala.collection.immutable.Vector[Int] = @@ -51,7 +45,7 @@ language: ru scala> val vv = v.view vv: scala.collection.IndexedSeqView[Int] = IndexedSeqView() -Применение `v.view` дает нам `IndexedSeqView[Int]`, тоесть лениво вычисляемым `IndexedSeq[Int]`. Также как и `LazyList`, +Применение `v.view` дает нам `IndexedSeqView[Int]`, тоесть лениво вычисляемым `IndexedSeq[Int]`. Так же как и `LazyList`, метод `toString` на отображении не принуждает выводить элементы, вот почему содержимое `vv` выводится как `View(?)`. Применение первого `map` к отображению дает: diff --git a/_ru/overviews/collections/introduction.md b/_ru/overviews/collections/introduction.md index 4f503f94d6..5b996a9254 100644 --- a/_ru/overviews/collections/introduction.md +++ b/_ru/overviews/collections/introduction.md @@ -1,14 +1,9 @@ --- layout: multipage-overview title: Введение - -discourse: false - partof: collections overview-name: Collections - num: 1 - language: ru --- diff --git a/_ru/overviews/parallel-collections/architecture.md b/_ru/overviews/parallel-collections/architecture.md index 5ee10d99b9..1e8fbbf37c 100644 --- a/_ru/overviews/parallel-collections/architecture.md +++ b/_ru/overviews/parallel-collections/architecture.md @@ -1,9 +1,6 @@ --- layout: multipage-overview title: Архитектура библиотеки параллельных коллекций - -discourse: false - partof: parallel-collections overview-name: Parallel Collections @@ -51,7 +48,7 @@ _Примечание:_ Если есть два `Combiner`а, `c1` и `c2` гд Параллельные коллекции Scala во многом созданы под влиянием дизайна библиотеки (последовательных) коллекций Scala. На рисунке ниже показано, что их дизайн фактически отражает соответствующие трейты фреймворка обычных коллекций. -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) +[parallel Collections Hierarchy]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png)
Иерархия библиотеки Scala: коллекции и параллельные коллекции

diff --git a/_ru/overviews/parallel-collections/concrete-parallel-collections.md b/_ru/overviews/parallel-collections/concrete-parallel-collections.md index ab41a576c7..1bda571a82 100644 --- a/_ru/overviews/parallel-collections/concrete-parallel-collections.md +++ b/_ru/overviews/parallel-collections/concrete-parallel-collections.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Конкретные классы параллельных коллекций - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - language: ru num: 2 --- @@ -47,10 +43,10 @@ num: 2 [ParRange](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/parallel/immutable/ParRange.html) представляет собой упорядоченную последовательность элементов, отстоящих друг от друга на одинаковые промежутки. Параллельный диапазон создается подобно последовательному [Range](https://www.scala-lang.org/api/{{ site.scala-212-version }}/scala/collection/immutable/Range.html): - scala> 1 to 3 par + scala> (1 to 3).par res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - scala> 15 to 5 by -2 par + 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). При создании отображения (mapping) элементов параллельного диапазона получается параллельный вектор. Последовательные и параллельные диапазоны могут эффективно преобразовываться друг в друга вызовами методов `seq` и `par`. @@ -76,7 +72,7 @@ num: 2 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 + scala> phs.map(x => x * x).sum res0: Int = 332833500 [Компоновщики]({{ site.baseurl }}/overviews/parallel-collections/architecture.html) параллельных хэш-деревьев действуют аналогично компоновщикам хэш-таблиц, а именно предварительно распределяют элементы по блокам, а после этого параллельно составляют результирующее хэш-дерево, назначая обработку различных блоков разным процессорам, каждый из которых независимо собирает свое поддерево. @@ -92,19 +88,19 @@ num: 2 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) + | 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) реализованы как `TrieMap`-- так как эта структура является многопоточной, при вызове метода трансформации создается только один компоновщик, разделяемый всеми процессорами. @@ -114,53 +110,52 @@ num: 2 Характеристики производительности последовательных типов (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 | - | - | - | - | +| | 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 | - +| | lookup | add | remove | +| ------------------------- | ------ | --- | ------ | +| **неизменяемые** | | | | +| `ParHashSet`/`ParHashMap` | eC | eC | eC | +| **изменяемые** | | | | +| `ParHashSet`/`ParHashMap` | C | C | C | +| `ParTrieMap` | eC | eC | eC | ### Расшифровка Обозначения в двух представленных выше таблицах означают следующее: -| | | -| --- | ---- | -| **C** | Операция (быстрая) выполняется за постоянное время. | -| **eC** | Операция выполняется за фактически постоянное время, но только при соблюдении некоторых предположений, например о максимальной длине вектора или распределении хэш-кодов.| +| | | +| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **C** | Операция (быстрая) выполняется за постоянное время. | +| **eC** | Операция выполняется за фактически постоянное время, но только при соблюдении некоторых предположений, например о максимальной длине вектора или распределении хэш-кодов. | | **aC** | Операция выполняется за амортизированное постоянное время. Некоторые вызовы операции могут выполняться медленнее, но при подсчете времени выполнения большого количества операций выходит, что в среднем на операцию требуется постоянное время. | -| **Log** | Операция занимает время, пропорциональное логарифму размера коллекции. | -| **L** | Операция линейна, то есть занимает время, пропорциональное размеру коллекции. | -| **-** | Операция не поддерживается. | +| **Log** | Операция занимает время, пропорциональное логарифму размера коллекции. | +| **L** | Операция линейна, то есть занимает время, пропорциональное размеру коллекции. | +| **-** | Операция не поддерживается. | Первая таблица трактует последовательные типы-- изменяемые и неизменяемые-- в контексте выполнения следующих операций: -| | | -| --- | ---- | -| **head** | Получение первого элемента последовательности. | -| **tail** | Получение новой последовательности, состоящей из всех элементов исходной, кроме первого. | -| **apply** | Индексирование. | -| **update** | Функциональное обновление (с помощью `updated`) для неизменяемых последовательностей, обновление с побочными действиями (с помощью `update`) для изменяемых. | -| **prepend**| Добавление элемента в начало последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | -| **append** | Добавление элемента в конец последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | -| **insert** | Вставка элемента в выбранную позицию последовательности. Поддерживается только изменяемыми последовательностями. | +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **head** | Получение первого элемента последовательности. | +| **tail** | Получение новой последовательности, состоящей из всех элементов исходной, кроме первого. | +| **apply** | Индексирование. | +| **update** | Функциональное обновление (с помощью `updated`) для неизменяемых последовательностей, обновление с побочными действиями (с помощью `update`) для изменяемых. | +| **prepend** | Добавление элемента в начало последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | +| **append** | Добавление элемента в конец последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | +| **insert** | Вставка элемента в выбранную позицию последовательности. Поддерживается только изменяемыми последовательностями. | Вторая таблица рассматривает изменяемые и неизменяемые множества и ассоциативные массивы в контексте следующих операций: -| | | -| --- | ---- | +| | | +| ---------- | ---------------------------------------------------------------------------------------------- | | **lookup** | Проверка принадлежности элемента множеству, или получение значения, ассоциированного с ключом. | -| **add** | Добавление нового элемента во множество или новой пары ключ/значение в ассоциативный массив. | -| **remove** | Удаление элемента из множества или ключа из ассоциативного массива. | -| **min** | Минимальный элемент множества или минимальный ключ ассоциативного массива. | +| **add** | Добавление нового элемента во множество или новой пары ключ/значение в ассоциативный массив. | +| **remove** | Удаление элемента из множества или ключа из ассоциативного массива. | +| **min** | Минимальный элемент множества или минимальный ключ ассоциативного массива. | diff --git a/_ru/overviews/parallel-collections/configuration.md b/_ru/overviews/parallel-collections/configuration.md index 5dbd2b1e38..253c4c30d5 100644 --- a/_ru/overviews/parallel-collections/configuration.md +++ b/_ru/overviews/parallel-collections/configuration.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Конфигурирование параллельных коллекций - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - language: ru num: 7 --- diff --git a/_ru/overviews/parallel-collections/conversions.md b/_ru/overviews/parallel-collections/conversions.md index 114f2d8986..f001762841 100644 --- a/_ru/overviews/parallel-collections/conversions.md +++ b/_ru/overviews/parallel-collections/conversions.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Преобразования параллельных коллекций - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - language: ru num: 3 --- diff --git a/_ru/overviews/parallel-collections/ctries.md b/_ru/overviews/parallel-collections/ctries.md index 9e52d407bf..640ac7e8b9 100644 --- a/_ru/overviews/parallel-collections/ctries.md +++ b/_ru/overviews/parallel-collections/ctries.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Многопоточные префиксные деревья - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - language: ru num: 4 --- diff --git a/_ru/overviews/parallel-collections/custom-parallel-collections.md b/_ru/overviews/parallel-collections/custom-parallel-collections.md index 6fd6fa9f58..4dbcdf7e96 100644 --- a/_ru/overviews/parallel-collections/custom-parallel-collections.md +++ b/_ru/overviews/parallel-collections/custom-parallel-collections.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Создание пользовательской параллельной коллекции - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - language: ru num: 6 --- diff --git a/_ru/overviews/parallel-collections/overview.md b/_ru/overviews/parallel-collections/overview.md index 2d68d91d8d..e2b4bbcd7d 100644 --- a/_ru/overviews/parallel-collections/overview.md +++ b/_ru/overviews/parallel-collections/overview.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Обзор - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - num: 1 language: ru --- diff --git a/_ru/overviews/parallel-collections/performance.md b/_ru/overviews/parallel-collections/performance.md index b86e90bc2c..64e533e359 100644 --- a/_ru/overviews/parallel-collections/performance.md +++ b/_ru/overviews/parallel-collections/performance.md @@ -1,12 +1,8 @@ --- layout: multipage-overview title: Измерение производительности - -discourse: false - partof: parallel-collections overview-name: Parallel Collections - num: 8 language: ru --- diff --git a/_ru/scala3/book/ca-context-bounds.md b/_ru/scala3/book/ca-context-bounds.md new file mode 100644 index 0000000000..91c18c5101 --- /dev/null +++ b/_ru/scala3/book/ca-context-bounds.md @@ -0,0 +1,141 @@ +--- +layout: multipage-overview +title: Контекстные границы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе представлены контекстные границы в Scala 3. +language: ru +num: 62 +previous-page: ca-context-parameters +next-page: ca-given-imports +--- + +Во многих ситуациях имя [контекстного параметра]({% link _overviews/scala3-book/ca-context-parameters.md %}#context-parameters) +не нужно указывать явно, поскольку оно используется компилятором только в синтезированных аргументах для других параметров контекста. +В этом случае вам не нужно определять имя параметра, а можно просто указать тип. + +## Предыстория + +Например, рассмотрим метод `maxElement`, возвращающий максимальное значение в коллекции: + +{% tabs context-bounds-max-named-param class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +```scala +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)(ord)) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +def maxElement[A](as: List[A])(using ord: Ord[A]): A = + as.reduceLeft(max(_, _)(using ord)) +``` + +{% endtab %} + +{% endtabs %} + +Метод `maxElement` принимает _контекстный параметр_ типа `Ord[A]` только для того, +чтобы передать его в качестве аргумента методу `max`. + +Для полноты приведем определения `max` и `Ord` +(обратите внимание, что на практике мы будем использовать существующий метод `max` для `List`, +но мы создали этот пример для иллюстрации): + +{% tabs context-bounds-max-ord class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +```scala +/** Определяет, как сравнивать значения типа `A` */ +trait Ord[A] { + def greaterThan(a1: A, a2: A): Boolean +} + +/** Возвращает максимальное из двух значений */ +def max[A](a1: A, a2: A)(implicit ord: Ord[A]): A = + if (ord.greaterThan(a1, a2)) a1 else a2 +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +/** Определяет, как сравнивать значения типа `A` */ +trait Ord[A]: + def greaterThan(a1: A, a2: A): Boolean + +/** Возвращает максимальное из двух значений */ +def max[A](a1: A, a2: A)(using ord: Ord[A]): A = + if ord.greaterThan(a1, a2) then a1 else a2 +``` + +{% endtab %} + +{% endtabs %} + +Обратите внимание, что метод `max` принимает контекстный параметр типа `Ord[A]`, как и метод `maxElement`. + +## Пропуск контекстных аргументов + +Так как `ord` - это контекстный параметр в методе `max`, +компилятор может предоставить его для нас в реализации `maxElement` при вызове `max`: + +{% tabs context-bounds-context class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +```scala +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +def maxElement[A](as: List[A])(using Ord[A]): A = + as.reduceLeft(max(_, _)) +``` + +Обратите внимание: поскольку нам не нужно явно передавать его методу `max`, +мы можем не указывать его имя в определении метода `maxElement`. +Это _анонимный параметр контекста_. + +{% endtab %} + +{% endtabs %} + +## Границы контекста + +Учитывая написанное выше, _привязка к контексту_ — это сокращенный синтаксис +для выражения шаблона "параметр контекста, применяемый к параметру типа". + +Используя привязку к контексту, метод `maxElement` можно записать следующим образом: + +{% tabs context-bounds-max-rewritten %} + +{% tab 'Scala 2 и 3' %} + +```scala +def maxElement[A: Ord](as: List[A]): A = + as.reduceLeft(max(_, _)) +``` + +{% endtab %} + +{% endtabs %} + +Привязка типа `: Ord` к параметру типа `A` метода или класса указывает на параметр контекста с типом `Ord[A]`. +Под капотом компилятор преобразует этот синтаксис в тот, который показан в разделе "Предыстория". + +Дополнительные сведения о границах контекста см. в разделе ["Что такое границы контекста?"]({% link _overviews/FAQ/index.md %}#what-are-context-bounds) раздел FAQ по Scala. diff --git a/_ru/scala3/book/ca-context-parameters.md b/_ru/scala3/book/ca-context-parameters.md new file mode 100644 index 0000000000..dc66f2fc98 --- /dev/null +++ b/_ru/scala3/book/ca-context-parameters.md @@ -0,0 +1,196 @@ +--- +layout: multipage-overview +title: Параметры контекста +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как объявлять параметры контекста и как компилятор выводит их на стороне вызова. +language: ru +num: 61 +previous-page: ca-extension-methods +next-page: ca-context-bounds +--- + +Scala предлагает две важные функции для контекстной абстракции: + +- **Параметры контекста** позволяют указать параметры, которые на стороне вызова могут быть опущены программистом + и должны автоматически предоставляться контекстом. +- **Экземпляры given** (в Scala 3) или **неявные определения** (в Scala 2) — это термины, + которые компилятор Scala может использовать для заполнения отсутствующих аргументов. + +## Параметры контекста + +При проектировании системы зачастую необходимо предоставлять контекстную информацию, +такую как конфигурация или настройки, различным компонентам вашей системы. +Одним из распространенных способов добиться этого является передача конфигурации +в качестве дополнительного аргумента методам. + +В следующем примере мы определяем кейс класс `Config` для моделирования некоторой конфигурации веб-сайта +и передаем ее в различных методах. + +{% tabs example %} +{% tab 'Scala 2 и 3' %} + +```scala +case class Config(port: Int, baseUrl: String) + +def renderWebsite(path: String, config: Config): String = + "" + renderWidget(List("cart"), config) + "" + +def renderWidget(items: List[String], config: Config): String = ??? + +val config = Config(8080, "docs.scala-lang.org") +renderWebsite("/home", config) +``` + +{% endtab %} +{% endtabs %} + +Предположим, что конфигурация не меняется на протяжении большей части нашей кодовой базы. +Передача `config` каждому вызову метода (например `renderWidget`) становится очень утомительной +и делает нашу программу более трудной для чтения, поскольку нам нужно игнорировать аргумент `config`. + +### Установка параметров как контекстных + +Мы можем пометить некоторые параметры наших методов как _контекстные_. + +{% tabs 'contextual-parameters' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def renderWebsite(path: String)(implicit config: Config): String = + "" + renderWidget(List("cart")) + "" + // ^ + // аргумент config больше не требуется + +def renderWidget(items: List[String])(implicit config: Config): String = ??? +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +def renderWebsite(path: String)(using config: Config): String = + "" + renderWidget(List("cart")) + "" + // ^ + // аргумент config больше не требуется + +def renderWidget(items: List[String])(using config: Config): String = ??? +``` + +{% endtab %} +{% endtabs %} + +Начав секцию параметров с ключевого слова `using` в Scala 3 или `implicit` в Scala 2, мы сообщаем компилятору, +что на стороне вызова он должен автоматически найти аргумент с необходимым типом. +Таким образом, компилятор Scala выполняет **вывод термов**. + +При вызове `renderWidget(List("cart"))` компилятор Scala увидит, что в области видимости есть терм типа `Config` +(в нашем случае - `config`) и автоматически предоставит его для `renderWidget`. +Таким образом, программа эквивалентна приведенной выше. + +На самом деле, поскольку в реализации `renderWebsite` больше не нужно ссылаться на `config`, +мы можем даже опустить его имя в подписи в Scala 3: + +{% tabs 'anonymous' %} +{% tab 'Только в Scala 3' %} + +```scala +// нет необходимости придумывать имя параметра +// vvvvvvvvvvvvv +def renderWebsite(path: String)(using Config): String = + "" + renderWidget(List("cart")) + "" +``` + +{% endtab %} +{% endtabs %} + +В Scala 2 именовать неявные параметры по-прежнему необходимо. + +### Явное указание контекстных параметров + +Мы увидели, как _абстрагироваться_ от контекстных параметров +и что компилятор Scala может автоматически предоставлять нам аргументы. +Но как мы можем указать, какую конфигурацию использовать для нашего вызова `renderWebsite`? + +{% tabs 'explicit' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +Мы явно указываем значение аргумента, как если бы это был обычный аргумент: + +```scala +renderWebsite("/home")(config) +``` + +{% endtab %} +{% tab 'Scala 3' %} + +Подобно тому, как мы указали наш раздел параметров с помощью `using`, +мы также можем явно указать контекстные параметры с помощью `using`: + +```scala +renderWebsite("/home")(using config) +``` + +{% endtab %} +{% endtabs %} + +Явное предоставление контекстных параметров может быть полезно, +когда у нас в области видимости есть несколько разных значений, +подходящих по типу, и мы хотим убедиться в корректности передачи параметра методу. + +Для всех остальных случаев, как мы увидим в следующем разделе, +есть еще один способ ввести контекстуальные значения в область видимости. + +## Экземпляры given (определения implicit в Scala 2) + +Мы видели, что можем явно передавать аргументы в качестве контекстных параметров. +Однако, если для определенного типа существует _единственное каноническое значение_, +есть другой предпочтительный способ сделать его доступным для компилятора Scala: +пометив его как `given` в Scala 3 или `implicit` в Scala 2. + +{% tabs 'instances' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +implicit val config: Config = Config(8080, "docs.scala-lang.org") +// ^^^^^^ +// это значение, которое выведет компилятор Scala +// в качестве аргумента контекстного параметра типа Config +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val config = Config(8080, "docs.scala-lang.org") + +// это тип, который мы хотим предоставить для канонического значения +// vvvvvv +given Config = config +// ^^^^^^ +// это значение, которое выведет компилятор Scala +// в качестве аргумента контекстного параметра типа Config +``` + +{% endtab %} +{% endtabs %} + +В приведенном выше примере мы указываем, что всякий раз, +когда в текущей области видимости опущен контекстный параметр типа `Config`, +компилятор должен вывести `config` в качестве аргумента. + +Определив каноническое значение для типа `Config`, +мы можем вызвать `renderWebsite` следующим образом: + +```scala +renderWebsite("/home") +// ^ +// снова без аргумента +``` + +Подробное руководство о том, где Scala ищет канонические значения, можно найти в [FAQ]({% link _overviews/FAQ/index.md %}#where-does-scala-look-for-implicits). + +[reference]: {{ site.scala3ref }}/overview.html +[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html diff --git a/_ru/scala3/book/ca-contextual-abstractions-intro.md b/_ru/scala3/book/ca-contextual-abstractions-intro.md new file mode 100644 index 0000000000..eef3910c30 --- /dev/null +++ b/_ru/scala3/book/ca-contextual-abstractions-intro.md @@ -0,0 +1,96 @@ +--- +layout: multipage-overview +title: Контекстные абстракции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлено введение в концепцию контекстных абстракций Scala 3. +language: ru +num: 59 +previous-page: types-others +next-page: ca-extension-methods +--- + +## Предпосылка + +Контекстные абстракции — это способ абстрагироваться от контекста. +Они представляют собой единую парадигму с большим разнообразием вариантов использования, среди которых: + +- реализация тайп классов (_type classes_) +- установление контекста +- внедрение зависимости (_dependency injection_) +- выражение возможностей +- вычисление новых типов и доказательство взаимосвязей между ними + +В этом отношении Scala оказала влияние на другие языки. Например, трейты в Rust или protocol extensions Swift. +Предложения по дизайну также представлены для Kotlin в качестве разрешения зависимостей во время компиляции, +для C# в качестве Shapes и Extensions или для F# в качестве Traits. +Контекстные абстракции также являются общей особенностью средств доказательства теорем, таких как Coq или Agda. + +Несмотря на то, что в этих проектах используется разная терминология, +все они являются вариантами основной идеи вывода терминов (term inference): +учитывая тип, компилятор синтезирует "канонический" термин, который имеет этот тип. + +## Редизайн в Scala 3 + +В Scala 2 контекстные абстракции поддерживаются пометкой `implicit` определений (методов и значений) или параметров +(см. [Параметры контекста]({% link _overviews/scala3-book/ca-context-parameters.md %})). + +Scala 3 включает в себя переработку контекстных абстракций. +Хотя эти концепции постепенно "открывались" в Scala 2, теперь они хорошо известны и понятны, и редизайн использует эти знания. + +Дизайн Scala 3 фокусируется на **намерении**, а не на **механизме**. +Вместо того, чтобы предлагать одну очень мощную функцию имплицитов, +Scala 3 предлагает несколько функций, ориентированных на варианты использования: + +- **Расширение классов задним числом**. + В Scala 2 методы расширения должны были кодироваться с использованием [неявных преобразований][implicit-conversions] или [неявных классов]({% link _overviews/core/implicit-classes.md %}). + Напротив, в Scala 3 [методы расширения][extension-methods] теперь встроены непосредственно в язык, что приводит к улучшению сообщений об ошибках и улучшению вывода типов. + +- **Абстрагирование контекстной информации**. + [Предложения Using][givens] позволяют программистам абстрагироваться от информации, + которая доступна в контексте вызова и должна передаваться неявно. + В качестве улучшения по сравнению со Scala 2 подразумевается, что предложения using могут быть указаны по типу, + освобождая сигнатуры функций от имен переменных, на которые никогда не ссылаются явно. + +- **Предоставление экземпляров тайп-классов**. + [Given экземпляры][givens] позволяют программистам определять _каноническое значение_ определенного типа. + Это делает программирование с [тайп-классами][type-classes] более простым без утечек деталей реализации. + +- **Неявное преобразование одного типа в другой**. + Неявное преобразование было [переработано с нуля][implicit-conversions] как экземпляры тайп-класса `Conversion`. + +- **Контекстные абстракции высшего порядка**. + _Совершенно новая_ функция [контекстных функций][contextual-functions] делает контекстные абстракции объектами первого класса. + Они являются важным инструментом для авторов библиотек и позволяют выражать лаконичный DSL. + +- **Полезная обратная связь от компилятора**. + Если компилятор не может разрешить неявный параметр, теперь он предлагает [предложения по импорту](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html), которые могут решить проблему. + +## Преимущества + +Эти изменения в Scala 3 обеспечивают лучшее разделение вывода терминов от остального языка: + +- существует единственный способ определить данные +- существует единственный способ ввести неявные параметры и аргументы +- существует отдельный способ [импорта givens][given-imports], который не позволяет им прятаться в море обычного импорта +- существует единственный способ определить [неявное преобразование][implicit-conversions], которое четко обозначено как таковое и не требует специального синтаксиса + +К преимуществам этих изменений относятся: + +- новый дизайн позволяет избежать взаимодействия функций и делает язык более согласованным +- implicits становятся более легкими для изучения и более сложными для злоупотреблений +- значительно улучшается ясность 95% программ Scala, использующих implicits +- есть потенциал, чтобы сделать вывод термов однозначным способом, который также доступен и удобен. + +В этой главе в следующих разделах представлены многие из этих новых функций. + +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} +[given-imports]: {% link _overviews/scala3-book/ca-given-imports.md %} +[implicit-conversions]: {% link _overviews/scala3-book/ca-implicit-conversions.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[context-bounds]: {% link _overviews/scala3-book/ca-context-bounds.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} +[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html diff --git a/_ru/scala3/book/ca-extension-methods.md b/_ru/scala3/book/ca-extension-methods.md new file mode 100644 index 0000000000..6f530b141f --- /dev/null +++ b/_ru/scala3/book/ca-extension-methods.md @@ -0,0 +1,145 @@ +--- +layout: multipage-overview +title: Методы расширения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе представлена работа методов расширения в Scala 3. +language: ru +num: 60 +previous-page: ca-contextual-abstractions-intro +next-page: ca-context-parameters +versionSpecific: true +--- + +В Scala 2 аналогичного результата можно добиться с помощью [неявных классов]({% link _overviews/core/implicit-classes.md %}). + +--- + +Методы расширения позволяют добавлять методы к типу после того, как он был определен, +т.е. они позволяют добавлять новые методы в закрытые классы. +Например, представьте, что кто-то создал класс `Circle`: + +{% tabs ext1 %} +{% tab 'Scala 2 и 3' %} + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +{% endtab %} +{% endtabs %} + +Теперь представим, что необходим метод `circumference`, но нет возможности изменить исходный код `Circle`. +До того как концепция вывода терминов была введена в языки программирования, +единственное, что можно было сделать, это написать метод в отдельном классе или объекте, подобном этому: + +{% tabs ext2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object CircleHelpers { + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object CircleHelpers: + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +``` + +{% endtab %} +{% endtabs %} + +Затем этот метод можно было использовать следующим образом: + +{% tabs ext3 %} +{% tab 'Scala 2 и 3' %} + +```scala +val aCircle = Circle(2, 3, 5) + +// без использования метода расширения +CircleHelpers.circumference(aCircle) +``` + +{% endtab %} +{% endtabs %} + +Но методы расширения позволяют создать метод `circumference` для работы с экземплярами `Circle`: + +{% tabs ext4 %} +{% tab 'Только в Scala 3' %} + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 +``` + +{% endtab %} +{% endtabs %} + +В этом коде: + +- `Circle` — это тип, к которому будет добавлен метод расширения `circumference` +- Синтаксис `c: Circle` позволяет ссылаться на переменную `c` в методах расширения + +Затем в коде метод `circumference` можно использовать так же, как если бы он был изначально определен в классе `Circle`: + +{% tabs ext5 %} +{% tab 'Только в Scala 3' %} + +```scala +aCircle.circumference +``` + +{% endtab %} +{% endtabs %} + +### Импорт методов расширения + +Представим, что `circumference` определен в пакете `lib` - его можно импортировать с помощью + +{% tabs ext6 %} +{% tab 'Только в Scala 3' %} + +```scala +import lib.circumference + +aCircle.circumference +``` + +{% endtab %} +{% endtabs %} + +Если импорт отсутствует, то компилятор выводит подробное сообщение об ошибке, подсказывая возможный импорт, например так: + +```text +value circumference is not a member of Circle, but could be made available as an extension method. + +The following import might fix the problem: + + import lib.circumference +``` + +## Обсуждение + +Ключевое слово `extension` объявляет о намерении определить один или несколько методов расширения для типа, заключенного в круглые скобки. +Чтобы определить для типа несколько методов расширения, используется следующий синтаксис: + +{% tabs ext7 %} +{% tab 'Только в Scala 3' %} + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/scala3/book/ca-given-imports.md b/_ru/scala3/book/ca-given-imports.md new file mode 100644 index 0000000000..41439a27be --- /dev/null +++ b/_ru/scala3/book/ca-given-imports.md @@ -0,0 +1,54 @@ +--- +layout: multipage-overview +title: Given импорты +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как работают операторы импорта 'given' в Scala 3. +language: ru +num: 63 +previous-page: ca-context-bounds +next-page: ca-type-classes +versionSpecific: true +--- + +Для большей ясности, откуда берутся данные в текущей области видимости, +для импорта экземпляров `given` используется специальная форма оператора `import`. +Базовая форма показана в этом примере: + +```scala +object A: + class TC + given tc: TC = ??? + def f(using TC) = ??? + +object B: + import A.* // импорт всех не-given элементов + import A.given // импорт экземпляров given +``` + +В этом коде предложение `import A.*` объекта `B` импортирует все элементы `A`, _кроме_ `given` экземпляра `tc`. +И наоборот, второй импорт, `import A.given`, импортирует _только_ экземпляр `given`. +Два предложения импорта также могут быть объединены в одно: + +```scala +object B: + import A.{given, *} +``` + +## Обсуждение + +Селектор с подстановочным знаком `*` помещает в область видимости все определения, кроме given-ов или расширений, +тогда как селектор `given` помещает в область видимости _все_ given-ы, включая те, которые являются результатом расширений. + +Эти правила имеют два основных преимущества: + +- понятнее, откуда берутся данные в текущей области видимости. + В частности, невозможно скрыть импортированные given-ы в длинном списке других импортов. +- есть возможность импортировать все given, не импортируя ничего другого. + Это важно, потому что given-ы могут быть анонимными, поэтому обычное использование именованного импорта нецелесообразно. + +Дополнительные примеры синтаксиса "import given" показаны в главе ["Пакеты и импорт"][imports]. + +[imports]: {% link _overviews/scala3-book/packaging-imports.md %} diff --git a/_ru/scala3/book/ca-implicit-conversions.md b/_ru/scala3/book/ca-implicit-conversions.md new file mode 100644 index 0000000000..f4703dc075 --- /dev/null +++ b/_ru/scala3/book/ca-implicit-conversions.md @@ -0,0 +1,236 @@ +--- +layout: multipage-overview +title: Неявное преобразование типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице демонстрируется, как реализовать неявное преобразование типов в Scala 3. +language: ru +num: 66 +previous-page: ca-multiversal-equality +next-page: ca-summary +--- + +Неявные преобразования — это мощная функция Scala, позволяющая пользователям предоставлять аргумент одного типа, +как если бы он был другого типа, чтобы избежать шаблонного преобразования. + +> Обратите внимание, что в Scala 2 неявные преобразования также использовались для предоставления дополнительных членов +> запечатанным классам (см. [Неявные классы]({% link _overviews/core/implicit-classes.md %})). +> В Scala 3 мы рекомендуем использовать эту функциональность, определяя методы расширения вместо неявных преобразований +> (хотя стандартная библиотека по-прежнему полагается на неявные преобразования по историческим причинам). + +## Пример + +Рассмотрим, например, метод `findUserById`, принимающий параметр типа `Long`: + +{% tabs implicit-conversions-1 %} +{% tab 'Scala 2 и 3' %} + +```scala +def findUserById(id: Long): Option[User] +``` + +{% endtab %} +{% endtabs %} + +Для краткости опустим определение типа `User` - это не имеет значения для нашего примера. + +В Scala есть возможность вызвать метод `findUserById` с аргументом типа `Int` вместо ожидаемого типа `Long`, +потому что аргумент будет неявно преобразован в тип `Long`: + +{% tabs implicit-conversions-2 %} +{% tab 'Scala 2 и 3' %} + +```scala +val id: Int = 42 +findUserById(id) // OK +``` + +{% endtab %} +{% endtabs %} + +Этот код не упадет с ошибкой компиляции “type mismatch: expected `Long`, found `Int`”, +потому что есть неявное преобразование, которое преобразует аргумент `id` в значение типа `Long`. + +## Детальное объяснение + +В этом разделе описывается, как определять и использовать неявные преобразования. + +### Определение неявного преобразования + +{% tabs implicit-conversions-3 class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +В Scala 2 неявное преобразование из типа `S` в тип `T` определяется [неявным классом]({% link _overviews/core/implicit-classes.md %}) `T`, +который принимает один параметр конструктора типа `S`, [неявное значение]({% link _overviews/scala3-book/ca-context-parameters.md %}) +типа функции `S => T` или неявный метод, преобразуемый в значение этого типа. + +Например, следующий код определяет неявное преобразование из `Int` в `Long`: + +```scala +import scala.language.implicitConversions + +implicit def int2long(x: Int): Long = x.toLong +``` + +Это неявный метод, преобразуемый в значение типа `Int => Long`. + +См. раздел "Остерегайтесь силы неявных преобразований" ниже для объяснения пункта `import scala.language.implicitConversions` в начале. +{% endtab %} + +{% tab 'Scala 3' %} +В Scala 3 неявное преобразование типа `S` в тип `T` определяется [`given` экземпляром]({% link _overviews/scala3-book/ca-context-parameters.md %}) +типа `scala.Conversion[S, T]`. +Для совместимости со Scala 2 его также можно определить неявным методом (подробнее читайте во вкладке Scala 2). + +Например, этот код определяет неявное преобразование из `Int` в `Long`: + +```scala +given int2long: Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong +``` + +Как и другие given определения, неявные преобразования могут быть анонимными: + +```scala +given Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong +``` + +Используя псевдоним, это можно выразить более кратко: + +```scala +given Conversion[Int, Long] = (x: Int) => x.toLong +``` + +{% endtab %} + +{% endtabs %} + +### Использование неявного преобразования + +Неявные преобразования применяются в двух случаях: + +1. Если выражение `e` имеет тип `S` и `S` не соответствует ожидаемому типу выражения `T`. +2. В выборе `e.m` с `e` типа `S`, где `S` не определяет `m` + (для поддержки [методов расширения][extension methods] в стиле Scala-2). + +В первом случае ищется конверсия `c`, применимая к `e` и тип результата которой соответствует `T`. + +В примере выше, когда мы передаем аргумент `id` типа `Int` в метод `findUserById`, +вставляется неявное преобразование `int2long(id)`. + +Во втором случае ищется преобразование `c`, применимое к `e` и результат которого содержит элемент с именем `m`. + +Примером является сравнение двух строк `"foo" < "bar"`. +В этом случае `String` не имеет члена `<`, поэтому вставляется неявное преобразование `Predef.augmentString("foo") < "bar"` +(`scala.Predef` автоматически импортируется во все программы Scala.). + +### Как неявные преобразования становятся доступными? + +Когда компилятор ищет подходящие преобразования: + +- во-первых, он смотрит в текущую лексическую область + - неявные преобразования, определенные в текущей области или во внешних областях + - импортированные неявные преобразования + - неявные преобразования, импортированные с помощью импорта подстановочных знаков (только в Scala 2) +- затем он просматривает [сопутствующие объекты][companion objects], _связанные_ с типом аргумента `S` или ожидаемым типом `T`. + Сопутствующие объекты, связанные с типом `X`: + - сам объект-компаньон `X` + - сопутствующие объекты, связанные с любым из унаследованных типов `X` + - сопутствующие объекты, связанные с любым аргументом типа в `X` + - если `X` - это внутренний класс, внешние объекты, в которые он встроен + +Например, рассмотрим неявное преобразование `fromStringToUser`, определенное в объекте `Conversions`: + +{% tabs implicit-conversions-4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.language.implicitConversions + +object Conversions { + implicit def fromStringToUser(name: String): User = (name: String) => User(name) +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object Conversions: + given fromStringToUser: Conversion[String, User] = (name: String) => User(name) +``` + +{% endtab %} +{% endtabs %} + +Следующие операции импорта эквивалентно передают преобразование в область действия: + +{% tabs implicit-conversions-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import Conversions.fromStringToUser +// или +import Conversions._ +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import Conversions.fromStringToUser +// или +import Conversions.given +// или +import Conversions.{given Conversion[String, User]} +``` + +Обратите внимание, что в Scala 3 импорт с подстановочными знаками (т.е. `import Conversions.*`) +не импортирует given определения. + +{% endtab %} +{% endtabs %} + +Во вводном примере преобразование из `Int` в `Long` не требует импорта, поскольку оно определено в объекте `Int`, +который является сопутствующим объектом типа `Int`. + +Дополнительная литература: +[Где Scala ищет неявные значения? (в Stackoverflow)](https://stackoverflow.com/a/5598107). + +### Остерегайтесь силы неявных преобразований + +{% tabs implicit-conversions-6 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +Поскольку неявные преобразования могут иметь подводные камни, если используются без разбора, +компилятор предупреждает при компиляции определения неявного преобразования. + +Чтобы отключить предупреждения, выполните одно из следующих действий: + +- Импорт `scala.language.implicitConversions` в область определения неявного преобразования +- Вызвать компилятор с командой `-language:implicitConversions` + +Предупреждение не выдается, когда компилятор применяет преобразование. +{% endtab %} +{% tab 'Scala 3' %} +Поскольку неявные преобразования могут иметь подводные камни, если они используются без разбора, +компилятор выдает предупреждение в двух случаях: + +- при компиляции определения неявного преобразования в стиле Scala 2. +- на стороне вызова, где given экземпляр `scala.Conversion` вставляется как конверсия. + +Чтобы отключить предупреждения, выполните одно из следующих действий: + +- Импортировать `scala.language.implicitConversions` в область: + - определения неявного преобразования в стиле Scala 2 + - стороны вызова, где given экземпляр `scala.Conversion` вставляется как конверсия. +- Вызвать компилятор с командой `-language:implicitConversions` + {% endtab %} + {% endtabs %} + +[extension methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[companion objects]: {% link _overviews/scala3-book/domain-modeling-tools.md %}#companion-objects diff --git a/_ru/scala3/book/ca-multiversal-equality.md b/_ru/scala3/book/ca-multiversal-equality.md new file mode 100644 index 0000000000..19960bc97a --- /dev/null +++ b/_ru/scala3/book/ca-multiversal-equality.md @@ -0,0 +1,207 @@ +--- +layout: multipage-overview +title: Многостороннее равенство +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице демонстрируется, как реализовать многостороннее равенство в Scala 3. +language: ru +num: 65 +previous-page: ca-type-classes +next-page: ca-implicit-conversions +versionSpecific: true +--- + +Раньше в Scala было _универсальное равенство_ (_universal equality_): +два значения любых типов можно было сравнивать друг с другом с помощью `==` и `!=`. +Это произошло из-за того факта, что `==` и `!=` реализованы в терминах метода `equals` Java, +который также может сравнивать значения любых двух ссылочных типов. + +Универсальное равенство удобно, но оно также опасно, поскольку подрывает безопасность типов. +Например, предположим, что после некоторого рефакторинга осталась ошибочная программа, +в которой значение `y` имеет тип `S` вместо правильного типа `T`: + +```scala +val x = ... // типа T +val y = ... // типа S, но должно быть типа T +x == y // результат проверки типов всегда будет false +``` + +Если `y` сравнивается с другими значениями типа `T`, программа все равно будет проверять тип, +так как значения всех типов можно сравнивать друг с другом. +Но это, вероятно, даст неожиданные результаты и завершится ошибкой во время выполнения. + +Типобезопасный язык программирования может работать лучше, а многостороннее равенство — +это дополнительный способ сделать универсальное равенство более безопасным. +Он использует класс двоичного типа `CanEqual`, чтобы указать, что значения двух заданных типов можно сравнивать друг с другом. + +## Разрешение сравнения экземпляров класса + +По умолчанию в Scala 3 все ещё можно сравнивать на равенство следующим образом: + +```scala +case class Cat(name: String) +case class Dog(name: String) +val d = Dog("Fido") +val c = Cat("Morris") + +d == c // false, но он компилируется +``` + +Но в Scala 3 такие сравнения можно отключить. +При (а) импорте `scala.language.strictEquality` или (б) использовании флага компилятора `-language:strictEquality` +это сравнение больше не компилируется: + +```scala +import scala.language.strictEquality + +val rover = Dog("Rover") +val fido = Dog("Fido") +println(rover == fido) // ошибка компиляции + +// сообщение об ошибке компиляции: +// Values of types Dog and Dog cannot be compared with == or != +``` + +## Включение сравнений + +Есть два способа включить сравнение с помощью класса типов `CanEqual`. +Для простых случаев класс может _выводиться_ (_derive_) от класса `CanEqual`: + +```scala +// Способ 1 +case class Dog(name: String) derives CanEqual +``` + +Как вы вскоре увидите, когда нужна большая гибкость, вы также можете использовать следующий синтаксис: + +```scala +// Способ 2 +case class Dog(name: String) +given CanEqual[Dog, Dog] = CanEqual.derived +``` + +Любой из этих двух подходов позволяет сравнивать экземпляры `Dog` друг с другом. + +## Более реалистичный пример + +В более реалистичном примере представим, что есть книжный интернет-магазин +и мы хотим разрешить или запретить сравнение бумажных, печатных и аудиокниг. +В Scala 3 для начала необходимо включить многостороннее равенство: + +```scala +// [1] добавить этот импорт или command line flag: -language:strictEquality +import scala.language.strictEquality +``` + +Затем создать объекты домена: + +```scala +// [2] создание иерархии классов +trait Book: + def author: String + def title: String + def year: Int + +case class PrintedBook( + author: String, + title: String, + year: Int, + pages: Int +) extends Book + +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book +``` + +Наконец, используем `CanEqual`, чтобы определить, какие сравнения необходимо разрешить: + +```scala +// [3] создайте экземпляры класса типов, чтобы определить разрешенные сравнения. +// разрешено `PrintedBook == PrintedBook` +// разрешено `AudioBook == AudioBook` +given CanEqual[PrintedBook, PrintedBook] = CanEqual.derived +given CanEqual[AudioBook, AudioBook] = CanEqual.derived + +// [4a] сравнение двух печатных книг разрешено +val p1 = PrintedBook("1984", "George Orwell", 1961, 328) +val p2 = PrintedBook("1984", "George Orwell", 1961, 328) +println(p1 == p2) // true + +// [4b] нельзя сравнивать печатную книгу и аудиокнигу +val pBook = PrintedBook("1984", "George Orwell", 1961, 328) +val aBook = AudioBook("1984", "George Orwell", 2006, 682) +println(pBook == aBook) // ошибка компиляции +``` + +Последняя строка кода приводит к следующему сообщению компилятора об ошибке: + +``` +Values of types PrintedBook and AudioBook cannot be compared with == or != +``` + +Вот как многостороннее равенство отлавливает недопустимые сравнения типов во время компиляции. + +### Включение “PrintedBook == AudioBook” + +Если есть необходимость разрешить сравнение "печатной книги" (`PrintedBook`) с аудио-книгой (`AudioBook`), +то достаточно создать следующие два дополнительных сравнения равенства: + +```scala +// разрешить `PrintedBook == AudioBook` и `AudioBook == PrintedBook` +given CanEqual[PrintedBook, AudioBook] = CanEqual.derived +given CanEqual[AudioBook, PrintedBook] = CanEqual.derived +``` + +Теперь можно сравнивать печатную книгу с аудио-книгой без ошибки компилятора: + +```scala +println(pBook == aBook) // false +println(aBook == pBook) // false +``` + +#### Внедрите "equals", чтобы они действительно работали + +Хотя эти сравнения теперь разрешены, они всегда будут ложными, +потому что их методы `equals` не знают, как проводить подобные сравнения. +Чтобы доработать сравнение, можно переопределить методы `equals` для каждого класса. +Например, если переопределить метод `equals` для `AudioBook`: + +```scala +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book: + // переопределить, чтобы разрешить сравнение AudioBook с PrintedBook + override def equals(that: Any): Boolean = that match + case a: AudioBook => + this.author == a.author + && this.title == a.title + && this.year == a.year + && this.lengthInMinutes == a.lengthInMinutes + case p: PrintedBook => + this.author == p.author && this.title == p.title + case _ => + false +``` + +Теперь можно сравнить `AudioBook` с `PrintedBook`: + +```scala +println(aBook == pBook) // true (работает из-за переопределенного `equals` в `AudioBook`) +println(pBook == aBook) // false +``` + +Книга `PrintedBook` не имеет метода `equals`, поэтому второе сравнение возвращает `false`. +Чтобы включить это сравнение, достаточно переопределить метод `equals` в `PrintedBook`. + +Вы можете найти дополнительную информацию о [многостороннем равенстве][ref-equal] в справочной документации. + +[ref-equal]: {{ site.scala3ref }}/contextual/multiversal-equality.html diff --git a/_ru/scala3/book/ca-summary.md b/_ru/scala3/book/ca-summary.md new file mode 100644 index 0000000000..f3b68b7211 --- /dev/null +++ b/_ru/scala3/book/ca-summary.md @@ -0,0 +1,38 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен краткий обзор уроков по контекстуальным абстракциям. +language: ru +num: 67 +previous-page: ca-implicit-conversions +next-page: concurrency +--- + +В этой главе представлено введение в большинство тем контекстных абстракций, в том числе: + +- [Методы расширения](ca-extension-methods.html) +- [Экземпляры given и параметры контекста](ca-context-parameters.html) +- [Контекстные границы](ca-context-bounds.html) +- [Импорт given](ca-given-imports.html) +- [Классы типов](ca-type-classes.html) +- [Многостороннее равенство](ca-multiversal-equality.html) +- [Неявные преобразования](ca-implicit-conversions.html) + +Все эти функции являются вариантами основной идеи **вывода термов**: +учитывая тип, компилятор синтезирует “канонический” терм, который имеет этот тип. + +Несколько более сложных тем здесь не рассматриваются, в том числе: + +- Условные given экземпляры +- Вывод класса типов +- Контекстные функции +- Контекстные параметры по имени +- Связь с имплицитами Scala 2 + +Эти темы подробно обсуждаются в [справочной документации][ref]. + +[ref]: {{ site.scala3ref }}/contextual diff --git a/_ru/scala3/book/ca-type-classes.md b/_ru/scala3/book/ca-type-classes.md new file mode 100644 index 0000000000..5da4f9468f --- /dev/null +++ b/_ru/scala3/book/ca-type-classes.md @@ -0,0 +1,230 @@ +--- +layout: multipage-overview +title: Классы типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе демонстрируется создание и использование классов типов. +language: ru +num: 64 +previous-page: ca-given-imports +next-page: ca-multiversal-equality +--- + +Класс типов (_type class_) — это абстрактный параметризованный тип, +который позволяет добавлять новое поведение к любому закрытому типу данных без использования подтипов. +Если вы пришли с Java, то можно думать о классах типов как о чем-то вроде [`java.util.Comparator[T]`][comparator]. + +> В статье ["Type Classes as Objects and Implicits"][typeclasses-paper] (2010 г.) обсуждаются основные идеи, +> лежащие в основе классов типов в Scala. +> Несмотря на то, что в статье используется более старая версия Scala, идеи актуальны и по сей день. + +Этот стиль программирования полезен во многих случаях, например: + +- выражение того, как тип, которым вы не владеете, например, из стандартной или сторонней библиотеки, соответствует такому поведению +- добавление поведения к нескольким типам без введения отношений подтипов между этими типами (например, когда один расширяет другой) + +Классы типов — это трейты с одним или несколькими параметрами, +реализации которых предоставляются в виде экземпляров `given` в Scala 3 или `implicit` значений в Scala 2. + +## Пример + +Например, `Show` - хорошо известный класс типов в Haskell, и в следующем коде показан один из способов его реализации в Scala. +Если предположить, что классы Scala не содержат метода `toString`, то можно определить класс типов `Show`, +чтобы добавить это поведение к любому типу, который вы хотите преобразовать в пользовательскую строку. + +### Класс типов + +Первым шагом в создании класса типов является объявление параметризованного trait, содержащего один или несколько абстрактных методов. +Поскольку `Showable` содержит только один метод с именем `show`, он записывается так: + +{% tabs 'definition' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// класс типов +trait Showable[A] { + def show(a: A): String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// класс типов +trait Showable[A]: + extension (a: A) def show: String +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что этот подход близок к обычному объектно-ориентированному подходу, +когда обычно trait `Show` определяется следующим образом: + +{% tabs 'trait' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// a trait +trait Show { + def show: String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// a trait +trait Show: + def show: String +``` + +{% endtab %} +{% endtabs %} + +Следует отметить несколько важных моментов: + +1. Классы типов, например, `Showable` принимают параметр типа `A`, чтобы указать, для какого типа мы предоставляем реализацию `show`; + в отличие от классических трейтов, наподобие `Show`. +2. Чтобы добавить функциональность `show` к определенному типу `A`, классический трейт требует наследования `A extends Show`, + в то время как для классов типов нам требуется реализация `Showable[A]`. +3. В Scala 3, чтобы разрешить один и тот же синтаксис вызова метода в обоих случаях `Showable`, + который имитирует синтаксис `Show`, мы определяем `Showable.show` как метод расширения. + +### Реализация конкретных экземпляров + +Следующий шаг — определить, какие классы `Showable` должны работать в вашем приложении, а затем реализовать для них это поведение. +Например, для реализации `Showable` следующего класса `Person`: + +{% tabs 'person' %} +{% tab 'Scala 2 и 3' %} + +```scala +case class Person(firstName: String, lastName: String) +``` + +{% endtab %} +{% endtabs %} + +необходимо определить одно _каноническое значение_ типа `Showable[Person]`, т.е. экземпляр `Showable` для типа `Person`, +как показано в следующем примере кода: + +{% tabs 'instance' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +implicit val showablePerson: Showable[Person] = new Showable[Person] { + def show(p: Person): String = + s"${p.firstName} ${p.lastName}" +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +given Showable[Person] with + extension (p: Person) def show: String = + s"${p.firstName} ${p.lastName}" +``` + +{% endtab %} +{% endtabs %} + +### Использование класса типов + +Теперь вы можете использовать этот класс типов следующим образом: + +{% tabs 'usage' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val person = Person("John", "Doe") +println(showablePerson.show(person)) +``` + +Обратите внимание, что на практике классы типов обычно используются со значениями, тип которых неизвестен, +в отличие от type `Person`, как показано в следующем разделе. + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val person = Person("John", "Doe") +println(person.show) +``` + +{% endtab %} +{% endtabs %} + +Опять же, если бы в Scala не было метода `toString`, доступного для каждого класса, вы могли бы использовать эту технику, +чтобы добавить поведение `Showable` к любому классу, который вы хотите преобразовать в `String`. + +### Написание методов, использующих класс типов + +Как и в случае с наследованием, вы можете определить методы, которые используют `Showable` в качестве параметра типа: + +{% tabs 'method' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit = + as.foreach(a => println(showable.show(a))) + +showAll(List(Person("Jane"), Person("Mary"))) +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +def showAll[A: Showable](as: List[A]): Unit = + as.foreach(a => println(a.show)) + +showAll(List(Person("Jane"), Person("Mary"))) +``` + +{% endtab %} +{% endtabs %} + +### Класс типов с несколькими методами + +Обратите внимание: если вы хотите создать класс типов с несколькими методами, исходный синтаксис выглядит следующим образом: + +{% tabs 'multiple-methods' class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasLegs[A] { + def walk(a: A): Unit + def run(a: A): Unit +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait HasLegs[A]: + extension (a: A) + def walk(): Unit + def run(): Unit +``` + +{% endtab %} +{% endtabs %} + +### Пример из реального мира + +В качестве примера из реального мира, как классы типов используются в Scala 3, +см. обсуждение `CanEqual` в [разделе Multiversal Equality][multiversal]. + +[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf + +[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} +[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} diff --git a/_ru/scala3/book/collections-classes.md b/_ru/scala3/book/collections-classes.md new file mode 100644 index 0000000000..80fcf30248 --- /dev/null +++ b/_ru/scala3/book/collections-classes.md @@ -0,0 +1,971 @@ +--- +layout: multipage-overview +title: Типы коллекций +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлены общие типы коллекций Scala 3 и некоторые из их методов. +language: ru +num: 38 +previous-page: collections-intro +next-page: collections-methods +--- + + +На этой странице показаны общие коллекции Scala 3 и сопутствующие им методы. +Scala поставляется с большим количеством типов коллекций, на изучение которых может уйти время, +поэтому желательно начать с нескольких из них, а затем использовать остальные по мере необходимости. +Точно так же у каждого типа коллекции есть десятки методов, облегчающих разработку, +поэтому лучше начать изучение лишь с небольшого количества. + +В этом разделе представлены наиболее распространенные типы и методы коллекций, +которые вам понадобятся для начала работы. + +В конце этого раздела представлены дополнительные ссылки, для более глубокого изучения коллекций. + +## Три основные категории коллекций + +Для коллекций Scala можно выделить три основные категории: + +- **Последовательности** (**Sequences**/**Seq**) представляют собой последовательный набор элементов + и могут быть _индексированными_ (как массив) или _линейными_ (как связанный список) +- **Мапы** (**Maps**) содержат набор пар ключ/значение, например Java `Map`, Python dictionary или Ruby `Hash` +- **Множества** (**Sets**) — это неупорядоченный набор уникальных элементов + +Все они являются базовыми типами и имеют подтипы подходящие под конкретные задачи, +таких как параллелизм (concurrency), кэширование (caching) и потоковая передача (streaming). +В дополнение к этим трем основным категориям существуют и другие полезные типы коллекций, +включая диапазоны (ranges), стеки (stacks) и очереди (queues). + + +### Иерархия коллекций + +В качестве краткого обзора следующие три рисунка показывают иерархию классов и трейтов в коллекциях Scala. + +На первом рисунке показаны типы коллекций в пакете _scala.collection_. +Все это высокоуровневые абстрактные классы или трейты, которые обычно имеют _неизменяемые_ и _изменяемые_ реализации. + +![General collection hierarchy][collections1] + +На этом рисунке показаны все коллекции в пакете _scala.collection.immutable_: + +![Immutable collection hierarchy][collections2] + +А на этом рисунке показаны все коллекции в пакете _scala.collection.mutable_: + +![Mutable collection hierarchy][collections3] + +В следующих разделах представлены некоторые из распространенных типов. + +## Общие коллекции + +Основные коллекции, используемые чаще всего: + +| Тип коллекции | Неизменяемая | Изменяемая | Описание | +|----------------|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `List` | ✓ | | Линейная неизменяемая последовательность (связный список) | +| `Vector` | ✓ | | Индексированная неизменяемая последовательность | +| `LazyList` | ✓ | | Ленивый неизменяемый связанный список, элементы которого вычисляются только тогда, когда они необходимы; подходит для больших или бесконечных последовательностей. | +| `ArrayBuffer` | | ✓ | Подходящий тип для изменяемой индексированной последовательности | +| `ListBuffer` | | ✓ | Используется, когда вам нужен изменяемый список; обычно преобразуется в `List` | +| `Map` | ✓ | ✓ | Итерируемая коллекция, состоящая из пар ключей и значений | +| `Set` | ✓ | ✓ | Итерируемая коллекция без повторяющихся элементов | + +Как показано, `Map` и `Set` бывают как изменяемыми, так и неизменяемыми. + +Основы каждого типа демонстрируются в следующих разделах. + +> В Scala _буфер_ (_buffer_), такой как `ArrayBuffer` или `ListBuffer`, представляет собой последовательность, +> которая может увеличиваться и уменьшаться. + +### Примечание о неизменяемых коллекциях + +В последующих разделах всякий раз, когда используется слово _immutable_, можно с уверенностью сказать, +что тип предназначен для использования в стиле _функционального программирования_ (ФП). +С помощью таких типов коллекция не меняется, +а при вызове функциональных методов возвращается новый результат - новая коллекция. + +## Выбор последовательности + +При выборе _последовательности_ (последовательной коллекции элементов) нужно руководствоваться двумя основными вопросами: + +- должна ли последовательность индексироваться (как массив), обеспечивая быстрый доступ к любому элементу, + или она должна быть реализована как линейный связанный список? +- необходима изменяемая или неизменяемая коллекция? + +Рекомендуемые универсальные последовательности: + +| Тип\Категория | Неизменяемая | Изменяемая | +|-----------------------------|--------------|---------------| +| индексируемая | `Vector` | `ArrayBuffer` | +| линейная (связанный список) | `List` | `ListBuffer` | + +Например, если нужна неизменяемая индексированная коллекция, в общем случае следует использовать `Vector`. +И наоборот, если нужна изменяемая индексированная коллекция, используйте `ArrayBuffer`. + +> `List` и `Vector` часто используются при написании кода в функциональном стиле. +> `ArrayBuffer` обычно используется при написании кода в императивном стиле. +> `ListBuffer` используется тогда, когда стили смешиваются, например, при создании списка. + +Следующие несколько разделов кратко демонстрируют типы `List`, `Vector` и `ArrayBuffer`. + + +## `List` + +[List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +представляет собой линейную неизменяемую последовательность. +Каждый раз, когда в список добавляются или удаляются элементы, по сути создается новый список из существующего. + +### Создание списка + +`List` можно создать различными способами: + +{% tabs list-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints = List(1, 2, 3) +val names = List("Joel", "Chris", "Ed") + +// другой путь создания списка List +val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil +``` +{% endtab %} + +{% endtabs %} + +При желании тип списка можно объявить, хотя обычно в этом нет необходимости: + +{% tabs list-type %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints: List[Int] = List(1, 2, 3) +val names: List[String] = List("Joel", "Chris", "Ed") +``` +{% endtab %} + +{% endtabs %} + +Одно исключение — когда в коллекции смешанные типы; в этом случае тип желательно указывать явно: + +{% tabs list-mixed-types class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val things: List[Any] = List(1, "two", 3.0) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val things: List[String | Int | Double] = List(1, "two", 3.0) // с типами объединения +val thingsAny: List[Any] = List(1, "two", 3.0) // с Any +``` +{% endtab %} + +{% endtabs %} + +### Добавление элементов в список + +Поскольку `List` неизменяем, в него нельзя добавлять новые элементы. +Вместо этого создается новый список с добавленными к существующему списку элементами. +Например, учитывая этот `List`: + +{% tabs adding-elements-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Для _добавления_ (_prepend_) к началу списка одного элемента используется метод `::`, для добавления нескольких — `:::`, как показано здесь: + +{% tabs adding-elements-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val b = 0 :: a // List(0, 1, 2, 3) +val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Также можно _добавить_ (_append_) элементы в конец `List`, но, поскольку `List` является односвязным, +следует добавлять к нему элементы только в начало; +добавление элементов в конец списка — относительно медленная операция, +особенно при работе с большими последовательностями. + +> Совет: если необходимо добавлять к неизменяемой последовательности элементы в начало и конец, используйте `Vector`. + +Поскольку `List` является связанным списком, +крайне нежелательно пытаться получить доступ к элементам больших списков по значению их индекса. +Например, если есть `List` с миллионом элементов, доступ к такому элементу, как `myList(999_999)`, +займет относительно много времени, потому что этот запрос должен пройти почти через все элементы. +Если есть большая коллекция и необходимо получать доступ к элементам по их индексу, то +вместо `List` используйте `Vector` или `ArrayBuffer`. + +### Как запомнить названия методов + +В методах Scala символ `:` представляет сторону, на которой находится последовательность, +поэтому, когда используется метод `+:`, список нужно указывать справа: + +{% tabs list-prepending %} + +{% tab 'Scala 2 и 3' %} +```scala +0 +: a +``` +{% endtab %} + +{% endtabs %} + +Аналогично, если используется `:+`, список должен быть слева: + +{% tabs list-appending %} + +{% tab 'Scala 2 и 3' %} +```scala +a :+ 4 +``` +{% endtab %} + +{% endtabs %} + +Хорошей особенностью таких символических имен у методов является то, что они стандартизированы. + +Те же имена методов используются с другими неизменяемыми последовательностями, такими как `Seq` и `Vector`. +Также можно использовать несимволические имена методов для добавления элементов в начало (`a.prepended(4)`) +или конец (`a.appended(4)`). + +### Как пройтись по списку + +Представим, что есть `List` имён: + +{% tabs list-loop-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val names = List("Joel", "Chris", "Ed") +``` +{% endtab %} + +{% endtabs %} + +Напечатать каждое имя можно следующим способом: + +{% tabs list-loop-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for (name <- names) println(name) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for name <- names do println(name) +``` +{% endtab %} + +{% endtabs %} + +Вот как это выглядит в REPL: + +{% tabs list-loop-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for name <- names do println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% endtabs %} + + +Преимуществом использования выражений вида `for` с коллекциями в том, что Scala стандартизирован, +и один и тот же подход работает со всеми последовательностями, +включая `Array`, `ArrayBuffer`, `List`, `Seq`, `Vector`, `Map`, `Set` и т.д. + +### Немного истории + +Список Scala подобен списку из языка программирования [Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)), +который был впервые представлен в 1958 году. +Действительно, в дополнение к привычному способу создания списка: + +{% tabs list-history-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +точно такой же список можно создать следующим образом: + +{% tabs list-history-init2 %} + +{% tab 'Scala 2 и 3' %} +```scala +val list = 1 :: 2 :: 3 :: Nil +``` +{% endtab %} + +{% endtabs %} + +REPL показывает, как это работает: + +{% tabs list-history-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val list = 1 :: 2 :: 3 :: Nil +list: List[Int] = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Это работает, потому что `List` — односвязный список, оканчивающийся элементом `Nil`, +а `::` — это метод `List`, работающий как оператор “cons” в Lisp. + + +### Отступление: LazyList + +Коллекции Scala также включают [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html), +который представляет собой _ленивый_ неизменяемый связанный список. +Он называется «ленивым» — или нестрогим — потому что вычисляет свои элементы только тогда, когда они необходимы. + +Вы можете увидеть отложенное вычисление `LazyList` в REPL: + +{% tabs lazylist-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val x = LazyList.range(1, Int.MaxValue) +x.take(1) // LazyList() +x.take(5) // LazyList() +x.map(_ + 1) // LazyList() +``` +{% endtab %} + +{% endtabs %} + +Во всех этих примерах ничего не происходит. +Действительно, ничего не произойдет, пока вы не заставите это произойти, например, вызвав метод `foreach`: + +{% tabs lazylist-evaluation-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> x.take(1).foreach(println) +1 +``` +{% endtab %} + +{% endtabs %} + +Дополнительные сведения об использовании, преимуществах и недостатках строгих и нестрогих (ленивых) коллекций +см. в обсуждениях “строгих” и “нестрогих” на странице [Архитектура коллекции в Scala 2.13][strict]. + +## Vector + +[Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) - это индексируемая неизменяемая последовательность. +“Индексируемая” часть описания означает, что она обеспечивает произвольный доступ +и обновление за практически постоянное время, +поэтому можно быстро получить доступ к элементам `Vector` по значению их индекса, +например, получить доступ к `listOfPeople(123_456_789)`. + +В общем, за исключением той разницы, что (а) `Vector` индексируется, а `List` - нет, +и (б) `List` имеет метод `::`, эти два типа работают одинаково, +поэтому мы быстро пробежимся по следующим примерам. + +Вот несколько способов создания `Vector`: + +{% tabs vector-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Vector(1, 2, 3, 4, 5) + +val strings = Vector("one", "two") + +case class Person(name: String) +val people = Vector( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} + +{% endtabs %} + +Поскольку `Vector` неизменяем, в него нельзя добавить новые элементы. +Вместо этого создается новая последовательность, с добавленными к существующему `Vector` в начало или в конец элементами. + +Например, так элементы добавляются в конец: + +{% tabs vector-appending %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = a :+ 4 // Vector(1, 2, 3, 4) +val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5) +``` +{% endtab %} + +{% endtabs %} + +А так - в начало Vector-а: + +{% tabs vector-prepending %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = 0 +: a // Vector(0, 1, 2, 3) +val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +В дополнение к быстрому произвольному доступу и обновлениям, `Vector` обеспечивает быстрое добавление в начало и конец. + +> Подробную информацию о производительности `Vector` и других коллекций +> см. [в характеристиках производительности коллекций](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html). + +Наконец, `Vector` в выражениях вида `for` используется точно так же, как `List`, `ArrayBuffer` или любая другая последовательность: + +{% tabs vector-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for name <- names do println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% endtabs %} + + +## ArrayBuffer + +`ArrayBuffer` используется тогда, когда нужна изменяемая индексированная последовательность общего назначения. +Поскольку `ArrayBuffer` индексирован, произвольный доступ к элементам выполняется быстро. + +### Создание ArrayBuffer + +Чтобы использовать `ArrayBuffer`, его нужно вначале импортировать: + +{% tabs arraybuffer-import %} + +{% tab 'Scala 2 и 3' %} +```scala +import scala.collection.mutable.ArrayBuffer +``` +{% endtab %} + +{% endtabs %} + +Если необходимо начать с пустого `ArrayBuffer`, просто укажите его тип: + +{% tabs arraybuffer-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +var strings = ArrayBuffer[String]() +var ints = ArrayBuffer[Int]() +var people = ArrayBuffer[Person]() +``` +{% endtab %} + +{% endtabs %} + +Если известен примерный размер `ArrayBuffer`, его можно задать: + +{% tabs list-creation-with-size %} + +{% tab 'Scala 2 и 3' %} +```scala +// готов вместить 100 000 чисел +val buf = new ArrayBuffer[Int](100_000) +``` +{% endtab %} + +{% endtabs %} + +Чтобы создать новый `ArrayBuffer` с начальными элементами, +достаточно просто указать начальные элементы, как для `List` или `Vector`: + +{% tabs arraybuffer-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) +val people = ArrayBuffer( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} + +{% endtabs %} + +### Добавление элементов в ArrayBuffer + +Новые элементы добавляются в `ArrayBuffer` с помощью методов `+=` и `++=`. +Также можно использовать текстовый аналог: `append`, `appendAll`, `insert`, `insertAll`, `prepend` и `prependAll`. +Вот несколько примеров с `+=` и `++=`: + +{% tabs arraybuffer-add %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) +nums += 4 // ArrayBuffer(1, 2, 3, 4) +nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) +``` +{% endtab %} + +{% endtabs %} + +### Удаление элементов из ArrayBuffer + +`ArrayBuffer` является изменяемым, +поэтому у него есть такие методы, как `-=`, `--=`, `clear`, `remove` и другие. +Примеры с `-=` и `--=`: + +{% tabs arraybuffer-remove %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) +a -= 'a' // ArrayBuffer(b, c, d, e, f, g) +a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g) +a --= Set('d', 'e') // ArrayBuffer(f, g) +``` +{% endtab %} + +{% endtabs %} + +### Обновление элементов в ArrayBuffer + +Элементы в `ArrayBuffer` можно обновлять, либо переназначать: + +{% tabs arraybuffer-update %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4) +a(2) = 50 // ArrayBuffer(1, 2, 50, 4) +a.update(0, 10) // ArrayBuffer(10, 2, 50, 4) +``` +{% endtab %} + +{% endtabs %} + + + +## Maps + +`Map` — это итерируемая коллекция, состоящая из пар ключей и значений. +В Scala есть как изменяемые, так и неизменяемые типы `Map`. +В этом разделе показано, как использовать _неизменяемый_ `Map`. + +### Создание неизменяемой Map + +Неизменяемая `Map` создается следующим образом: + +{% tabs map-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) +``` +{% endtab %} + +{% endtabs %} + +Перемещаться по элементам `Map` используя выражение `for` можно следующим образом: + +{% tabs map-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} + +{% endtabs %} + +REPL показывает, как это работает: + +{% tabs map-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for ((k, v) <- states) println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for (k, v) <- states do println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% endtabs %} + +### Доступ к элементам Map + +Доступ к элементам `Map` осуществляется через указание в скобках значения ключа: + +{% tabs map-access-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val ak = states("AK") // ak: String = Alaska +val al = states("AL") // al: String = Alabama +``` +{% endtab %} + +{% endtabs %} + +На практике также используются такие методы, как `keys`, `keySet`, `keysIterator`, `for` выражения +и функции высшего порядка, такие как `map`, для работы с ключами и значениями `Map`. + +### Добавление элемента в Map + +При добавлении элементов в неизменяемую мапу с помощью `+` и `++`, создается новая мапа: + +{% tabs map-add-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map(1 -> "one") // a: Map(1 -> one) +val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) +val c = b ++ Seq( + 3 -> "three", + 4 -> "four" +) +// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) +``` +{% endtab %} + +{% endtabs %} + +### Удаление элементов из Map + +Элементы удаляются с помощью методов `-` или `--`. +В случае неизменяемой `Map` создается новый экземпляр, который нужно присвоить новой переменной: + +{% tabs map-remove-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three", + 4 -> "four" +) + +val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three) +val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two) +``` +{% endtab %} + +{% endtabs %} + +### Обновление элементов в Map + +Чтобы обновить элементы на неизменяемой `Map`, используется метод `update` (или оператор `+`): + +{% tabs map-update-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three" +) + +val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!) +val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three) +``` +{% endtab %} + +{% endtabs %} + +### Перебор элементов в Map + +Элементы в `Map` можно перебрать с помощью выражения `for`, как и для остальных коллекций: + +{% tabs map-traverse class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} + +{% endtabs %} + +Существует _много_ способов работы с ключами и значениями на `Map`. +Общие методы `Map` включают `foreach`, `map`, `keys` и `values`. + +В Scala есть много других специализированных типов `Map`, +включая `CollisionProofHashMap`, `HashMap`, `LinkedHashMap`, `ListMap`, `SortedMap`, `TreeMap`, `WeakHashMap` и другие. + + +## Работа с множествами + +Множество ([Set]({{site.baseurl}}/overviews/collections-2.13/sets.html)) - итерируемая коллекция без повторяющихся элементов. + +В Scala есть как изменяемые, так и неизменяемые типы `Set`. +В этом разделе демонстрируется _неизменяемое_ множество. + +### Создание множества + +Создание нового пустого множества: + +{% tabs set-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Set[Int]() +val letters = Set[Char]() +``` +{% endtab %} + +{% endtabs %} + +Создание множества с исходными данными: + +{% tabs set-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) +val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') +``` +{% endtab %} + +{% endtabs %} + + +### Добавление элементов в множество + +В неизменяемое множество новые элементы добавляются с помощью `+` и `++`, результат присваивается новой переменной: + +{% tabs set-add-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Set(1, 2) // Set(1, 2) +val b = a + 3 // Set(1, 2, 3) +val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4) +``` +{% endtab %} + +{% endtabs %} + +Стоит отметить, что повторяющиеся элементы не добавляются в множество, +а также, что порядок элементов произвольный. + + +### Удаление элементов из множества + +Элементы из множества удаляются с помощью методов `-` и `--`, результат также должен присваиваться новой переменной: + +{% tabs set-remove-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4) +val b = a - 5 // HashSet(1, 2, 3, 4) +val c = b -- Seq(3, 4) // HashSet(1, 2) +``` +{% endtab %} + +{% endtabs %} + + + +## Диапазон (Range) + +`Range` часто используется для заполнения структур данных и для `for` выражений. +Эти REPL примеры демонстрируют, как создавать диапазоны: + +{% tabs range-init %} + +{% tab 'Scala 2 и 3' %} +```scala +1 to 5 // Range(1, 2, 3, 4, 5) +1 until 5 // Range(1, 2, 3, 4) +1 to 10 by 2 // Range(1, 3, 5, 7, 9) +'a' to 'c' // NumericRange(a, b, c) +``` +{% endtab %} + +{% endtabs %} + +Range можно использовать для заполнения коллекций: + +{% tabs range-conversion %} + +{% tab 'Scala 2 и 3' %} +```scala +val x = (1 to 5).toList // List(1, 2, 3, 4, 5) +val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5) +``` +{% endtab %} + +{% endtabs %} + +Они также используются в `for` выражениях: + +{% tabs range-iteration class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (i <- 1 to 3) println(i) +1 +2 +3 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for i <- 1 to 3 do println(i) +1 +2 +3 +``` +{% endtab %} + +{% endtabs %} + +Во многих коллекциях есть метод `range`: + +{% tabs range-methods %} + +{% tab 'Scala 2 и 3' %} +```scala +Vector.range(1, 5) // Vector(1, 2, 3, 4) +List.range(1, 10, 2) // List(1, 3, 5, 7, 9) +Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4) +``` +{% endtab %} + +{% endtabs %} + +Диапазоны также полезны для создания тестовых коллекций: + +{% tabs range-tests %} + +{% tab 'Scala 2 и 3' %} +```scala +val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10) +val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0) + +// Создание Map +val map = (1 to 3).map(e => (e,s"$e")).toMap +// map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3") +``` +{% endtab %} + +{% endtabs %} + + +## Больше деталей + +Если вам нужна дополнительная информация о специализированных коллекциях, см. следующие ресурсы: + +- [Конкретные неизменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html) +- [Конкретные изменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html) +- [Как устроены коллекции? Какую из них следует выбрать?](https://docs.scala-lang.org/tutorials/FAQ/collections.html) + + + +[strict]: {% link _overviews/core/architecture-of-scala-213-collections.md %} +[collections1]: /resources/images/tour/collections-diagram-213.svg +[collections2]: /resources/images/tour/collections-immutable-diagram-213.svg +[collections3]: /resources/images/tour/collections-mutable-diagram-213.svg diff --git a/_ru/scala3/book/collections-intro.md b/_ru/scala3/book/collections-intro.md new file mode 100644 index 0000000000..c29c5ecc29 --- /dev/null +++ b/_ru/scala3/book/collections-intro.md @@ -0,0 +1,22 @@ +--- +layout: multipage-overview +title: Коллекции в Scala +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице представлено введение в общие классы коллекций и их методы в Scala 3. +language: ru +num: 37 +previous-page: packaging-imports +next-page: collections-classes +--- + +В этой главе представлены наиболее распространенные коллекции Scala 3 и сопутствующие им методы. +Scala поставляется с множеством типов коллекций, +Вы можете многого добиться, начав использовать лишь небольшое количество типов, а затем, по мере необходимости, начать применять остальные. +Точно так же у каждого типа есть десятки методов, +облегчающих разработку, но можно многого добиться, начав лишь с нескольких. + +Поэтому в этом разделе представлены наиболее распространенные типы и методы коллекций, +которые вам понадобятся для начала работы. diff --git a/_ru/scala3/book/collections-methods.md b/_ru/scala3/book/collections-methods.md new file mode 100644 index 0000000000..a11cdd1738 --- /dev/null +++ b/_ru/scala3/book/collections-methods.md @@ -0,0 +1,662 @@ +--- +layout: multipage-overview +title: Методы в коллекциях +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показаны общие методы классов коллекций Scala 3. +language: ru +num: 39 +previous-page: collections-classes +next-page: collections-summary +--- + + + +Важным преимуществом коллекций Scala является то, что они поставляются с десятками методов “из коробки”, +которые доступны как для неизменяемых, так и для изменяемых типов коллекций. +Больше нет необходимости писать пользовательские циклы `for` каждый раз, когда нужно работать с коллекцией. +При переходе от одного проекта к другому, можно обнаружить, что используются одни и те же методы. + +В коллекциях доступны _десятки_ методов, поэтому здесь показаны не все из них. +Показаны только некоторые из наиболее часто используемых методов, в том числе: + +- `map` +- `filter` +- `foreach` +- `head` +- `tail` +- `take`, `takeWhile` +- `drop`, `dropWhile` +- `reduce` + +Следующие методы работают со всеми типами последовательностей, включая `List`, `Vector`, `ArrayBuffer` и т.д. +Примеры рассмотрены на `List`-е, если не указано иное. + +> Важно напомнить, что ни один из методов в `List` не изменяет список. +> Все они работают в функциональном стиле, то есть возвращают новую коллекцию с измененными результатами. + +## Примеры распространенных методов + +Для общего представления в примерах ниже показаны некоторые из наиболее часто используемых методов коллекций. +Вот несколько методов, которые не используют лямбда-выражения: + +{% tabs common-method-examples %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +``` +{% endtab %} + +{% endtabs %} + + +### Функции высшего порядка и лямбда-выражения + +Далее будут показаны некоторые часто используемые функции высшего порядка (HOF), +которые принимают лямбды (анонимные функции). +Для начала приведем несколько вариантов лямбда-синтаксиса, +начиная с самой длинной формы, поэтапно переходящей к наиболее сжатой: + +{% tabs higher-order-functions-example %} + +{% tab 'Scala 2 и 3' %} +```scala +// все эти функции одинаковые и возвращают +// одно и тоже: List(10, 20, 10) + +a.filter((i: Int) => i < 25) // 1. наиболее расширенная форма +a.filter((i) => i < 25) // 2. `Int` необязателен +a.filter(i => i < 25) // 3. скобки можно опустить +a.filter(_ < 25) // 4. `i` необязателен +``` +{% endtab %} + +{% endtabs %} + +В этих примерах: + +1. Первый пример показывает самую длинную форму. + Такое многословие требуется _редко_, только в самых сложных случаях. +2. Компилятор знает, что `a` содержит `Int`, поэтому нет необходимости повторять это в функции. +3. Если в функции только один параметр, например `i`, то скобки не нужны. +4. В случае одного параметра, если он появляется в анонимной функции только раз, его можно заменить на `_`. + +В главе [Анонимные функции][lambdas] представлена более подробная информация +и примеры правил, связанных с сокращением лямбда-выражений. + +Примеры других HOF, использующих краткий лямбда-синтаксис: + +{% tabs anonymous-functions-example %} + +{% tab 'Scala 2 и 3' %} +```scala +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ > 100) // List() +a.filterNot(_ < 25) // List(30, 40) +a.find(_ > 20) // Some(30) +a.takeWhile(_ < 30) // List(10, 20) +``` +{% endtab %} + +{% endtabs %} + +Важно отметить, что HOF также принимают в качестве параметров методы и функции, а не только лямбда-выражения. +Вот несколько примеров, в которых используется метод с именем `double`. +Снова показаны несколько вариантов лямбда-выражений: + +{% tabs method-as-parameter-example %} + +{% tab 'Scala 2 и 3' %} +```scala +def double(i: Int) = i * 2 + +// these all return `List(20, 40, 60, 80, 20)` +a.map(i => double(i)) +a.map(double(_)) +a.map(double) +``` +{% endtab %} + +{% endtabs %} + +В последнем примере, когда анонимная функция состоит из одного вызова функции, принимающей один аргумент, +нет необходимости указывать имя аргумента, поэтому даже `_` не требуется. + +Наконец, HOF можно комбинировать: + +{% tabs higher-order-functions-combination-example %} + +{% tab 'Scala 2 и 3' %} +```scala +// выдает `List(100, 200)` +a.filter(_ < 40) + .takeWhile(_ < 30) + .map(_ * 10) +``` +{% endtab %} + +{% endtabs %} + + +## Пример данных + +В следующих разделах используются такие списки: + +{% tabs sample-data %} + +{% tab 'Scala 2 и 3' %} +```scala +val oneToTen = (1 to 10).toList +val names = List("adam", "brandy", "chris", "david") +``` +{% endtab %} + +{% endtabs %} + + +## `map` + +Метод `map` проходит через каждый элемент в списке, применяя переданную функцию к элементу, по одному за раз; +затем возвращается новый список с измененными элементами. + +Вот пример применения метода `map` к списку `oneToTen`: + +{% tabs map-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val doubles = oneToTen.map(_ * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} + +{% endtabs %} + +Также можно писать анонимные функции, используя более длинную форму, например: + +{% tabs map-example-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val doubles = oneToTen.map(i => i * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} + +{% endtabs %} + +Однако в этом документе будет всегда использоваться первая, более короткая форма. + +Вот еще несколько примеров применения метода `map` к `oneToTen` и `names`: + +{% tabs few-more-examples %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val capNames = names.map(_.capitalize) +capNames: List[String] = List(Adam, Brandy, Chris, David) + +scala> val nameLengthsMap = names.map(s => (s, s.length)).toMap +nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david -> 5) + +scala> val isLessThanFive = oneToTen.map(_ < 5) +isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) +``` +{% endtab %} + +{% endtabs %} + +Как показано в последних двух примерах, совершенно законно (и распространено) использование `map` для возврата коллекции, +которая имеет тип, отличный от исходного типа. + + +## `filter` + +Метод `filter` создает новый список, содержащий только те элементы, которые удовлетворяют предоставленному предикату. +Предикат или условие — это функция, которая возвращает `Boolean` (`true` или `false`). +Вот несколько примеров: + +{% tabs filter-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val lessThanFive = oneToTen.filter(_ < 5) +lessThanFive: List[Int] = List(1, 2, 3, 4) + +scala> val evens = oneToTen.filter(_ % 2 == 0) +evens: List[Int] = List(2, 4, 6, 8, 10) + +scala> val shortNames = names.filter(_.length <= 4) +shortNames: List[String] = List(adam) +``` +{% endtab %} + +{% endtabs %} + +Отличительной особенностью функциональных методов коллекций является то, +что их можно объединять вместе для решения задач. +Например, в этом примере показано, как связать `filter` и `map`: + +{% tabs filter-example-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.filter(_ < 4).map(_ * 10) +``` +{% endtab %} + +{% endtabs %} + +REPL показывает результат: + +{% tabs filter-example-anonymous-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> oneToTen.filter(_ < 4).map(_ * 10) +val res1: List[Int] = List(10, 20, 30) +``` +{% endtab %} + +{% endtabs %} + + +## `foreach` + +Метод `foreach` используется для перебора всех элементов коллекции. +Стоит обратить внимание, что `foreach` используется для побочных эффектов, таких как печать информации. +Вот пример с `names`: + +{% tabs foreach-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> names.foreach(println) +adam +brandy +chris +david +``` +{% endtab %} + +{% endtabs %} + + + +## `head` + +Метод `head` взят из Lisp и других более ранних языков функционального программирования. +Он используется для доступа к первому элементу (головному (_head_) элементу) списка: + +{% tabs head-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.head // 1 +names.head // adam +``` +{% endtab %} + +{% endtabs %} + +`String` можно рассматривать как последовательность символов, т.е. строка также является коллекцией, +а значит содержит соответствующие методы. +Вот как `head` работает со строками: + +{% tabs string-head-example %} + +{% tab 'Scala 2 и 3' %} +```scala +"foo".head // 'f' +"bar".head // 'b' +``` +{% endtab %} + +{% endtabs %} + +`head` — отличный метод для работы, но в качестве предостережения следует помнить, что +он также может генерировать исключение при вызове для пустой коллекции: + +{% tabs head-error-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val emptyList = List[Int]() // emptyList: List[Int] = List() +emptyList.head // java.util.NoSuchElementException: head of empty list +``` +{% endtab %} + +{% endtabs %} + +Чтобы не натыкаться на исключение вместо `head` желательно использовать `headOption`, +особенно при разработке в функциональном стиле: + +{% tabs head-option-example %} + +{% tab 'Scala 2 и 3' %} +```scala +emptyList.headOption // None +``` +{% endtab %} + +{% endtabs %} + +`headOption` не генерирует исключение, а возвращает тип `Option` со значением `None`. +Более подробно о функциональном стиле программирования будет рассказано [в соответствующей главе][fp-intro]. + + +## `tail` + +Метод `tail` также взят из Lisp и используется для вывода всех элементов в списке после `head`. + +{% tabs tail-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.head // 1 +oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) + +names.head // adam +names.tail // List(brandy, chris, david) +``` +{% endtab %} + +{% endtabs %} + +Так же, как и `head`, `tail` можно использовать со строками: + +{% tabs string-tail-example %} + +{% tab 'Scala 2 и 3' %} +```scala +"foo".tail // "oo" +"bar".tail // "ar" +``` +{% endtab %} + +{% endtabs %} + +`tail` выбрасывает исключение _java.lang.UnsupportedOperationException_, если список пуст, +поэтому, как и в случае с `head` и `headOption`, существует также метод `tailOption`, +который предпочтительнее в функциональном программировании. + +Список матчится, поэтому можно использовать такие выражения: + +{% tabs tail-match-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val x :: xs = names +``` +{% endtab %} + +{% endtabs %} + +Помещение этого кода в REPL показывает, что `x` назначается заглавному элементу списка, а `xs` назначается "хвосту": + +{% tabs tail-match-example-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val x :: xs = names +val x: String = adam +val xs: List[String] = List(brandy, chris, david) +``` +{% endtab %} + +{% endtabs %} + +Подобное сопоставление с образцом полезно во многих случаях, например, при написании метода `sum` с использованием рекурсии: + +{% tabs tail-match-sum-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(list: List[Int]): Int = list match { + case Nil => 0 + case x :: xs => x + sum(xs) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(list: List[Int]): Int = list match + case Nil => 0 + case x :: xs => x + sum(xs) +``` +{% endtab %} + +{% endtabs %} + + + +## `take`, `takeRight`, `takeWhile` + +Методы `take`, `takeRight` и `takeWhile` предоставляют удобный способ “брать” (_taking_) элементы из списка для создания нового. +Примеры `take` и `takeRight`: + +{% tabs take-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.take(1) // List(1) +oneToTen.take(2) // List(1, 2) + +oneToTen.takeRight(1) // List(10) +oneToTen.takeRight(2) // List(9, 10) +``` +{% endtab %} + +{% endtabs %} + +Обратите внимание, как эти методы работают с «пограничными» случаями, +когда запрашивается больше элементов, чем есть в последовательности, +или запрашивается ноль элементов: + +{% tabs take-edge-cases-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.take(0) // List() +oneToTen.takeRight(0) // List() +``` +{% endtab %} + +{% endtabs %} + +А это `takeWhile`, который работает с функцией-предикатом: + +{% tabs take-while-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) +names.takeWhile(_.length < 5) // List(adam) +``` +{% endtab %} + +{% endtabs %} + + +## `drop`, `dropRight`, `dropWhile` + +`drop`, `dropRight` и `dropWhile` удаляют элементы из списка +и, по сути, противоположны своим аналогам “take”. +Вот некоторые примеры: + +{% tabs drop-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.drop(5) // List(6, 7, 8, 9, 10) + +oneToTen.dropRight(8) // List(1, 2) +oneToTen.dropRight(7) // List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Пограничные случаи: + +{% tabs drop-edge-cases-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.drop(Int.MaxValue) // List() +oneToTen.dropRight(Int.MaxValue) // List() +oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +``` +{% endtab %} + +{% endtabs %} + +А это `dropWhile`, который работает с функцией-предикатом: + +{% tabs drop-while-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) +names.dropWhile(_ != "chris") // List(chris, david) +``` +{% endtab %} + +{% endtabs %} + + +## `reduce` + +Метод `reduce` позволяет свертывать коллекцию до одного агрегируемого значения. +Он принимает функцию (или анонимную функцию) и последовательно применяет эту функцию к элементам в списке. + +Лучший способ объяснить `reduce` — создать небольшой вспомогательный метод. +Например, метод `add`, который складывает вместе два целых числа, +а также предоставляет хороший вывод отладочной информации: + +{% tabs reduce-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def add(x: Int, y: Int): Int = { + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def add(x: Int, y: Int): Int = + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +``` +{% endtab %} + +{% endtabs %} + +Рассмотрим список: + +{% tabs reduce-example-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(1,2,3,4) +``` +{% endtab %} + +{% endtabs %} + +вот что происходит, когда в `reduce` передается метод `add`: + +{% tabs reduce-example-evaluation %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(add) +received 1 and 2, their sum is 3 +received 3 and 3, their sum is 6 +received 6 and 4, their sum is 10 +res0: Int = 10 +``` +{% endtab %} + +{% endtabs %} + +Как видно из результата, функция `reduce` использует `add` для сокращения списка `a` до единственного значения, +в данном случае — суммы всех чисел в списке. + +`reduce` можно использовать с анонимными функциями: + +{% tabs reduce-example-sum %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(_ + _) +res0: Int = 10 +``` +{% endtab %} + +{% endtabs %} + +Аналогично можно использовать другие функции, например, умножение: + +{% tabs reduce-example-multiply %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(_ * _) +res1: Int = 24 +``` +{% endtab %} + +{% endtabs %} + +> Важная концепция, которую следует знать о `reduce`, заключается в том, что, как следует из ее названия +> (_reduce_ - сокращать), она используется для сокращения коллекции до одного значения. + + +## Дальнейшее изучение коллекций + +В коллекциях Scala есть десятки дополнительных методов, которые избавляют от необходимости писать еще один цикл `for`. +Более подробную информацию о коллекциях Scala см. +в разделе [Изменяемые и неизменяемые коллекции][mut-immut-colls] +и [Архитектура коллекций Scala][architecture]. + +> В качестве последнего примечания, при использовании Java-кода в проекте Scala, +> коллекции Java можно преобразовать в коллекции Scala. +> После этого, их можно использовать в выражениях `for`, +> а также воспользоваться преимуществами методов функциональных коллекций Scala. +> Более подробную информацию можно найти в разделе [Взаимодействие с Java][interacting]. + + +[interacting]: {% link _overviews/scala3-book/interacting-with-java.md %} +[lambdas]: {% link _overviews/scala3-book/fun-anonymous-functions.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[mut-immut-colls]: {% link _overviews/collections-2.13/overview.md %} +[architecture]: {% link _overviews/core/architecture-of-scala-213-collections.md %} + diff --git a/_ru/scala3/book/collections-summary.md b/_ru/scala3/book/collections-summary.md new file mode 100644 index 0000000000..7d89b4961c --- /dev/null +++ b/_ru/scala3/book/collections-summary.md @@ -0,0 +1,35 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен краткий итог главы «Коллекции». +language: ru +num: 40 +previous-page: collections-methods +next-page: fp-intro +--- + +В этой главе представлен обзор общих коллекций Scala 3 и сопутствующих им методов. +Как было показано, Scala поставляется с множеством коллекций и методов. + +Если вам нужно увидеть более подробную информацию о типах коллекций, +показанных в этой главе, см. их Scaladoc страницы: + +- [List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +- [Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) +- [ArrayBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ArrayBuffer.html) +- [Range](https://www.scala-lang.org/api/current/scala/collection/immutable/Range.html) + +Также упоминавшиеся неизменяемые `Map` и `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) + +и изменяемые `Map` и `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/mutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/mutable/Set.html) + diff --git a/_ru/scala3/book/concurrency.md b/_ru/scala3/book/concurrency.md new file mode 100644 index 0000000000..ec43810bfa --- /dev/null +++ b/_ru/scala3/book/concurrency.md @@ -0,0 +1,333 @@ +--- +layout: multipage-overview +title: Параллелизм +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице обсуждается, как работает параллелизм в Scala, с упором на Scala Futures. +language: ru +num: 68 +previous-page: ca-summary +next-page: scala-tools +--- + +Для написания параллельных приложений на Scala, _можно_ использовать нативный Java `Thread`, +но Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) предлагает более высокоуровневый +и идиоматический подход, поэтому он предпочтителен и рассматривается в этой главе. + +## Введение + +Вот описание Scala `Future` из его Scaladoc: + +> "`Future` представляет собой значение, которое может быть или не быть доступным _в настоящее время_, +> но будет доступно в какой-то момент, или вызовет исключение, если это значение не может быть сделано доступным". + +Чтобы продемонстрировать, что это значит, сначала рассмотрим однопоточное программирование. +В однопоточном мире результат вызова метода привязывается к переменной следующим образом: + +```scala +def aShortRunningTask(): Int = 42 +val x = aShortRunningTask() +``` + +В этом коде значение `42` сразу привязывается к `x`. + +При работе с `Future` процесс назначения выглядит примерно так: + +```scala +def aLongRunningTask(): Future[Int] = ??? +val x = aLongRunningTask() +``` + +Но главное отличие в этом случае заключается в том, что, поскольку `aLongRunningTask` возвращает неопределенное время, +значение `x` может быть доступно или недоступно _в данный момент_, но оно будет доступно в какой-то момент — в будущем. + +Другой способ взглянуть на это с точки зрения блокировки. +В этом однопоточном примере оператор `println` не печатается до тех пор, пока не завершится выполнение `aShortRunningTask`: + +```scala +def aShortRunningTask(): Int = + Thread.sleep(500) + 42 +val x = aShortRunningTask() +println("Here") +``` + +И наоборот, если `aShortRunningTask` создается как `Future`, оператор `println` печатается почти сразу, +потому что `aShortRunningTask` порождается в другом потоке — он не блокируется. + +В этой главе будет рассказано, как использовать `Future`, +в том числе как запускать несколько `Future` параллельно и объединять их результаты в выражении `for`. +Также будут показаны примеры методов, которые используются для обработки значения `Future` после его возврата. + +> О `Future`, важно знать, что они задуманы как одноразовая конструкция +> "Обработайте это относительно медленное вычисление в каком-нибудь другом потоке и верните мне результат, когда закончите". +> В отличие от этого, акторы [Akka](https://akka.io) предназначены для работы в течение длительного времени +> и отвечают на множество запросов в течение своей жизни. +> В то время как субъект может жить вечно, `Future` в конечном итоге содержит результат вычисления, +> которое выполнялось только один раз. + +## Пример в REPL + +`Future` используется для создания временного кармана параллелизма. +Например, можно использовать `Future`, когда нужно вызвать алгоритм, +который выполняется неопределенное количество времени — например, вызов удаленного микросервиса, — +поэтому его желательно запустить вне основного потока. + +Чтобы продемонстрировать, как это работает, начнем с примера `Future` в REPL. +Во-первых, вставим необходимые инструкции импорта: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +``` + +Теперь можно создать `Future`. +Для этого примера сначала определим долговременный однопоточный алгоритм: + +```scala +def longRunningAlgorithm() = + Thread.sleep(10_000) + 42 +``` + +Этот причудливый алгоритм возвращает целочисленное значение `42` после десятисекундной задержки. +Теперь вызовем этот алгоритм, поместив его в конструктор `Future` и присвоив результат переменной: + +```scala +scala> val eventualInt = Future(longRunningAlgorithm()) +eventualInt: scala.concurrent.Future[Int] = Future() +``` + +Вычисления начинают выполняться после вызова `longRunningAlgorithm()`. +Если сразу проверить значение переменной `eventualInt`, то можно увидеть, что `Future` еще не завершен: + +```scala +scala> eventualInt +val res1: scala.concurrent.Future[Int] = Future() +``` + +Но если проверить через десять секунд ещё раз, то можно увидеть, что оно выполнено успешно: + +```scala +scala> eventualInt +val res2: scala.concurrent.Future[Int] = Future(Success(42)) +``` + +Хотя это относительно простой пример, он демонстрирует основной подход: +просто создайте новое `Future` с помощью своего долговременного алгоритма. + +Одна вещь, на которую следует обратить внимание - +это то, что ожидаемый результат `42` обернут в `Success`, который обернут в `Future`. +Это ключевая концепция для понимания: значение `Future` всегда является экземпляром одного из `scala.util.Try`: `Success` или `Failure`. +Поэтому, при работе с результатом `Future`, используются обычные методы обработки `Try`. + +### Использование `map` с `Future` + +`Future` содержит метод `map`, который используется точно так же, как метод `map` для коллекций. +Вот как выглядит результат, при вызове `map` сразу после создания переменной `a`: + +```scala +scala> val a = Future(longRunningAlgorithm()).map(_ * 2) +a: scala.concurrent.Future[Int] = Future() +``` + +Как показано, для `Future`, созданного с помощью `longRunningAlgorithm`, первоначальный вывод показывает `Future()`. +Но если проверить значение `a` через десять секунд, то можно увидеть, что оно содержит ожидаемый результат `84`: + +```scala +scala> a +res1: scala.concurrent.Future[Int] = Future(Success(84)) +``` + +Еще раз, успешный результат обернут внутри `Success` и `Future`. + +### Использование методов обратного вызова с `Future` + +В дополнение к функциям высшего порядка, таким как `map`, с `Future` также можно использовать методы обратного вызова. +Одним из часто используемых методов обратного вызова является `onComplete`, принимающий _частично определенную функцию_, +в которой обрабатываются случаи `Success` и `Failure`: + +```scala +Future(longRunningAlgorithm()).onComplete { + case Success(value) => println(s"Got the callback, value = $value") + case Failure(e) => e.printStackTrace +} +``` + +Если вставить этот код в REPL, то в конечном итоге придет результат: + +```scala +Got the callback, value = 42 +``` + +## Другие методы `Future` + +Класс `Future` содержит некоторые методы, которые можно найти в классах коллекций Scala, в том числе: + +- `filter` +- `flatMap` +- `map` + +Методы обратного вызова: + +- `onComplete` +- `andThen` +- `foreach` + +Другие методы трансформации: + +- `fallbackTo` +- `recover` +- `recoverWith` + +См. страницу [Futures and Promises][futures] для обсуждения дополнительных методов, доступных для `Future`. + +## Запуск нескольких `Future` и объединение результатов + +Чтобы запустить несколько вычислений параллельно и соединить их результаты после завершения всех `Future`, +можно использовать выражение `for`. + +Правильный подход такой: + +1. Запустить вычисления, которые возвращают `Future` результаты +2. Объединить их результаты в выражении `for` +3. Извлечь объединенный результат, используя `onComplete` или аналогичный метод + +### Пример + +Три шага правильного подхода показаны в следующем примере. +Ключевой момент - сначала запускаются вычисления, возвращающие `Future`, а затем они объединяются в выражении `for`: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +val startTime = System.currentTimeMillis() +def delta() = System.currentTimeMillis() - startTime +def sleep(millis: Long) = Thread.sleep(millis) + +@main def multipleFutures1 = + + println(s"creating the futures: ${delta()}") + + // (1) запуск вычислений, возвращающих Future + val f1 = Future { sleep(800); 1 } // в конце концов возвращается 1 + val f2 = Future { sleep(200); 2 } // в конце концов возвращается 2 + val f3 = Future { sleep(400); 3 } // в конце концов возвращается 3 + + // (2) объединение нескольких Future в выражении `for` + val result = + for + r1 <- f1 + r2 <- f2 + r3 <- f3 + yield + println(s"in the 'yield': ${delta()}") + (r1 + r2 + r3) + + // (3) обработка результата + result.onComplete { + case Success(x) => + println(s"in the Success case: ${delta()}") + println(s"result = $x") + case Failure(e) => + e.printStackTrace + } + + println(s"before the 'sleep(3000)': ${delta()}") + + // важно для небольшой параллельной демонстрации: не глушить jvm + sleep(3000) +``` + +После запуска этого приложения, вывод выглядит следующим образом: + +``` +creating the futures: 1 +before the 'sleep(3000)': 2 +in the 'yield': 806 +in the Success case: 806 +result = 6 +``` + +Как показывает вывод, `Future` создаются очень быстро, +и всего за две миллисекунды достигается оператор печати непосредственно перед операцией `sleep(3000)` в конце метода. +Весь этот код выполняется в основном потоке JVM. Затем, через 806 мс, три `Future` завершаются, и выполняется код в блоке `yield`. +Затем код немедленно переходит к варианту `Success` в методе `onComplete`. + +Вывод 806 мс является ключом к тому, чтобы убедиться, что три вычисления выполняются параллельно. +Если бы они выполнялись последовательно, общее время составило бы около 1400 мс — сумма времени ожидания трех вычислений. +Но поскольку они выполняются параллельно, общее время чуть больше, чем у самого продолжительного вычисления `f1`, +которое составляет 800 мс. + +> Обратите внимание, что если бы вычисления выполнялись в выражении `for`, +> они выполнялись бы последовательно, а не параллельно: +> +> ``` +> // последовательное выполнение (без параллелизма!) +> for +> r1 <- Future { sleep(800); 1 } +> r2 <- Future { sleep(200); 2 } +> r3 <- Future { sleep(400); 3 } +> yield +> r1 + r2 + r3 +> ``` +> +> Итак, если необходимо, чтобы вычисления выполнялись параллельно, не забудьте запустить их вне выражения `for`. + +### Метод, возвращающий Future + +Было показано, как передавать однопоточный алгоритм в конструктор `Future`. +Ту же технику можно использовать для создания метода, который возвращает `Future`: + +```scala +// моделируем медленно работающий метод +def slowlyDouble(x: Int, delay: Long): Future[Int] = Future { + sleep(delay) + x * 2 +} +``` + +Как и в предыдущих примерах, достаточно просто присвоить результат вызова метода новой переменной. +Тогда, если сразу проверить результат, то можно увидеть, что он не завершен, +но по истечении времени задержки в `Future` результат будет выдан: + +``` +scala> val f = slowlyDouble(2, 5_000L) +val f: concurrent.Future[Int] = Future() + +scala> f +val res0: concurrent.Future[Int] = Future() + +scala> f +val res1: concurrent.Future[Int] = Future(Success(4)) +``` + +## Ключевые моменты о Future + +Надеюсь, эти примеры дадут вам представление о том, как работает Scala `Future`. +Подводя итог, несколько ключевых моментов о `Future`: + +- `Future` создается для запуска задач вне основного потока +- `Future` предназначены для одноразовых, потенциально длительных параллельных задач, которые _в конечном итоге_ возвращают значение; + они создают временный карман параллелизма +- `Future` начинает работать в момент построения +- преимущество `Future` над потоками заключается в том, что они работают с выражениями `for` + и имеют множество методов обратного вызова, упрощающих процесс работы с параллельными потоками +- при работе с `Future` не нужно беспокоиться о низкоуровневых деталях управления потоками +- результат `Future` обрабатывается с помощью методов обратного вызова, таких как `onComplete` и `andThen`, + или методов преобразования, таких как `filter`, `map` и т.д. +- значение внутри `Future` всегда является экземпляром одного из типов `Try`: `Success` или `Failure` +- при использовании нескольких `Future` для получения одного результата, они объединяются в выражении `for` + +Кроме того, как было видно по операторам `import`, Scala `Future` зависит от `ExecutionContext`. + +Дополнительные сведения о `Future` см. в статье [Futures and Promises][futures], +в которой обсуждаются futures, promises и execution contexts. +В ней также обсуждается, как выражение `for` транслируется в операцию `flatMap`. + +[futures]: {% link _overviews/core/futures.md %} diff --git a/_ru/scala3/book/control-structures.md b/_ru/scala3/book/control-structures.md new file mode 100644 index 0000000000..41b5e0646f --- /dev/null +++ b/_ru/scala3/book/control-structures.md @@ -0,0 +1,1016 @@ +--- +layout: multipage-overview +title: Структуры управления +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице представлено введение в структуры управления Scala, включая if/then/else, циклы for, выражения for, выражения match, try/catch/finally и циклы while. +language: ru +num: 19 +previous-page: string-interpolation +next-page: domain-modeling-intro +--- + + +В Scala есть все структуры управления, которые вы ожидаете найти в языке программирования, в том числе: + +- `if`/`then`/`else` +- циклы `for` +- циклы `while` +- `try`/`catch`/`finally` + +Здесь также есть две другие мощные конструкции, которые вы, возможно, не видели раньше, +в зависимости от вашего опыта программирования: + +- `for` выражения (также известные как _`for` comprehensions_) +- `match` выражения + +Все они продемонстрированы в следующих разделах. + + +## Конструкция if/then/else + +Однострочный Scala оператор `if` выглядит так: + +{% tabs control-structures-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-1 %} +```scala +if (x == 1) println(x) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-1 %} +```scala +if x == 1 then println(x) +``` +{% endtab %} +{% endtabs %} + +Когда необходимо выполнить несколько строк кода после `if`, используется синтаксис: + +{% tabs control-structures-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-2 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-2 %} +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +``` +{% endtab %} +{% endtabs %} + +`if`/`else` синтаксис выглядит так: + +{% tabs control-structures-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-3 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} else { + println("x was not 1") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-3 %} +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +else + println("x was not 1") +``` +{% endtab %} +{% endtabs %} + +А это синтаксис `if`/`else if`/`else`: + +{% tabs control-structures-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-4 %} +```scala +if (x < 0) + println("negative") +else if (x == 0) + println("zero") +else + println("positive") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-4 %} +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` +{% endtab %} +{% endtabs %} + +### Утверждение `end if` + +
+  Это новое в Scala 3 и не поддерживается в Scala 2. +
+ +При желании можно дополнительно включить оператор `end if` в конце каждого выражения: +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +end if +``` + +### `if`/`else` выражения всегда возвращают результат + +Сравнения `if`/`else` образуют _выражения_ - это означает, что они возвращают значение, которое можно присвоить переменной. +Поэтому нет необходимости в специальном тернарном операторе: + +{% tabs control-structures-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-6 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-6 %} +```scala +val minValue = if a < b then a else b +``` +{% endtab %} +{% endtabs %} + + +Поскольку эти выражения возвращают значение, то выражения `if`/`else` можно использовать в качестве тела метода: + +{% tabs control-structures-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-7 %} +```scala +def compare(a: Int, b: Int): Int = + if (a < b) + -1 + else if (a == b) + 0 + else + 1 +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-7 %} +```scala +def compare(a: Int, b: Int): Int = + if a < b then + -1 + else if a == b then + 0 + else + 1 +``` +{% endtab %} +{% endtabs %} + +### В сторону: программирование, ориентированное на выражения + +Кратко о программировании в целом: когда каждое написанное вами выражение возвращает значение, +такой стиль называется _программированием, ориентированным на выражения_, +или EOP (_expression-oriented programming_). +Например, это _выражение_: + +{% tabs control-structures-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-8 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-8 %} +```scala +val minValue = if a < b then a else b +``` +{% endtab %} +{% endtabs %} + +И наоборот, строки кода, которые не возвращают значения, называются _операторами_ +и используются для получения _побочных эффектов_. +Например, эти строки кода не возвращают значения, поэтому они используются для побочных эффектов: + +{% tabs control-structures-9 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-9 %} +```scala +if (a == b) action() +println("Hello") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-9 %} +```scala +if a == b then action() +println("Hello") +``` +{% endtab %} +{% endtabs %} + +В первом примере метод `action` запускается как побочный эффект, когда `a` равно `b`. +Второй пример используется для побочного эффекта печати строки в STDOUT. +Когда вы узнаете больше о Scala, то обнаружите, что пишете больше _выражений_ и меньше _операторов_. + +## Циклы `for` + +В самом простом случае цикл `for` в Scala можно использовать для перебора элементов в коллекции. +Например, имея последовательность целых чисел, +вы можете перебрать ее элементы и вывести их значения следующим образом: + +{% tabs control-structures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-10 %} +```scala +val ints = Seq(1, 2, 3) +for (i <- ints) println(i) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-10 %} +```scala +val ints = Seq(1, 2, 3) +for i <- ints do println(i) +``` +{% endtab %} +{% endtabs %} + + +Код `i <- ints` называется _генератором_. + +Вот как выглядит результат в Scala REPL: + +{% tabs control-structures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for (i <- ints) println(i) +1 +2 +3 +```` +{% endtab %} +{% tab 'Scala 3' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for i <- ints do println(i) +1 +2 +3 +```` +{% endtab %} +{% endtabs %} + + +Если вам нужен многострочный блок кода после генератора `for`, используйте следующий синтаксис: + +{% tabs control-structures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-12 %} +```scala +for (i <- ints) { + val x = i * 2 + println(s"i = $i, x = $x") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-12 %} +```scala +for i <- ints +do + val x = i * 2 + println(s"i = $i, x = $x") +``` +{% endtab %} +{% endtabs %} + + +### Несколько генераторов + +Циклы `for` могут иметь несколько генераторов, как показано в этом примере: + +{% tabs control-structures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-13 %} +```scala +for { + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +} { + println(s"i = $i, j = $j, k = $k") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-13 %} +```scala +for + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +do + println(s"i = $i, j = $j, k = $k") +``` +{% endtab %} +{% endtabs %} + + +Это выражение выводит следующее: + +```` +i = 1, j = a, k = 1 +i = 1, j = a, k = 6 +i = 1, j = b, k = 1 +i = 1, j = b, k = 6 +i = 2, j = a, k = 1 +i = 2, j = a, k = 6 +i = 2, j = b, k = 1 +i = 2, j = b, k = 6 +```` + +### "Ограничители" + +Циклы `for` также могут содержать операторы `if`, известные как _ограничители_ (_guards_): + +{% tabs control-structures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-14 %} +```scala +for { + i <- 1 to 5 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-14 %} +```scala +for + i <- 1 to 5 + if i % 2 == 0 +do + println(i) +``` +{% endtab %} +{% endtabs %} + + +Результат этого цикла: + +```` +2 +4 +```` + +Цикл `for` может содержать столько стражников, сколько необходимо. +В этом примере показан один из способов печати числа `4`: + +{% tabs control-structures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-15 %} +```scala +for { + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-15 %} +```scala +for + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +do + println(i) +``` +{% endtab %} +{% endtabs %} + +### Использование `for` с Maps + +Вы также можете использовать циклы `for` с `Map`. +Например, если задана такая `Map` с аббревиатурами штатов и их полными названиями: + +{% tabs map %} +{% tab 'Scala 2 и 3' for=map %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AR" -> "Arizona" +) +``` +{% endtab %} +{% endtabs %} + +, то можно распечатать ключи и значения, используя `for`. Например: + +{% tabs control-structures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-16 %} +```scala +for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-16 %} +```scala +for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +``` +{% endtab %} +{% endtabs %} + +Вот как это выглядит в REPL: + +{% tabs control-structures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-17 %} +```scala +scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-17 %} +```scala +scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` +{% endtab %} +{% endtabs %} + +Когда цикл `for` перебирает мапу, каждая пара ключ/значение привязывается +к переменным `abbrev` и `fullName`, которые находятся в кортеже: + +```scala +(abbrev, fullName) <- states +``` + +По мере выполнения цикла переменная `abbrev` принимает значение текущего _ключа_ в мапе, +а переменная `fullName` - соответствующему ключу _значению_. + +## Выражение `for` + +В предыдущих примерах циклов `for` все эти циклы использовались для _побочных эффектов_, +в частности для вывода этих значений в STDOUT с помощью `println`. + +Важно знать, что вы также можете создавать _выражения_ `for`, которые возвращают значения. +Вы создаете выражение `for`, добавляя ключевое слово `yield` и возвращаемое выражение, например: + +{% tabs control-structures-18 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-18 %} +```scala +val list = + for (i <- 10 to 12) + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-18 %} +```scala +val list = + for i <- 10 to 12 + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` +{% endtab %} +{% endtabs %} + + +После выполнения этого выражения `for` переменная `list` содержит `Vector` с отображаемыми значениями. +Вот как работает выражение: + +1. Выражение `for` начинает перебирать значения в диапазоне `(10, 11, 12)`. + Сначала оно работает со значением `10`, умножает его на `2`, затем выдает результат - `20`. +2. Далее берет `11` — второе значение в диапазоне. Умножает его на `2`, а затем выдает значение `22`. + Можно представить эти полученные значения как накопление во временном хранилище. +3. Наконец, цикл берет число `12` из диапазона, умножает его на `2`, получая число `24`. + Цикл завершается в этой точке и выдает конечный результат - `Vector(20, 22, 24)`. + +Хотя целью этого раздела является демонстрация выражений `for`, полезно знать, +что показанное выражение `for` эквивалентно вызову метода `map`: + +{% tabs map-call %} +{% tab 'Scala 2 и 3' for=map-call %} +```scala +val list = (10 to 12).map(i => i * 2) +``` +{% endtab %} +{% endtabs %} + +Выражения `for` можно использовать в любой момент, когда вам нужно просмотреть все элементы в коллекции +и применить алгоритм к этим элементам для создания нового списка. + +Вот пример, который показывает, как использовать блок кода после `yield`: + +{% tabs control-structures-19 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-19 %} +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for (name <- names) yield { + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName +} + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-19 %} +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` +{% endtab %} +{% endtabs %} + +### Использование выражения `for` в качестве тела метода + + +Поскольку выражение `for` возвращает результат, его можно использовать в качестве тела метода, +который возвращает полезное значение. +Этот метод возвращает все значения в заданном списке целых чисел, которые находятся между `3` и `10`: + +{% tabs control-structures-20 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-20 %} +```scala +def between3and10(xs: List[Int]): List[Int] = + for { + x <- xs + if x >= 3 + if x <= 10 + } yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-20 %} +```scala +def between3and10(xs: List[Int]): List[Int] = + for + x <- xs + if x >= 3 + if x <= 10 + yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` +{% endtab %} +{% endtabs %} + +## Циклы `while` + +Синтаксис цикла `while` в Scala выглядит следующим образом: + +{% tabs control-structures-21 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-21 %} +```scala +var i = 0 + +while (i < 3) { + println(i) + i += 1 +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-21 %} +```scala +var i = 0 + +while i < 3 do + println(i) + i += 1 +``` +{% endtab %} +{% endtabs %} + +## `match` выражения + +Сопоставление с образцом (_pattern matching_) является основой функциональных языков программирования. +Scala включает в себя pattern matching, обладающий множеством возможностей. + +В самом простом случае можно использовать выражение `match`, подобное оператору Java `switch`, +сопоставляя на основе целочисленного значения. +Как и предыдущие структуры, сопоставление с образцом - это действительно выражение, поскольку оно вычисляет результат: + +{% tabs control-structures-22 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-22 %} +```scala +// `i` is an integer +val day = i match { + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // по умолчанию, перехватывает остальное +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-22 %} +```scala +// `i` is an integer +val day = i match + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // по умолчанию, перехватывает остальное +``` +{% endtab %} +{% endtabs %} + + +В этом примере переменная `i` сопоставляется с заданными числами. +Если она находится между `0` и `6`, `day` принимает значение строки, представляющей день недели. +В противном случае она соответствует подстановочному знаку, представленному символом `_`, +и `day` принимает значение строки `"invalid day"`. + +Поскольку сопоставляемые значения рассматриваются в том порядке, в котором они заданы, +и используется первое совпадение, +случай по умолчанию, соответствующий любому значению, должен идти последним. +Любые сопоставляемые случаи после значения по умолчанию будут помечены как недоступные и будет выведено предупреждение. + +> При написании простых выражений соответствия, подобных этому, рекомендуется использовать аннотацию `@switch` для переменной `i`. +> Эта аннотация содержит предупреждение во время компиляции, если switch не может быть скомпилирован в `tableswitch` +> или `lookupswitch`, которые лучше подходят с точки зрения производительности. + + +### Использование значения по умолчанию + +Когда необходимо получить доступ к универсальному значению по умолчанию в `match` выражении, +просто укажите вместо `_` имя переменной в левой части оператора `case`, +а затем используйте это имя переменной в правой части оператора при необходимости: + +{% tabs control-structures-23 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-23 %} +```scala +i match { + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-23 %} +```scala +i match + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what") +``` +{% endtab %} +{% endtabs %} + +Имя, используемое в шаблоне, должно начинаться со строчной буквы. +Имя, начинающееся с заглавной буквы, не представляет собой новую переменную, +но соответствует значению в области видимости: + +{% tabs control-structures-24 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-24 %} +```scala +val N = 42 +i match { + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-24 %} +```scala +val N = 42 +i match + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +``` +{% endtab %} +{% endtabs %} + +Если `i` равно `42`, то оно будет соответствовать `case N` и напечатает строку `"42"`. +И не достигнет случая по умолчанию. + +### Обработка нескольких возможных совпадений в одной строке + +Как уже упоминалось, `match` выражения многофункциональны. +В этом примере показано, как в каждом операторе `case` использовать несколько возможных совпадений: + +{% tabs control-structures-25 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-25 %} +```scala +val evenOrOdd = i match { + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-25 %} +```scala +val evenOrOdd = i match + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +``` +{% endtab %} +{% endtabs %} + +### Использование `if` стражников в `case` предложениях + +В case выражениях также можно использовать стражников. +В этом примере второй и третий case используют стражников для сопоставления нескольких целочисленных значений: + +{% tabs control-structures-26 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-26 %} +```scala +i match { + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-26 %} +```scala +i match + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +``` +{% endtab %} +{% endtabs %} + + +Вот еще один пример, который показывает, как сопоставить заданное значение с диапазоном чисел: + +{% tabs control-structures-27 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-27 %} +```scala +i match { + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-27 %} +```scala +i match + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +``` +{% endtab %} +{% endtabs %} + + +#### Case классы и сопоставление с образцом + +Вы также можете извлекать поля из `case` классов — и классов, которые имеют корректно написанные методы `apply`/`unapply` — +и использовать их в своих условиях. +Вот пример использования простого case класса `Person`: + +{% tabs control-structures-28 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-28 %} +```scala +case class Person(name: String) + +def speak(p: Person) = p match { + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") +} + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-28 %} +```scala +case class Person(name: String) + +def speak(p: Person) = p match + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` +{% endtab %} +{% endtabs %} + +### Использование `match` выражения в качестве тела метода + +Поскольку `match` выражения возвращают значение, их можно использовать в качестве тела метода. +Метод `isTruthy` принимает в качестве входного параметра значение `Matchable` +и возвращает `Boolean` на основе сопоставления с образцом: + +{% tabs control-structures-29 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-29 %} +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" | false => false + case _ => true +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-29 %} +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` +{% endtab %} +{% endtabs %} + +Входной параметр `a` имеет [тип `Matchable`][matchable], являющийся основой всех типов Scala, +для которых может выполняться сопоставление с образцом. +Метод реализован путем сопоставления на входе в метод, обрабатывая два варианта: +первый проверяет, является ли заданное значение целым числом `0`, пустой строкой или `false` и в этом случае возвращается `false`. +Для любого другого значения в случае по умолчанию мы возвращаем `true`. +Примеры ниже показывают, как работает этот метод: + +{% tabs is-truthy-call %} +{% tab 'Scala 2 и 3' for=is-truthy-call %} +```scala +isTruthy(0) // false +isTruthy(false) // false +isTruthy("") // false +isTruthy(1) // true +isTruthy(" ") // true +isTruthy(2F) // true +``` +{% endtab %} +{% endtabs %} + +Использование сопоставления с образцом в качестве тела метода очень распространено. + +#### Использование различных шаблонов в сопоставлении с образцом + +Для выражения `match` можно использовать множество различных шаблонов. +Например: + +- Сравнение с константой (такое как `case 3 =>`) +- Сравнение с последовательностями (такое как `case List(els : _*) =>`) +- Сравнение с кортежами (такое как `case (x, y) =>`) +- Сравнение с конструктором класса (такое как `case Person(first, last) =>`) +- Сравнение по типу (такое как `case p: Person =>`) + +Все эти виды шаблонов показаны в следующем методе `pattern`, +который принимает входной параметр типа `Matchable` и возвращает `String`: + +{% tabs control-structures-30 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-30 %} +```scala +def pattern(x: Matchable): String = x match { + + // сравнение с константой + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // сравнение с последовательностями + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // сравнение с кортежами + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // сравнение с конструктором класса + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // сравнение по типу + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // значение по умолчанию с подстановочным знаком + case _ => "Unknown" +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-30 %} +```scala +def pattern(x: Matchable): String = x match + + // сравнение с константой + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // сравнение с последовательностями + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // сравнение с кортежами + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // сравнение с конструктором класса + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // сравнение по типу + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // значение по умолчанию с подстановочным знаком + case _ => "Unknown" +``` +{% endtab %} +{% endtabs %} + +## try/catch/finally + +Как и в Java, в Scala есть конструкция `try`/`catch`/`finally`, позволяющая перехватывать исключения и управлять ими. +Для обеспечения согласованности Scala использует тот же синтаксис, что и выражения `match`, +и поддерживает сопоставление с образцом для различных возможных исключений. + +В следующем примере `openAndReadAFile` - это метод, который выполняет то, что следует из его названия: +он открывает файл и считывает из него текст, присваивая результат изменяемой переменной `text`: + +{% tabs control-structures-31 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-31 %} +```scala +var text = "" +try { + text = openAndReadAFile(filename) +} catch { + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +} finally { + // здесь необходимо закрыть ресурсы + println("Came to the 'finally' clause.") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-31 %} +```scala +var text = "" +try + text = openAndReadAFile(filename) +catch + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +finally + // здесь необходимо закрыть ресурсы + println("Came to the 'finally' clause.") +``` +{% endtab %} +{% endtabs %} + +Предполагая, что метод `openAndReadAFile` использует Java `java.io.*` классы для чтения файла +и не перехватывает его исключения, попытка открыть и прочитать файл может привести как к `FileNotFoundException`, +так и к `IOException`, и эти два исключения перехватываются в блоке `catch` этого примера. + +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_ru/scala3/book/domain-modeling-fp.md b/_ru/scala3/book/domain-modeling-fp.md new file mode 100644 index 0000000000..a5bdb326c5 --- /dev/null +++ b/_ru/scala3/book/domain-modeling-fp.md @@ -0,0 +1,825 @@ +--- +layout: multipage-overview +title: Моделирование ФП +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе представлено введение в моделирование предметной области с использованием ФП в Scala 3. +language: ru +num: 23 +previous-page: domain-modeling-oop +next-page: methods-intro +--- + + +В этой главе представлено введение в моделирование предметной области +с использованием функционального программирования (ФП) в Scala 3. +При моделировании окружающего нас мира с помощью ФП обычно используются следующие конструкции Scala: + +- Перечисления +- Case классы +- Trait-ы + +> Если вы не знакомы с алгебраическими типами данных (ADT) и их обобщенной версией (GADT), +> то можете прочитать главу ["Алгебраические типы данных"][adts], прежде чем читать этот раздел. + +## Введение + +В ФП *данные* и *операции над этими данными* — это две разные вещи; вы не обязаны инкапсулировать их вместе, как в ООП. + +Концепция аналогична числовой алгебре. +Когда вы думаете о целых числах, больших либо равных нулю, +у вас есть *набор* возможных значений, который выглядит следующим образом: + +```` +0, 1, 2 ... Int.MaxValue +```` + +Игнорируя деление целых чисел, возможные *операции* над этими значениями такие: + +```` ++, -, * +```` + +Схема ФП реализуется аналогичным образом: + +- описывается свой набор значений (данные) +- описываются операции, которые работают с этими значениями (функции) + +> Как будет видно, рассуждения о программах в этом стиле сильно отличаются от объектно-ориентированного программирования. +> Проще говоря о данных в ФП: +> Отделение функциональности от данных позволяет проверять свои данные, не беспокоясь о поведении. + +В этой главе мы смоделируем данные и операции для “пиццы” в пиццерии. +Будет показано, как реализовать часть “данных” модели Scala/ФП, +а затем - несколько различных способов организации операций с этими данными. + +## Моделирование данных + +В Scala достаточно просто описать модель данных: + +- если необходимо смоделировать данные с различными вариантами, то используется конструкция `enum` (или `case object` в Scala 2) +- если необходимо только сгруппировать сущности (или нужен более детальный контроль), то используются case class-ы + +### Описание вариантов + +Данные, которые просто состоят из различных вариантов, таких как размер корочки, тип корочки и начинка, +кратко моделируются с помощью перечислений: + +{% tabs data_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_1 %} + +В Scala 2 перечисления выражаются комбинацией `sealed class` и нескольких `case object`, которые расширяют класс: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=data_1 %} + +В Scala 3 перечисления кратко выражаются конструкцией `enum`: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +> Типы данных, которые описывают различные варианты (например, `CrustSize`), +> также иногда называются суммированными типами (_sum types_). + +### Описание основных данных + +Пиццу можно рассматривать как _составной_ контейнер с различными атрибутами, указанными выше. +Мы можем использовать `case class`, чтобы описать, +что `Pizza` состоит из `crustSize`, `crustType` и, возможно, нескольких `toppings`: + +{% tabs data_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_2 %} + +```scala +import CrustSize._ +import CrustType._ +import Topping._ + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +{% endtab %} +{% tab 'Scala 3' for=data_2 %} + +```scala +import CrustSize.* +import CrustType.* +import Topping.* + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +{% endtab %} +{% endtabs %} + +> Типы данных, объединяющие несколько компонентов (например, `Pizza`), также иногда называют типами продуктов (_product types_). + +И все. Это модель данных для системы доставки пиццы в стиле ФП. +Решение очень лаконично, поскольку оно не требует объединения модели данных с операциями с пиццей. +Модель данных легко читается, как объявление дизайна для реляционной базы данных. +Также очень легко создавать значения нашей модели данных и проверять их: + +{% tabs data_3 %} +{% tab 'Scala 2 и 3' for=data_3 %} + +```scala +val myFavPizza = Pizza(Small, Regular, Seq(Cheese, Pepperoni)) +println(myFavPizza.crustType) // печатает Regular +``` + +{% endtab %} +{% endtabs %} + +#### Подробнее о модели данных + +Таким же образом можно было бы смоделировать всю систему заказа пиццы. +Вот несколько других `case class`-ов, которые используются для моделирования такой системы: + +{% tabs data_4 %} +{% tab 'Scala 2 и 3' for=data_4 %} + +```scala +case class Address( + street1: String, + street2: Option[String], + city: String, + state: String, + zipCode: String +) + +case class Customer( + name: String, + phone: String, + address: Address +) + +case class Order( + pizzas: Seq[Pizza], + customer: Customer +) +``` + +{% endtab %} +{% endtabs %} + +#### “Узкие доменные объекты” + +В своей книге *Functional and Reactive Domain Modeling*, Debasish Ghosh утверждает, +что там, где специалисты по ООП описывают свои классы как “широкие модели предметной области”, +которые инкапсулируют данные и поведение, +модели данных ФП можно рассматривать как “узкие объекты предметной области”. +Это связано с тем, что, как показано выше, модели данных определяются как `case` классы с атрибутами, +но без поведения, что приводит к коротким и лаконичным структурам данных. + +## Моделирование операций + +Возникает интересный вопрос: поскольку ФП отделяет данные от операций над этими данными, +то как эти операции реализуются в Scala? + +Ответ на самом деле довольно прост: пишутся функции (или методы), работающие со значениями смоделированных данных. +Например, можно определить функцию, которая вычисляет цену пиццы. + +{% tabs data_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_5 %} + +```scala +def pizzaPrice(p: Pizza): Double = p match { + case Pizza(crustSize, crustType, toppings) => { + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_5 %} + +```scala +def pizzaPrice(p: Pizza): Double = p match + case Pizza(crustSize, crustType, toppings) => + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops +``` + +{% endtab %} +{% endtabs %} + +Можно заметить, что реализация функции просто повторяет форму данных: поскольку `Pizza` является case class-ом, +используется сопоставление с образцом для извлечения компонентов, +а затем вызываются вспомогательные функции для вычисления отдельных цен. + +{% tabs data_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_6 %} + +```scala +def toppingPrice(t: Topping): Double = t match { + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_6 %} + +```scala +def toppingPrice(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` + +{% endtab %} +{% endtabs %} + +Точно так же, поскольку `Topping` является перечислением, +используется сопоставление с образцом, чтобы разделить варианты. +Сыр и лук продаются по 50 центов за штуку, остальные — по 75. + +{% tabs data_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_7 %} + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match { + // если размер корочки маленький или средний, тип не важен + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 + } +``` + +{% endtab %} + +{% tab 'Scala 3' for=data_7 %} + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match + // если размер корочки маленький или средний, тип не важен + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 +``` + +{% endtab %} +{% endtabs %} + +Чтобы рассчитать цену корки, мы одновременно сопоставляем образцы как по размеру, так и по типу корки. + +> Важным моментом во всех показанных выше функциях является то, что они являются чистыми функциями (_pure functions_): +> они не изменяют данные и не имеют других побочных эффектов (таких, как выдача исключений или запись в файл). +> Всё, что они делают - это просто получают значения и вычисляют результат. + +## Как организовать функциональность? + +При реализации функции `pizzaPrice`, описанной выше, не было сказано, _где_ ее определять. +Scala предоставляет множество отличных инструментов для организации логики в различных пространствах имен и модулях. + +Существует несколько способов реализации и организации поведения: + +- определить функции в сопутствующих объектах (companion object) +- использовать модульный стиль программирования +- использовать подход “функциональных объектов” +- определить функциональность в методах расширения + +Эти различные решения показаны в оставшейся части этого раздела. + +### Сопутствующий объект + +Первый подход — определить поведение (функции) в сопутствующем объекте. + +> Как обсуждалось в разделе [“Инструменты”][modeling-tools], +> _сопутствующий объект_ — это `object` с тем же именем, что и у класса, и объявленный в том же файле, что и класс. + +При таком подходе в дополнение к `enum` или `case class` также определяется сопутствующий объект с таким же именем, +который содержит поведение (функции). + +{% tabs org_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=org_1 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// сопутствующий объект для case class Pizza +object Pizza { + // тоже самое, что и `pizzaPrice` + def price(p: Pizza): Double = ... +} + +sealed abstract class Topping + +// сопутствующий объект для перечисления Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping + + // тоже самое, что и `toppingPrice` + def price(t: Topping): Double = ... +} +``` + +{% endtab %} +{% tab 'Scala 3' for=org_1 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// сопутствующий объект для case class Pizza +object Pizza: + // тоже самое, что и `pizzaPrice` + def price(p: Pizza): Double = ... + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions + +// сопутствующий объект для перечисления Topping +object Topping: + // тоже самое, что и `toppingPrice` + def price(t: Topping): Double = ... +``` + +{% endtab %} +{% endtabs %} + +При таком подходе можно создать `Pizza` и вычислить ее цену следующим образом: + +{% tabs org_2 %} +{% tab 'Scala 2 и 3' for=org_2 %} + +```scala +val pizza1 = Pizza(Small, Thin, Seq(Cheese, Onions)) +Pizza.price(pizza1) +``` + +{% endtab %} +{% endtabs %} + +Группировка функциональности с помощью сопутствующих объектов имеет несколько преимуществ: + +- связывает функциональность с данными и облегчает их поиск программистам (и компилятору). +- создает пространство имен и, например, позволяет использовать `price` в качестве имени метода, не полагаясь на перегрузку. +- реализация `Topping.price` может получить доступ к значениям перечисления, таким как `Cheese`, без необходимости их импорта. + +Однако также есть несколько компромиссов, которые следует учитывать: + +- модель данных тесно связывается с функциональностью. + В частности, сопутствующий объект должен быть определен в том же файле, что и `case class`. +- неясно, где определять такие функции, как `crustPrice`, + которые с одинаковым успехом можно поместить в сопутствующий объект `CrustSize` или `CrustType`. + +## Модули + +Второй способ организации поведения — использование “модульного” подхода. +В книге _“Программирование на Scala”_ _модуль_ определяется как +“небольшая часть программы с четко определенным интерфейсом и скрытой реализацией”. +Давайте посмотрим, что это значит. + +### Создание интерфейса `PizzaService` + +Первое, о чем следует подумать, — это “поведение” `Pizza`. +Делая это, определяем `trait PizzaServiceInterface` следующим образом: + +{% tabs module_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_1 %} + +```scala +trait PizzaServiceInterface { + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_1 %} + +```scala +trait PizzaServiceInterface: + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +``` + +{% endtab %} +{% endtabs %} + +Как показано, каждый метод принимает `Pizza` в качестве входного параметра вместе с другими параметрами, +а затем возвращает экземпляр `Pizza` в качестве результата. + +Когда пишется такой чистый интерфейс, можно думать о нем как о контракте, +в котором говорится: “Все неабстрактные классы, расширяющие этот trait, должны предоставлять реализацию этих сервисов”. + +На этом этапе также можно представить, что вы являетесь потребителем этого API. +Когда вы это сделаете, будет полезно набросать некоторый пример “потребительского” кода, +чтобы убедиться, что API выглядит так, как хотелось: + +{% tabs module_2 %} +{% tab 'Scala 2 и 3' for=module_2 %} + +```scala +val p = Pizza(Small, Thin, Seq(Cheese)) + +// как вы хотите использовать методы в PizzaServiceInterface +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) +``` + +{% endtab %} +{% endtabs %} + +Если с этим кодом все в порядке, как правило, можно начать набрасывать другой API, например API для заказов, +но, поскольку сейчас рассматривается только `Pizza`, перейдем к созданию конкретной реализации этого интерфейса. + +> Обратите внимание, что обычно это двухэтапный процесс. +> На первом шаге набрасывается контракт API в качестве _интерфейса_. +> На втором шаге создается конкретная _реализация_ этого интерфейса. +> В некоторых случаях в конечном итоге создается несколько конкретных реализаций базового интерфейса. + +### Создание конкретной реализации + +Теперь, когда известно, как выглядит `PizzaServiceInterface`, можно создать конкретную реализацию, +написав тело для всех методов, определенных в интерфейсе: + +{% tabs module_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_3 %} + +```scala +object PizzaService extends PizzaServiceInterface { + + def price(p: Pizza): Double = + ... // реализация была дана выше + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_3 %} + +```scala +object PizzaService extends PizzaServiceInterface: + + def price(p: Pizza): Double = + ... // реализация была дана выше + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) + +end PizzaService +``` + +{% endtab %} +{% endtabs %} + +Хотя двухэтапный процесс создания интерфейса с последующей реализацией не всегда необходим, +явное продумывание API и его использования — хороший подход. + +Когда все готово, можно использовать `Pizza` и `PizzaService`: + +{% tabs module_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_4 %} + +```scala +import PizzaService._ + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// использование методов PizzaService +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // печатает 8.75 +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_4 %} + +```scala +import PizzaService.* + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// использование методов PizzaService +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // печатает 8.75 +``` + +{% endtab %} +{% endtabs %} + +### Функциональные объекты + +В книге _“Программирование на Scala”_ авторы определяют термин “Функциональные объекты” как +“объекты, которые не имеют никакого изменяемого состояния”. +Это также относится к типам в `scala.collection.immutable`. +Например, методы в `List` не изменяют внутреннего состояния, а вместо этого в результате создают копию `List`. + +Об этом подходе можно думать, как о “гибридном дизайне ФП/ООП”, потому что: + +- данные моделируются, используя неизменяемые `case` классы. +- определяется поведение (методы) _того же типа_, что и данные. +- поведение реализуется как чистые функции: они не изменяют никакого внутреннего состояния; скорее - возвращают копию. + +> Это действительно гибридный подход: как и в **дизайне ООП**, методы инкапсулированы в класс с данными, +> но, как это обычно бывает **в дизайне ФП**, методы реализованы как чистые функции, которые данные не изменяют. + +#### Пример + +Используя этот подход, можно напрямую реализовать функциональность пиццы в `case class`: + +{% tabs module_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_5 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) { + + // операции этой модели данных + def price: Double = + pizzaPrice(this) // такая же имплементация, как и выше + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=module_5 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +): + + // операции этой модели данных + def price: Double = + pizzaPrice(this) // такая же имплементация, как и выше + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что в отличие от предыдущих подходов, поскольку это методы класса `Pizza`, +они не принимают ссылку `Pizza` в качестве входного параметра. +Вместо этого у них есть собственная ссылка на текущий экземпляр пиццы - `this`. + +Теперь можно использовать этот новый дизайн следующим образом: + +{% tabs module_6 %} +{% tab 'Scala 2 и 3' for=module_6 %} + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +{% endtab %} +{% endtabs %} + +### Методы расширения + +Методы расширения - подход, который находится где-то между первым (определение функций в сопутствующем объекте) +и последним (определение функций как методов самого типа). + +Методы расширения позволяют создавать API, похожий на API функционального объекта, +без необходимости определять функции как методы самого типа. +Это может иметь несколько преимуществ: + +- модель данных снова _очень лаконична_ и не упоминает никакого поведения. +- можно _задним числом_ развить функциональность типов дополнительными методами, не изменяя исходного определения. +- помимо сопутствующих объектов или прямых методов типов, методы расширения могут быть определены _извне_ в другом файле. + +Вернемся к примеру: + +{% tabs module_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_7 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +implicit class PizzaOps(p: Pizza) { + def price: Double = + pizzaPrice(p) // такая же имплементация, как и выше + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` +В приведенном выше коде мы определяем различные методы для пиццы как методы в _неявном классе_ (_implicit class_). +С `implicit class PizzaOps(p: Pizza)` тогда, где бы `PizzaOps` ни был импортирован, +его методы будут доступны в экземплярах `Pizza`. +Получатель в этом случае `p`. + +{% endtab %} +{% tab 'Scala 3' for=module_7 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +extension (p: Pizza) + def price: Double = + pizzaPrice(p) // такая же имплементация, как и выше + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +``` +В приведенном выше коде мы определяем различные методы для пиццы как _методы расширения_ (_extension methods_). +С помощью `extension (p: Pizza)` мы говорим, что хотим сделать методы доступными для экземпляров `Pizza`. +Получатель в этом случае `p`. + +{% endtab %} +{% endtabs %} + +Используя наши методы расширения, мы можем получить тот же API, что и раньше: + +{% tabs module_8 %} +{% tab 'Scala 2 и 3' for=module_8 %} + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +{% endtab %} +{% endtabs %} + +При этом методы расширения можно определить в любом другом модуле. +Как правило, если вы являетесь разработчиком модели данных, то определяете свои методы расширения в сопутствующем объекте. +Таким образом, они уже доступны всем пользователям. +В противном случае методы расширения должны быть импортированы явно, чтобы их можно было использовать. + +## Резюме функционального подхода + +Определение модели данных в Scala/ФП, как правило, простое: +моделируются варианты данных с помощью перечислений и составных данных с помощью `case` классов. +Затем, чтобы смоделировать поведение, определяются функции, которые работают со значениями модели данных. +Были рассмотрены разные способы организации функций: + +- можно поместить методы в сопутствующие объекты +- можно использовать модульный стиль программирования, разделяющий интерфейс и реализацию +- можно использовать подход “функциональных объектов” и хранить методы в определенном типе данных +- можно использовать методы расширения, чтобы снабдить модель данных функциональностью + +[adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} +[modeling-tools]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_ru/scala3/book/domain-modeling-intro.md b/_ru/scala3/book/domain-modeling-intro.md new file mode 100644 index 0000000000..79828b6d84 --- /dev/null +++ b/_ru/scala3/book/domain-modeling-intro.md @@ -0,0 +1,19 @@ +--- +layout: multipage-overview +title: Моделирование предметной области +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе показано, как можно моделировать предметную область с помощью Scala 3. +language: ru +num: 20 +previous-page: control-structures +next-page: domain-modeling-tools +--- + +В этой главе показано, как можно смоделировать предметную область с помощью Scala 3: + +- В разделе "Инструменты" представлены доступные вам инструменты, включая классы, трейты, перечисления и многое другое. +- В разделе "Моделирование ООП" рассматриваются атрибуты и поведение моделирования в стиле объектно-ориентированного программирования (ООП). +- В разделе "Моделирование ФП" рассматривается моделирование предметной области в стиле функционального программирования (ФП). diff --git a/_ru/scala3/book/domain-modeling-oop.md b/_ru/scala3/book/domain-modeling-oop.md new file mode 100644 index 0000000000..f9de517ddd --- /dev/null +++ b/_ru/scala3/book/domain-modeling-oop.md @@ -0,0 +1,602 @@ +--- +layout: multipage-overview +title: Моделирование ООП +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе представлено введение в моделирование предметной области с использованием ООП в Scala 3. +language: ru +num: 22 +previous-page: domain-modeling-tools +next-page: domain-modeling-fp +--- + +В этой главе представлено введение в моделирование предметной области с использованием +объектно-ориентированного программирования (ООП) в Scala 3. + +## Введение + +Scala предоставляет все необходимые инструменты для объектно-ориентированного проектирования: + +- **Traits** позволяют указывать (абстрактные) интерфейсы, а также конкретные реализации. +- **Mixin Composition** предоставляет инструменты для создания компонентов из более мелких деталей. +- **Классы** могут реализовывать интерфейсы, заданные трейтами. +- **Экземпляры** классов могут иметь свое собственное приватное состояние. +- **Subtyping** позволяет использовать экземпляр одного класса там, где ожидается экземпляр его суперкласса. +- **Модификаторы доступа** позволяют управлять, к каким членам класса можно получить доступ с помощью какой части кода. + +## Трейты + +В отличие от других языков с поддержкой ООП, таких как Java, возможно, +основным инструментом декомпозиции в Scala являются не классы, а трейты. +Они могут служить для описания абстрактных интерфейсов, таких как: + +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait Showable: + def show: String +``` +{% endtab %} +{% endtabs %} + +а также могут содержать конкретные реализации: + +{% tabs traits_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String + def showHtml = "

" + show + "

" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait Showable: + def show: String + def showHtml = "

" + show + "

" +``` +{% endtab %} +{% endtabs %} + +На примере видно, что метод `showHtml` определяется в терминах абстрактного метода `show`. + +[Odersky и Zenger][scalable] представляют _сервис-ориентированную компонентную модель_ и рассматривают: + +- **абстрактные члены** как _требуемые_ службы: их все еще необходимо реализовать в подклассе. +- **конкретные члены** как _предоставляемые_ услуги: они предоставляются подклассу. + +Это видно на примере со `Showable`: определяя класс `Document`, который расширяет `Showable`, +все еще нужно определить `show`, но `showHtml` уже предоставляется: + +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Document(text: String) extends Showable { + def show = text +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class Document(text: String) extends Showable: + def show = text +``` + +{% endtab %} +{% endtabs %} + +#### Абстрактные члены + +Абстрактными в `trait` могут оставаться не только методы. +`trait` может содержать: + +- абстрактные методы (`def m(): T`) +- абстрактные переменные (`val x: T`) +- абстрактные типы (`type T`), потенциально с ограничениями (`type T <: S`) +- абстрактные given (`given t: T`) только в Scala 3 + +Каждая из вышеперечисленных функций может быть использована для определения той или иной формы требований к реализатору `trait`. + +## Смешанная композиция + +Кроме того, что `trait`-ы могут содержать абстрактные и конкретные определения, +Scala также предоставляет мощный способ создания нескольких `trait`: +структура, которую часто называют _смешанной композицией_. + +Предположим, что существуют следующие два (потенциально независимо определенные) `trait`-а: + +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait GreetingService { + def translate(text: String): String + def sayHello = translate("Hello") +} + +trait TranslationService { + def translate(text: String): String = "..." +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait GreetingService: + def translate(text: String): String + def sayHello = translate("Hello") + +trait TranslationService: + def translate(text: String): String = "..." +``` + +{% endtab %} +{% endtabs %} + +Чтобы скомпоновать два сервиса, можно просто создать новый `trait`, расширяющий их: + +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait ComposedService extends GreetingService with TranslationService +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait ComposedService extends GreetingService, TranslationService +``` + +{% endtab %} +{% endtabs %} + +Абстрактные элементы в одном `trait`-е (например, `translate` в `GreetingService`) +автоматически сопоставляются с конкретными элементами в другом `trait`-е. +Это работает не только с методами, как в этом примере, но и со всеми другими абстрактными членами, +упомянутыми выше (то есть типами, переменными и т.д.). + +## Классы + +`trait`-ы отлично подходят для модуляции компонентов и описания интерфейсов (обязательных и предоставляемых). +Но в какой-то момент возникнет необходимость создавать их экземпляры. +При разработке программного обеспечения в Scala часто бывает полезно рассмотреть возможность +использования классов только на начальных этапах модели наследования: + +{% tabs table-traits-cls-summary class=tabs-scala-version %} +{% tab 'Scala 2' %} +| Трейты | `T1`, `T2`, `T3` +| Составные трейты | `S1 extends T1 with T2`, `S2 extends T2 with T3` +| Классы | `C extends S1 with T3` +| Экземпляры | `new C()` +{% endtab %} +{% tab 'Scala 3' %} +| Трейты | `T1`, `T2`, `T3` +| Составные трейты | `S1 extends T1, T2`, `S2 extends T2, T3` +| Классы | `C extends S1, T3` +| Экземпляры | `C()` +{% endtab %} +{% endtabs %} + +Это еще более актуально в Scala 3, где трейты теперь также могут принимать параметры конструктора, +что еще больше устраняет необходимость в классах. + +#### Определение класса + +Подобно `trait`-ам, классы могут расширять несколько `trait`-ов (но только один суперкласс): + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class MyService(name: String) extends ComposedService with Showable { + def show = s"$name says $sayHello" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class MyService(name: String) extends ComposedService, Showable: + def show = s"$name says $sayHello" +``` + +{% endtab %} +{% endtabs %} + +#### Подтипы + +Экземпляр `MyService` создается следующим образом: + +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1: MyService = new MyService("Service 1") +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val s1: MyService = MyService("Service 1") +``` + +{% endtab %} +{% endtabs %} + +С помощью подтипов экземпляр `s1` можно использовать везде, где ожидается любое из расширенных свойств: + +{% tabs class_3 %} +{% tab 'Scala 2 и 3' %} + +```scala +val s2: GreetingService = s1 +val s3: TranslationService = s1 +val s4: Showable = s1 +// ... и так далее ... +``` +{% endtab %} +{% endtabs %} + +#### Планирование расширения + +Как упоминалось ранее, можно расширить еще один класс: + +{% tabs class_4 %} +{% tab 'Scala 2 и 3' %} + +```scala +class Person(name: String) +class SoftwareDeveloper(name: String, favoriteLang: String) + extends Person(name) +``` + +{% endtab %} +{% endtabs %} + +Однако, поскольку `trait`-ы разработаны как основное средство декомпозиции, +то не рекомендуется расширять класс, определенный в одном файле, из другого файла. + +
Открытые классы только в Scala 3
+ +В Scala 3 расширение неабстрактных классов в других файлах ограничено. +Чтобы разрешить это, базовый класс должен быть помечен как `open`: + +{% tabs class_5 %} +{% tab 'Только в Scala 3' %} + +```scala +open class Person(name: String) +``` +{% endtab %} +{% endtabs %} + +Маркировка классов с помощью [`open`][open] - это новая функция Scala 3. +Необходимость явно помечать классы как открытые позволяет избежать многих распространенных ошибок в ООП. +В частности, это требует, чтобы разработчики библиотек явно планировали расширение +и, например, документировали классы, помеченные как открытые. + +## Экземпляры и приватное изменяемое состояние + +Как и в других языках с поддержкой ООП, трейты и классы в Scala могут определять изменяемые поля: + +{% tabs instance_6 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Counter { + // получить значение можно только с помощью метода `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class Counter: + // получить значение можно только с помощью метода `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +``` + +{% endtab %} +{% endtabs %} + +Каждый экземпляр класса `Counter` имеет собственное приватное состояние, +которое можно получить только через метод `count`, как показано в следующем взаимодействии: + +{% tabs instance_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val c1 = new Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val c1 = Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +{% endtab %} +{% endtabs %} + +#### Модификаторы доступа + +По умолчанию все определения элементов в Scala общедоступны. +Чтобы скрыть детали реализации, можно определить элементы (методы, поля, типы и т.д.) в качестве `private` или `protected`. +Таким образом, вы можете управлять доступом к ним или их переопределением. +Закрытые (`private`) элементы видны только самому классу/трейту и его сопутствующему объекту. +Защищенные (`protected`) элементы также видны для подклассов класса. + +## Дополнительный пример: сервис-ориентированный дизайн + +Далее будут проиллюстрированы некоторые расширенные возможности Scala и показано, +как их можно использовать для структурирования более крупных программных компонентов. +Примеры взяты из статьи Мартина Одерски и Маттиаса Зенгера ["Scalable Component Abstractions"][scalable]. +Пример в первую очередь предназначен для демонстрации того, +как использовать несколько функций типа для создания более крупных компонентов. + +Цель состоит в том, чтобы определить программный компонент с семейством типов, +которые могут быть уточнены позже при реализации компонента. +Конкретно, следующий код определяет компонент `SubjectObserver` как `trait` с двумя членами абстрактного типа, +`S` (для субъектов) и `O` (для наблюдателей): + +{% tabs example_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait SubjectObserver { + + type S <: Subject + type O <: Observer + + trait Subject { self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = { + observers = obs :: observers + } + def publish() = { + for ( obs <- observers ) obs.notify(this) + } + } + + trait Observer { + def notify(sub: S): Unit + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait SubjectObserver: + + type S <: Subject + type O <: Observer + + trait Subject: + self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = + observers = obs :: observers + def publish() = + for obs <- observers do obs.notify(this) + + trait Observer: + def notify(sub: S): Unit +``` + +{% endtab %} +{% endtabs %} + +Есть несколько вещей, которые нуждаются в объяснении. + +#### Члены абстрактного типа + +Тип объявления `S <: Subject` говорит, что внутри trait `SubjectObserver` можно ссылаться на +некоторый _неизвестный_ (то есть абстрактный) тип, который называется `S`. +Однако этот тип не является полностью неизвестным: мы знаем, по крайней мере, что это какой-то подтип `Subject`. +Все trait-ы и классы, расширяющие `SubjectObserver`, могут свободно выбирать любой тип для `S`, +если выбранный тип является подтипом `Subject`. +Часть `<: Subject` декларации также упоминается как верхняя граница на `S`. + +#### Вложенные trait-ы + +_В рамках_ trait-а `SubjectObserver` определяются два других trait-а. +trait `Observer`, который определяет только один абстрактный метод `notify` с одним аргументом типа `S`. +Как будет видно, важно, чтобы аргумент имел тип `S`, а не тип `Subject`. + +Второй trait, `Subject`, определяет одно приватное поле `observers` для хранения всех наблюдателей, +подписавшихся на этот конкретный объект. Подписка на объект просто сохраняет объект в списке. +Опять же, тип параметра `obs` - это `O`, а не `Observer`. + +#### Аннотации собственного типа + +Наконец, что означает `self: S =>` в trait-е `Subject`? Это называется аннотацией собственного типа. +И требует, чтобы подтипы `Subject` также были подтипами `S`. +Это необходимо, чтобы иметь возможность вызывать `obs.notify` с `this` в качестве аргумента, +поскольку для этого требуется значение типа `S`. +Если бы `S` был конкретным типом, аннотацию собственного типа можно было бы заменить на `trait Subject extends S`. + +### Реализация компонента + +Теперь можно реализовать вышеуказанный компонент и определить члены абстрактного типа как конкретные типы: + +{% tabs example_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object SensorReader extends SubjectObserver { + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject { + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = { + currentValue = v + publish() + } + } + + class Display extends Observer { + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +object SensorReader extends SubjectObserver: + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject: + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = + currentValue = v + publish() + + class Display extends Observer: + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") +``` + +{% endtab %} +{% endtabs %} + +В частности, мы определяем _singleton_ `object SensorReader`, который расширяет `SubjectObserver`. +В реализации `SensorReader` говорится, что тип `S` теперь определяется как тип `Sensor`, +а тип `O` определяется как тип `Display`. +И `Sensor`, и `Display` определяются как вложенные классы в `SensorReader`, +реализующие trait-ы `Subject` и `Observer` соответственно. + +Помимо того, что этот код является примером сервис-ориентированного дизайна, +он также освещает многие аспекты объектно-ориентированного программирования: + +- Класс `Sensor` вводит свое собственное частное состояние (`currentValue`) + и инкапсулирует изменение состояния за методом `changeValue`. +- Реализация `changeValue` использует метод `publish`, определенный в родительском trait-е. +- Класс `Display` расширяет trait `Observer` и реализует отсутствующий метод `notify`. + +Важно отметить, что реализация `notify` может безопасно получить доступ только к `label` и значению `sub`, +поскольку мы изначально объявили параметр типа `S`. + +### Использование компонента + +Наконец, следующий код иллюстрирует, как использовать компонент `SensorReader`: + +{% tabs example_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import SensorReader._ + +// настройка сети +val s1 = new Sensor("sensor1") +val s2 = new Sensor("sensor2") +val d1 = new Display() +val d2 = new Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// распространение обновлений по сети +s1.changeValue(2) +s2.changeValue(3) + +// печатает: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 + +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +import SensorReader.* + +// настройка сети +val s1 = Sensor("sensor1") +val s2 = Sensor("sensor2") +val d1 = Display() +val d2 = Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// распространение обновлений по сети +s1.changeValue(2) +s2.changeValue(3) + +// печатает: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 +``` + +{% endtab %} +{% endtabs %} + +Имея под рукой все утилиты объектно-ориентированного программирования, в следующем разделе будет продемонстрировано, +как разрабатывать программы в функциональном стиле. + +[scalable]: https://doi.org/10.1145/1094811.1094815 +[open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html diff --git a/_ru/scala3/book/domain-modeling-tools.md b/_ru/scala3/book/domain-modeling-tools.md new file mode 100644 index 0000000000..ec35430443 --- /dev/null +++ b/_ru/scala3/book/domain-modeling-tools.md @@ -0,0 +1,1175 @@ +--- +layout: multipage-overview +title: Инструменты +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этой главе представлено введение в доступные инструменты моделирования предметной области в Scala 3, включая классы, трейты, перечисления и многое другое. +language: ru +num: 21 +previous-page: domain-modeling-intro +next-page: domain-modeling-oop +--- + + +Scala предоставляет множество различных конструкций для моделирования предметной области: + +- Классы +- Объекты +- Сопутствующие объекты +- Трейты +- Абстрактные классы +- Перечисления только в Scala 3 +- Case классы +- Case объекты + +В этом разделе кратко представлена каждая из этих языковых конструкций. + + +## Классы + +Как и в других языках, _класс_ в Scala — это шаблон для создания экземпляров объекта. +Вот несколько примеров классов: + +{% tabs class_1 %} +{% tab 'Scala 2 и 3' %} + +```scala +class Person(var name: String, var vocation: String) +class Book(var title: String, var author: String, var year: Int) +class Movie(var name: String, var director: String, var year: Int) +``` + +{% endtab %} +{% endtabs %} + +Эти примеры показывают, что в Scala есть очень легкий способ объявления классов. + +Все параметры в примерах наших классов определены как `var` поля, а значит, они изменяемы: их можно читать, а также изменять. +Если вы хотите, чтобы они были неизменяемыми — только для чтения — создайте их как `val` поля или используйте case класс. + +До Scala 3 для создания нового экземпляра класса использовалось ключевое слово `new`: + +{% tabs class_2 %} +{% tab 'Scala 2 и 3' %} + +```scala +val p = new Person("Robert Allen Zimmerman", "Harmonica Player") +// --- +``` + +{% endtab %} +{% endtabs %} + +Однако с [универсальными apply методами][creator] в Scala 3 этого больше не требуется: только в Scala 3. + +{% tabs class_3 %} +{% tab 'Только в Scala 3' %} + +```scala +val p = Person("Robert Allen Zimmerman", "Harmonica Player") +``` + +{% endtab %} +{% endtabs %} + +Если у вас есть экземпляр класса, такой как `p`, то вы можете получить доступ к полям экземпляра, +которые в этом примере являются параметрами конструктора: + +{% tabs class_4 %} +{% tab 'Scala 2 и 3' %} + +```scala +p.name // "Robert Allen Zimmerman" +p.vocation // "Harmonica Player" +``` + +{% endtab %} +{% endtabs %} + +Как уже упоминалось, все эти параметры были созданы как `var` поля, поэтому они изменяемые: + +{% tabs class_5 %} +{% tab 'Scala 2 и 3' %} + +```scala +p.name = "Bob Dylan" +p.vocation = "Musician" +``` + +{% endtab %} +{% endtabs %} + +### Поля и методы + +Классы также могут содержать методы и дополнительные поля, не являющиеся частью конструкторов. +Они определены в теле класса. +Тело инициализируется как часть конструктора по умолчанию: + +{% tabs method class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person(var firstName: String, var lastName: String) { + + println("initialization begins") + val fullName = firstName + " " + lastName + + // метод класса + def printFullName: Unit = + // обращение к полю `fullName`, определенному выше + println(fullName) + + printFullName + println("initialization ends") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class Person(var firstName: String, var lastName: String): + + println("initialization begins") + val fullName = firstName + " " + lastName + + // метод класса + def printFullName: Unit = + // обращение к полю `fullName`, определенному выше + println(fullName) + + printFullName + println("initialization ends") +``` + +{% endtab %} +{% endtabs %} + +Следующая сессия REPL показывает, как создать новый экземпляр `Person` с этим классом: + +{% tabs demo-person class=tabs-scala-version %} +{% tab 'Scala 2' %} +````scala +scala> val john = new Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` +{% endtab %} +{% tab 'Scala 3' %} +````scala +scala> val john = Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` +{% endtab %} +{% endtabs %} + +Классы также могут расширять трейты и абстрактные классы, которые мы рассмотрим в специальных разделах ниже. + +### Значения параметров по умолчанию + +В качестве беглого взгляда на некоторые другие функции, +параметры конструктора класса также могут иметь значения по умолчанию: + +{% tabs default-values_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000) { + override def toString = s"timeout: $timeout, linger: $linger" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000): + override def toString = s"timeout: $timeout, linger: $linger" +``` + +{% endtab %} +{% endtabs %} + +Отличительной особенностью этой функции является то, что она позволяет потребителям вашего кода +создавать классы различными способами, как если бы у класса были альтернативные конструкторы: + +{% tabs default-values_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s = new Socket() // timeout: 5000, linger: 5000 +val s = new Socket(2_500) // timeout: 2500, linger: 5000 +val s = new Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = new Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = new Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val s = Socket() // timeout: 5000, linger: 5000 +val s = Socket(2_500) // timeout: 2500, linger: 5000 +val s = Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +{% endtab %} +{% endtabs %} + +При создании нового экземпляра класса вы также можете использовать именованные параметры. +Это особенно полезно, когда несколько параметров имеют одинаковый тип, как показано в этом сравнении: + +{% tabs default-values_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// пример 1 +val s = new Socket(10_000, 10_000) + +// пример 2 +val s = new Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// пример 1 +val s = Socket(10_000, 10_000) + +// пример 2 +val s = Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +{% endtab %} +{% endtabs %} + +### Вспомогательные конструкторы + +Вы можете определить класс с несколькими конструкторами, +чтобы клиенты вашего класса могли создавать его различными способами. +Например, предположим, что вам нужно написать код для моделирования студентов в системе приема в колледж. +При анализе требований вы увидели, что необходимо создавать экземпляр `Student` тремя способами: + +- С именем и государственным удостоверением личности, когда они впервые начинают процесс приема +- С именем, государственным удостоверением личности и дополнительной датой подачи заявки, когда они подают заявку +- С именем, государственным удостоверением личности и студенческим билетом после того, как они будут приняты + +Один из способов справиться с этой ситуацией в стиле ООП - с помощью нижеследующего кода: + +{% tabs structor_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import java.time._ + +// [1] основной конструктор +class Student( + var name: String, + var govtId: String +) { + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] конструктор для студента, подавшего заявку + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = { + this(name, govtId) + _applicationDate = Some(applicationDate) + } + + // [3] конструктор, когда учащийся принят и теперь имеет студенческий билет + def this( + name: String, + govtId: String, + studentId: Int + ) = { + this(name, govtId) + _studentId = studentId + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +import java.time.* + +// [1] основной конструктор +class Student( + var name: String, + var govtId: String +): + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] конструктор для студента, подавшего заявку + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = + this(name, govtId) + _applicationDate = Some(applicationDate) + + // [3] конструктор, когда учащийся принят и теперь имеет студенческий билет + def this( + name: String, + govtId: String, + studentId: Int + ) = + this(name, govtId) + _studentId = studentId +``` + +{% endtab %} +{% endtabs %} + +Класс содержит три конструктора, обозначенных комментариями в коде: + +1. Первичный конструктор, заданный `name` и `govtId` в определении класса +2. Вспомогательный конструктор с параметрами `name`, `govtId` и `applicationDate` +3. Другой вспомогательный конструктор с параметрами `name`, `govtId` и `studentId` + +Эти конструкторы можно вызывать следующим образом: + +{% tabs structor_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1 = new Student("Mary", "123") +val s2 = new Student("Mary", "123", LocalDate.now) +val s3 = new Student("Mary", "123", 456) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +val s1 = Student("Mary", "123") +val s2 = Student("Mary", "123", LocalDate.now) +val s3 = Student("Mary", "123", 456) +``` + +{% endtab %} +{% endtabs %} + +Хотя этот метод можно использовать, имейте в виду, что параметры конструктора также могут иметь значения по умолчанию, +из-за чего создается впечатление, что класс содержит несколько конструкторов. +Это показано в предыдущем примере `Socket`. + +## Объекты + +Объект — это класс, который имеет ровно один экземпляр. +Инициализируется он лениво, тогда, когда на его элементы ссылаются, подобно `lazy val`. +Объекты в Scala позволяют группировать методы и поля в одном пространстве имен, аналогично тому, +как вы используете `static` члены в классе в Java, Javascript (ES6) или `@staticmethod` в Python. + +Объявление `object` аналогично объявлению `class`. +Вот пример объекта “строковые утилиты”, который содержит набор методов для работы со строками: + +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object StringUtils { + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +object StringUtils: + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +``` + +{% endtab %} +{% endtabs %} + +Мы можем использовать объект следующим образом: + +{% tabs object_2 %} +{% tab 'Scala 2 и 3' %} + +```scala +StringUtils.truncate("Chuck Bartowski", 5) // "Chuck" +``` + +{% endtab %} +{% endtabs %} + +Импорт в Scala очень гибкий и позволяет импортировать _все_ члены объекта: + +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import StringUtils._ +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +import StringUtils.* +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +{% endtab %} +{% endtabs %} + +или только _некоторые_: + +{% tabs object_4 %} +{% tab 'Scala 2 и 3' %} + +```scala +import StringUtils.{truncate, containsWhitespace} +truncate("Charles Carmichael", 7) // "Charles" +containsWhitespace("Captain Awesome") // true +isNullOrEmpty("Morgan Grimes") // Not found: isNullOrEmpty (error) +``` + +{% endtab %} +{% endtabs %} + +Объекты также могут содержать поля, доступ к которым также осуществляется как к статическим элементам: + +{% tabs object_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object MathConstants { + val PI = 3.14159 + val E = 2.71828 +} + +println(MathConstants.PI) // 3.14159 +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +object MathConstants: + val PI = 3.14159 + val E = 2.71828 + +println(MathConstants.PI) // 3.14159 +``` + +{% endtab %} +{% endtabs %} + +## Сопутствующие объекты + +Объект `object`, имеющий то же имя, что и класс, и объявленный в том же файле, что и класс, +называется _"сопутствующим объектом"_. Точно так же соответствующий класс называется сопутствующим классом объекта. +Сопутствующие класс или объект могут получить доступ к закрытым членам своего “соседа”. + +Сопутствующие объекты используются для методов и значений, не относящихся к экземплярам сопутствующего класса. +Например, в следующем примере у класса `Circle` есть элемент с именем `area`, специфичный для каждого экземпляра, +а у его сопутствующего объекта есть метод с именем `calculateArea`, +который (а) не специфичен для экземпляра и (б) доступен для каждого экземпляра: + +{% tabs companion class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.math._ + +class Circle(val radius: Double) { + def area: Double = Circle.calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +import scala.math.* + +class Circle(val radius: Double): + def area: Double = Circle.calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area +``` + +{% endtab %} +{% endtabs %} + +В этом примере метод `area`, доступный для каждого экземпляра `Circle`, +использует метод `calculateArea`, определенный в сопутствующем объекте. +Кроме того, поскольку `calculateArea` является приватным, к нему нельзя получить доступ с помощью другого кода, +но, как показано, его могут видеть экземпляры класса `Circle`. + +### Другие виды использования сопутствующих объектов + +Сопутствующие объекты могут использоваться для нескольких целей: + +- их можно использовать для группировки “статических” методов в пространстве имен, как в примере выше + - эти методы могут быть `public` или `private` + - если бы `calculateArea` был `public`, к нему можно было бы получить доступ из любого места как `Circle.calculateArea` +- они могут содержать методы `apply`, которые — благодаря некоторому синтаксическому сахару — + работают как фабричные методы для создания новых экземпляров +- они могут содержать методы `unapply`, которые используются для деконструкции объектов, например, с помощью сопоставления с шаблоном + +Вот краткий обзор того, как методы `apply` можно использовать в качестве фабричных методов для создания новых объектов: + +{% tabs companion-use class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person { + var name = "" + var age = 0 + override def toString = s"$name is $age years old" +} + +object Person { + // фабричный метод с одним аргументом + def apply(name: String): Person = { + var p = new Person + p.name = name + p + } + + // фабричный метод с двумя аргументами + def apply(name: String, age: Int): Person = { + var p = new Person + p.name = name + p.age = age + p + } +} + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +Метод `unapply` здесь не рассматривается, но описан в [Спецификации языка](https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns). + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class Person: + var name = "" + var age = 0 + override def toString = s"$name is $age years old" + +object Person: + + // фабричный метод с одним аргументом + def apply(name: String): Person = + var p = new Person + p.name = name + p + + // фабричный метод с двумя аргументами + def apply(name: String, age: Int): Person = + var p = new Person + p.name = name + p.age = age + p + +end Person + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +Метод `unapply` здесь не рассматривается, но описан в [справочной документации]({{ site.scala3ref }}/changed-features/pattern-matching.html). + +{% endtab %} +{% endtabs %} + +## Трейты + +Если провести аналогию с Java, то Scala `trait` похож на интерфейс в Java 8+. +Trait-ы могут содержать: + +- абстрактные методы и поля +- конкретные методы и поля + +В базовом использовании `trait` может использоваться как интерфейс, определяющий только абстрактные члены, +которые будут реализованы другими классами: + +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Employee { + def id: Int + def firstName: String + def lastName: String +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait Employee: + def id: Int + def firstName: String + def lastName: String +``` + +{% endtab %} +{% endtabs %} + +Однако трейты также могут содержать конкретные члены. +Например, следующий трейт определяет два абстрактных члена — `numLegs` и `walk()` — +а также имеет конкретную реализацию метода `stop()`: + +{% tabs traits_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasLegs { + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +``` + +{% endtab %} +{% endtabs %} + +Вот еще один трейт с абстрактным членом и двумя конкретными реализациями: + +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasTail { + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait HasTail: + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что каждый трейт обрабатывает только очень специфичные атрибуты и поведение: +`HasLegs` имеет дело только с "лапами", а `HasTail` имеет дело только с функциональностью, связанной с хвостом. +Трейты позволяют создавать такие небольшие модули. + +Позже в вашем коде классы могут смешивать несколько трейтов для создания более крупных компонентов: + +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class IrishSetter(name: String) extends HasLegs with HasTail { + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +class IrishSetter(name: String) extends HasLegs, HasTail: + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что класс `IrishSetter` реализует абстрактные члены, определенные в `HasLegs` и `HasTail`. +Теперь вы можете создавать новые экземпляры `IrishSetter`: + +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val d = new IrishSetter("Big Red") // "Big Red is a Dog" +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val d = IrishSetter("Big Red") // "Big Red is a Dog" +``` + +{% endtab %} +{% endtabs %} + +Это всего лишь пример того, чего можно добиться с помощью trait-ов. +Дополнительные сведения см. в остальных уроках по моделированию. + +## Абстрактные классы + +Когда необходимо написать класс, но известно, что в нем будут абстрактные члены, можно создать либо `trait`, либо абстрактный класс. +В большинстве случаев желательно использовать `trait`, но исторически сложилось так, что было две ситуации, +когда предпочтительнее использование абстрактного класса: + +- необходимо создать базовый класс, который принимает аргументы конструктора +- код будет вызван из Java-кода + +### Базовый класс, который принимает аргументы конструктора + +До Scala 3, когда базовому классу нужно было принимать аргументы конструктора, он объявлялся как `abstract class`: + +{% tabs abstract_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +abstract class Pet(name: String) { + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" +} + +class Dog(name: String, var age: Int) extends Pet(name) { + val greeting = "Woof" +} + +val d = new Dog("Fido", 1) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +abstract class Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, var age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +{% endtab %} +{% endtabs %} + +

Параметры в trait только в Scala 3

+ +Однако в Scala 3 трейты теперь могут иметь [параметры][trait-params], +так что теперь вы можете использовать трейты в той же ситуации: + +{% tabs abstract_2 %} + +{% tab 'Только в Scala 3' %} + +```scala +trait Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, var age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +{% endtab %} +{% endtabs %} + +Trait-ы более гибки в составлении, потому что можно смешивать (наследовать) несколько trait-ов, но только один класс. +В большинстве случаев trait-ы следует предпочитать классам и абстрактным классам. +Правило выбора состоит в том, чтобы использовать классы всякий раз, когда необходимо создавать экземпляры определенного типа, +и trait-ы, когда желательно разложить и повторно использовать поведение. + +

Перечисления только в Scala 3

+ +Перечисление (_an enumeration_) может быть использовано для определения типа, +состоящего из конечного набора именованных значений (в разделе, посвященном [моделированию ФП][fp-modeling], +будут показаны дополнительные возможности перечислений). +Базовые перечисления используются для определения наборов констант, +таких как месяцы в году, дни в неделе, направления, такие как север/юг/восток/запад, и многое другое. + +В качестве примера, рассмотрим перечисления, определяющие наборы атрибутов, связанных с пиццами: + +{% tabs enum_1 %} +{% tab 'Только в Scala 3' %} + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +Для использования в коде в первую очередь перечисление нужно импортировать, а затем - использовать: + +{% tabs enum_2 %} +{% tab 'Только в Scala 3' %} + +```scala +import CrustSize.* +val currentCrustSize = Small +``` + +{% endtab %} +{% endtabs %} + +Значения перечислений можно сравнивать (`==`) и использовать в сопоставлении: + +{% tabs enum_3 %} +{% tab 'Только в Scala 3' %} + +```scala +// if/then +if currentCrustSize == Large then + println("You get a prize!") + +// match +currentCrustSize match + case Small => println("small") + case Medium => println("medium") + case Large => println("large") +``` + +{% endtab %} +{% endtabs %} + +### Дополнительные функции перечисления + +Перечисления также могут быть параметризованы: + +{% tabs enum_4 %} +{% tab 'Только в Scala 3' %} + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +{% endtab %} +{% endtabs %} + +И они также могут содержать элементы (например, поля и методы): + +{% tabs enum_5 %} +{% tab 'Только в Scala 3' %} + +```scala +enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = + otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // далее идут остальные планеты ... +``` + +{% endtab %} +{% endtabs %} + +### Совместимость с перечислениями Java + +Если вы хотите использовать перечисления, определенные в Scala, как перечисления Java, +то можете сделать это, расширив класс `java.lang.Enum` (импортированный по умолчанию) следующим образом: + +{% tabs enum_6 %} +{% tab 'Только в Scala 3' %} + +```scala +enum Color extends Enum[Color] { case Red, Green, Blue } +``` + +{% endtab %} +{% endtabs %} + +Параметр типа берется из определения Java `enum` и должен совпадать с типом перечисления. +Нет необходимости предоставлять аргументы конструктора (как определено в документации Java API) для `java.lang.Enum` +при его расширении — компилятор генерирует их автоматически. + +После такого определения `Color` вы можете использовать его так же, как перечисление Java: + +```` +scala> Color.Red.compareTo(Color.Green) +val res0: Int = -1 +```` + +В разделе об [алгебраических типах данных][adts] и [справочной документации][ref-enums] перечисления рассматриваются более подробно. + +## Case class-ы + +Case class используются для моделирования неизменяемых структур данных. +Возьмем следующий пример: + +{% tabs case-classes_1 %} +{% tab 'Scala 2 и 3' %} + +```scala: +case class Person(name: String, relation: String) +``` + +{% endtab %} +{% endtabs %} + +Поскольку мы объявляем `Person` как `case class`, поля `name` и `relation` по умолчанию общедоступны и неизменяемы. +Мы можем создавать экземпляры case классов следующим образом: + +{% tabs case-classes_2 %} +{% tab 'Scala 2 и 3' %} + +```scala +val christina = Person("Christina", "niece") +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что поля не могут быть изменены: + +{% tabs case-classes_3 %} +{% tab 'Scala 2 и 3' %} + +```scala +christina.name = "Fred" // ошибка: reassignment to val +``` + +{% endtab %} +{% endtabs %} + +Поскольку предполагается, что поля case класса неизменяемы, +компилятор Scala может сгенерировать для вас множество полезных методов: + +- Генерируется метод `unapply`, позволяющий выполнять сопоставление с образцом case класса (то есть `case Person(n, r) => ...`). +- В классе генерируется метод `copy`, полезный для создания модифицированных копий экземпляра. +- Генерируются методы `equals` и `hashCode`, использующие структурное равенство, + что позволяет использовать экземпляры case классов в `Map`-ах. +- Генерируется дефолтный метод `toString`, полезный для отладки. + +Эти дополнительные функции показаны в следующем примере: + +{% tabs case-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// Case class-ы можно использовать в качестве шаблонов +christina match { + case Person(n, r) => println("name is " + n) +} + +// для вас генерируются методы `equals` и `hashCode` +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// метод `toString` +println(christina) // Person(Christina,niece) + +// встроенный метод `copy` +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// в результате: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) + +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +// Case class-ы можно использовать в качестве шаблонов +christina match + case Person(n, r) => println("name is " + n) + +// для вас генерируются методы `equals` и `hashCode` +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// метод `toString` +println(christina) // Person(Christina,niece) + +// встроенный метод `copy` +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// в результате: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) +``` + +{% endtab %} +{% endtabs %} + +### Поддержка функционального программирования + +Как уже упоминалось ранее, case class-ы поддерживают функциональное программирование (ФП): + +- ФП избегает изменения структур данных. + Поэтому поля конструктора по умолчанию имеют значение `val`. + Поскольку экземпляры case class не могут быть изменены, ими можно легко делиться, не опасаясь мутаций или условий гонки. +- вместо изменения экземпляра можно использовать метод `copy` в качестве шаблона для создания нового (потенциально измененного) экземпляра. + Этот процесс можно назвать “обновлением по мере копирования”. +- наличие автоматически сгенерированного метода `unapply` позволяет использовать case class в сопоставлении шаблонов. + +## Case object-ы + +Case object-ы относятся к объектам так же, как case class-ы относятся к классам: +они предоставляют ряд автоматически генерируемых методов, чтобы сделать их более мощными. +Case object-ы особенно полезны тогда, когда необходим одноэлементный объект, +который нуждается в небольшой дополнительной функциональности, +например, для использования с сопоставлением шаблонов в выражениях `match`. + +Case object-ы полезны, когда необходимо передавать неизменяемые сообщения. +Например, представим проект музыкального проигрывателя, и создадим набор команд или сообщений: + +{% tabs case-objects_1 %} +{% tab 'Scala 2 и 3' %} + +```scala +sealed trait Message +case class PlaySong(name: String) extends Message +case class IncreaseVolume(amount: Int) extends Message +case class DecreaseVolume(amount: Int) extends Message +case object StopPlaying extends Message +``` + +{% endtab %} +{% endtabs %} + +Затем в других частях кода можно написать методы, которые используют сопоставление с образцом +для обработки входящего сообщения +(при условии, что методы `playSong`, `changeVolume` и `stopPlayingSong` определены где-то еще): + +{% tabs case-objects_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def handleMessages(message: Message): Unit = message match { + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +def handleMessages(message: Message): Unit = message match + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +``` + +{% endtab %} +{% endtabs %} + +[ref-enums]: {{ site.scala3ref }}/enums/enums.html +[adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} +[fp-modeling]: {% link _overviews/scala3-book/domain-modeling-fp.md %} +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[unapply]: {{ site.scala3ref }}/changed-features/pattern-matching.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html diff --git a/_ru/scala3/book/first-look-at-types.md b/_ru/scala3/book/first-look-at-types.md new file mode 100644 index 0000000000..5873df07f7 --- /dev/null +++ b/_ru/scala3/book/first-look-at-types.md @@ -0,0 +1,354 @@ +--- +layout: multipage-overview +title: Первый взгляд на типы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице представлено краткое введение во встроенные типы данных Scala, включая Int, Double, String, Long, Any, AnyRef, Nothing и Null. +language: ru +num: 17 +previous-page: taste-summary +next-page: string-interpolation +--- + +## Все значения имеют тип + +В Scala все значения имеют тип, включая числовые значения и функции. +На приведенной ниже диаграмме показано подмножество иерархии типов. + +Scala 3 Type Hierarchy + +## Иерархия типов Scala + +`Any` - это супертип всех типов, также называемый **верхним типом** (**the top type**). +Он определяет универсальные методы, такие как `equals`, `hashCode` и `toString`. + +У верхнего типа `Any` есть подтип [`Matchable`][matchable], который используется для обозначения всех типов, +для которых возможно выполнить pattern matching (сопоставление с образцом). +Важно гарантировать вызов свойства _“параметричность”_, что вкратце означает, +что мы не можем сопоставлять шаблоны для значений типа `Any`, а только для значений, которые являются подтипом `Matchable`. +[Справочная документация][matchable] содержит более подробную информацию о `Matchable`. + +`Matchable` содержит два важных подтипа: `AnyVal` и `AnyRef`. + +_`AnyVal`_ представляет типы значений. +Существует несколько предопределенных типов значений, и они non-nullable: +`Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit` и `Boolean`. +`Unit` - это тип значения, который не несет никакой значимой информации. Существует ровно один экземпляр `Unit` - `()`. + +_`AnyRef`_ представляет ссылочные типы. Все типы, не являющиеся значениями, определяются как ссылочные типы. +Каждый пользовательский тип в Scala является подтипом `AnyRef`. +Если Scala используется в контексте среды выполнения Java, `AnyRef` соответствует `java.lang.Object`. + +В языках, основанных на операторах, `void` используется для методов, которые ничего не возвращают. +В Scala для методов, которые не имеют возвращаемого значения, +такие как следующий метод, для той же цели используется `Unit`: + +{% tabs unit %} +{% tab 'Scala 2 и 3' for=unit %} + +```scala +def printIt(a: Any): Unit = println(a) +``` + +{% endtab %} +{% endtabs %} + +Вот пример, демонстрирующий, что строки, целые числа, символы, логические значения и функции являются экземплярами `Any` +и могут обрабатываться так же, как и любой другой объект: + +{% tabs any %} +{% tab 'Scala 2 и 3' for=any %} + +```scala +val list: List[Any] = List( + "a string", + 732, // число + 'c', // буква + '\'', // Экранированный символ + true, // булево значение + () => "an anonymous function returning a string" +) + +list.foreach(element => println(element)) +``` + +{% endtab %} +{% endtabs %} + +Код определяет список значений типа `List[Any]`. +Список инициализируется элементами различных типов, но каждый из них является экземпляром `scala.Any`, +поэтому мы можем добавить их в список. + +Вот вывод программы: + +``` +a string +732 +c +' +true + +``` + +## Типы значений в Scala + +Как показано выше, числовые типы Scala расширяют `AnyVal`, и все они являются полноценными объектами. +В этих примерах показано, как объявлять переменные этих числовых типов: + +{% tabs anyval %} +{% tab 'Scala 2 и 3' for=anyval %} + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` + +{% endtab %} +{% endtabs %} + +В первых четырех примерах, если явно не указать тип, то тип числа `1` по умолчанию будет равен `Int`, +поэтому, если нужен один из других типов данных — `Byte`, `Long` или `Short` — необходимо явно объявить эти типы. +Числа с десятичной дробью (например, `2.0`) по умолчанию будут иметь тип `Double`, +поэтому, если необходим `Float`, нужно объявить `Float` явно, как показано в последнем примере. + +Поскольку `Int` и `Double` являются числовыми типами по умолчанию, их можно создавать без явного объявления типа данных: + +{% tabs anynum %} +{% tab 'Scala 2 и 3' for=anynum %} + +```scala +val i = 123 // по умолчанию Int +val x = 1.0 // по умолчанию Double +``` + +{% endtab %} +{% endtabs %} + +Также можно добавить символы `L`, `D`, and `F` (или их эквивалент в нижнем регистре) +для того, чтобы задать `Long`, `Double` или `Float` значения: + +{% tabs type-post %} +{% tab 'Scala 2 и 3' for=type-post %} + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = -3.3F // val z: Float = -3.3 +``` + +Вы также можете использовать шестнадцатеричное представление для форматирования целых чисел +(обычно это `Int`, но также поддерживается суффикс `L` для указания `Long`): + +```scala +val a = 0xACE // val a: Int = 2766 +val b = 0xfd_3aL // val b: Long = 64826 +``` + +Scala поддерживает множество различных способов форматирования одного и того же числа с плавающей запятой, +например: + +```scala +val q = .25 // val q: Double = 0.25 +val r = 2.5e-1 // val r: Double = 0.25 +val s = .0025e2F // val s: Float = 0.25 +``` + +{% endtab %} +{% endtabs %} + +В Scala также есть типы `String` и `Char`, которые обычно можно объявить в неявной форме: + +{% tabs type-string %} +{% tab 'Scala 2 и 3' for=type-string %} + +```scala +val s = "Bill" +val c = 'a' +``` + +{% endtab %} +{% endtabs %} + +Как показано, заключайте строки в двойные кавычки или тройные кавычки для многострочных строк, +а одиночный символ заключайте в одинарные кавычки. + +Типы данных и их диапазоны: + +| Тип данных | Возможные значения | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Boolean | `true` или `false` | +| Byte | 8-битное целое число в дополнении до двух со знаком (от -2^7 до 2^7-1 включительно)
от -128 до 127 | +| Short | 16-битное целое число в дополнении до двух со знаком (от -2^15 до 2^15-1 включительно)
от -32 768 до 32 767 | +| Int | 32-битное целое число с дополнением до двух со знаком (от -2^31 до 2^31-1 включительно)
от -2 147 483 648 до 2 147 483 647 | +| Long | 64-битное целое число с дополнением до двух со знаком (от -2^63 до 2^63-1 включительно)
(от -2^63 до 2^63-1 включительно) | +| Float | 32-разрядный IEEE 754 одинарной точности с плавающей точкой
от 1,40129846432481707e-45 до 3,40282346638528860e+38 | +| Double | 64-битный IEEE 754 двойной точности с плавающей запятой
от 4,94065645841246544e-324 до 1,79769313486231570e+308 | +| Char | 16-битный символ Unicode без знака (от 0 до 2^16-1 включительно)
от 0 до 65 535 | +| String | последовательность `Char` | + +## Строки + +Строки Scala похожи на строки Java, +хотя в отличие от Java (по крайней мере, до Java 15) +в Scala легко создавать многострочные строки с тройными кавычками: + +{% tabs string-mlines1 %} +{% tab 'Scala 2 и 3' for=string-mlines1 %} + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +{% endtab %} +{% endtabs %} + +Одним из недостатков этого базового подхода является то, +что строки после первой строки содержат отступ и выглядят следующим образом: + +{% tabs string-mlines2 %} +{% tab 'Scala 2 и 3' for=string-mlines2 %} + +```scala +"The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting." +``` + +{% endtab %} +{% endtabs %} + +Если важно исключить отступ, можно поставить символ `|` перед всеми строками после первой +и вызвать метод `stripMargin` после строки: + +{% tabs string-mlines3 %} +{% tab 'Scala 2 и 3' for=string-mlines3 %} + +```scala +val quote = """The essence of Scala: + |Fusion of functional and object-oriented + |programming in a typed setting.""".stripMargin +``` + +{% endtab %} +{% endtabs %} + +Теперь все строки выравниваются по левому краю: + +{% tabs string-mlines4 %} +{% tab 'Scala 2 и 3' for=string-mlines4 %} + +```scala +"The essence of Scala: +Fusion of functional and object-oriented +programming in a typed setting." +``` + +{% endtab %} +{% endtabs %} + +Строки Scala также поддерживают мощные методы интерполяции строк, +о которых мы поговорим [в следующей главе][string-interpolation]. + +## `BigInt` и `BigDecimal` + +Для действительно больших чисел можно использовать типы `BigInt` и `BigDecimal`: + +{% tabs type-bigint %} +{% tab 'Scala 2 и 3' for=type-bigint %} + +```scala +val a = BigInt(1_234_567_890_987_654_321L) +val b = BigDecimal(123456.789) +``` + +{% endtab %} +{% endtabs %} + +Где `Double` и `Float` являются приблизительными десятичными числами, +а `BigDecimal` используется для точной арифметики, например, при работе с валютой. + +`BigInt` и `BigDecimal` поддерживают все привычные числовые операторы: + +{% tabs type-bigint2 %} +{% tab 'Scala 2 и 3' for=type-bigint2 %} + +```scala +val b = BigInt(1234567890) // scala.math.BigInt = 1234567890 +val c = b + b // scala.math.BigInt = 2469135780 +val d = b * b // scala.math.BigInt = 1524157875019052100 +``` + +{% endtab %} +{% endtabs %} + +## Приведение типов + +Типы значений могут быть приведены следующим образом: + +Scala Type Hierarchy + +Например: + +{% tabs cast1 %} +{% tab 'Scala 2 и 3' for=cast1 %} + +```scala +val b: Byte = 127 +val i: Int = b // 127 + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +{% endtab %} +{% endtabs %} + +Вы можете привести к типу, только если нет потери информации. +В противном случае вам нужно четко указать приведение типов: + +{% tabs cast2 %} +{% tab 'Scala 2 и 3' for=cast2 %} + +```scala +val x: Long = 987654321 +val y: Float = x.toFloat // 9.8765434E8 (обратите внимание, что требуется `.toFloat`, потому что приведение приводит к потере точности) +val z: Long = y // Ошибка +``` + +{% endtab %} +{% endtabs %} + +Вы также можете привести ссылочный тип к подтипу. +Это будет рассмотрено в книге позже. + +## `Nothing` и `null` + +`Nothing` является подтипом всех типов, также называемым **нижним типом** (**the bottom type**). +Нет значения, которое имело бы тип `Nothing`. +Он обычно сигнализирует о прекращении, таком как thrown exception, выходе из программы или бесконечном цикле - +т.е. это тип выражения, который не вычисляется до определенного значения, или метод, который нормально не возвращается. + +`Null` - это подтип всех ссылочных типов (т.е. любой подтип `AnyRef`). +Он имеет единственное значение, определяемое ключевым словом `null`. +В настоящее время применение `null` считается плохой практикой. +Его следует использовать в основном для взаимодействия с другими языками JVM. +Опция компилятора `opt-in` изменяет статус `Null`, делая все ссылочные типы non-nullable. +Этот параметр может [стать значением по умолчанию][safe-null] в будущей версии Scala. + +В то же время `null` почти никогда не следует использовать в коде Scala. +Альтернативы `null` обсуждаются в главе о [функциональном программировании][fp] и в [документации API][option-api]. + +[reference]: {{ site.scala3ref }}/overview.html +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +[fp]: {% link _overviews/scala3-book/fp-intro.md %} +[string-interpolation]: {% link _overviews/scala3-book/string-interpolation.md %} +[option-api]: https://scala-lang.org/api/3.x/scala/Option.html +[safe-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html diff --git a/_ru/scala3/book/fp-functional-error-handling.md b/_ru/scala3/book/fp-functional-error-handling.md new file mode 100644 index 0000000000..ca3f7857eb --- /dev/null +++ b/_ru/scala3/book/fp-functional-error-handling.md @@ -0,0 +1,436 @@ +--- +layout: multipage-overview +title: Функциональная обработка ошибок +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в функциональную обработку ошибок в Scala 3. +language: ru +num: 46 +previous-page: fp-functions-are-values +next-page: fp-summary +--- + + + +Функциональное программирование похоже на написание ряда алгебраических уравнений, +и поскольку алгебра не имеет null значений или исключений, они не используются и в ФП. +Что поднимает интересный вопрос: как быть в ситуациях, в которых вы обычно используете null значение или исключение программируя в ООП стиле? + +Решение Scala заключается в использовании конструкций, основанных на классах типа `Option`/`Some`/`None`. +Этот урок представляет собой введение в использование такого подхода. + +Примечание: + +- классы `Some` и `None` являются подклассами `Option` +- вместо того чтобы многократно повторять “`Option`/`Some`/`None`”, + следующий текст обычно просто ссылается на “`Option`” или на “классы `Option`” + + +## Первый пример + +Хотя этот первый пример не имеет дело с `null` значениями, это хороший способ познакомиться с классами `Option`. + +Представим, что нужно написать метод, который упрощает преобразование строк в целочисленные значения. +И нужен элегантный способ обработки исключения, которое возникает, +когда метод получает строку типа `"Hello"` вместо `"1"`. +Первое предположение о таком методе может выглядеть следующим образом: + +{% tabs fp-java-try class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Int = + try { + Integer.parseInt(s.trim) + } catch { + case e: Exception => 0 + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Int = + try + Integer.parseInt(s.trim) + catch + case e: Exception => 0 +``` +{% endtab %} + +{% endtabs %} + +Если преобразование работает, метод возвращает правильное значение `Int`, но в случае сбоя метод возвращает `0`. +Для некоторых целей это может быть хорошо, но не совсем точно. +Например, метод мог получить `"0"`, но мог также получить `"foo"`, `"bar"` +или бесконечное количество других строк, которые выдадут исключение. +Это реальная проблема: как определить, когда метод действительно получил `"0"`, а когда получил что-то еще? +При таком подходе нет способа узнать правильный ответ наверняка. + + +## Использование Option/Some/None + +Распространенным решением этой проблемы в Scala является использование классов, +известных как `Option`, `Some` и `None`. +Классы `Some` и `None` являются подклассами `Option`, поэтому решение работает следующим образом: + +- объявляется, что `makeInt` возвращает тип `Option` +- если `makeInt` получает строку, которую он _может_ преобразовать в `Int`, ответ помещается внутрь `Some` +- если `makeInt` получает строку, которую _не может_ преобразовать, то возвращает `None` + +Вот доработанная версия `makeInt`: + +{% tabs fp--try-option class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Option[Int] = + try { + Some(Integer.parseInt(s.trim)) + } catch { + case e: Exception => None + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Option[Int] = + try + Some(Integer.parseInt(s.trim)) + catch + case e: Exception => None +``` +{% endtab %} + +{% endtabs %} + +Этот код можно прочитать следующим образом: +“Когда данная строка преобразуется в целое число, верните значение `Int`, заключенное в `Some`, например `Some(1)`. +Когда строка не может быть преобразована в целое число и генерируется исключение, метод возвращает значение `None`.” + +Эти примеры показывают, как работает `makeInt`: + +{% tabs fp-try-option-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = makeInt("1") // Some(1) +val b = makeInt("one") // None +``` +{% endtab %} + +{% endtabs %} + +Как показано, строка `"1"` приводится к `Some(1)`, а строка `"one"` - к `None`. +В этом суть альтернативного подхода к обработке ошибок. +Данная техника используется для того, чтобы методы могли возвращать _значения_ вместо _исключений_. +В других ситуациях значения `Option` также используются для замены `null` значений. + +Примечание: + +- этот подход используется во всех классах библиотеки Scala, а также в сторонних библиотеках Scala. +- ключевым моментом примера является то, что функциональные методы не генерируют исключения; + вместо этого они возвращают такие значения, как `Option`. + + +## Потребитель makeInt + +Теперь представим, что мы являемся потребителем метода `makeInt`. +Известно, что он возвращает подкласс `Option[Int]`, поэтому возникает вопрос: +как работать с такими возвращаемыми типами? + +Есть два распространенных ответа, в зависимости от потребностей: + +- использование `match` выражений +- использование `for` выражений + +## Использование `match` выражений + +Одним из возможных решений является использование выражения `match`: + +{% tabs fp-option-match class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn’t work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn’t work.") +``` +{% endtab %} + +{% endtabs %} + +В этом примере, если `x` можно преобразовать в `Int`, вычисляется первый вариант в правой части предложения `case`; +если `x` не может быть преобразован в `Int`, вычисляется второй вариант в правой части предложения `case`. + + +## Использование `for` выражений + +Другим распространенным решением является использование выражения `for`, то есть комбинации `for`/`yield`. +Например, представим, что необходимо преобразовать три строки в целочисленные значения, а затем сложить их. +Решение задачи с использованием выражения `for`: + +{% tabs fp-for-comprehension class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} + +{% endtabs %} + +После выполнения этого выражения `y` может принять одно из двух значений: + +- если _все_ три строки конвертируются в значения `Int`, `y` будет равно `Some[Int]`, т.е. целым числом, обернутым внутри `Some` +- если _какая-либо_ из трех строк не может быть преобразована в `Int`, `y` равен `None` + +Это можно проверить на примере: + +{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} + +{% endtabs %} + +С этими демонстрационными данными переменная `y` примет значение `Some(6)`. + +Чтобы увидеть негативный кейс, достаточно изменить любую из строк на что-то, что нельзя преобразовать в целое число. +В этом случае `y` равно `None`: + +{% tabs fp-for-comprehension-failure-result %} + +{% tab 'Scala 2 и 3' %} +```scala +y: Option[Int] = None +``` +{% endtab %} + +{% endtabs %} + + +## Восприятие Option, как контейнера + +Для лучшего восприятия `Option`, его можно представить как _контейнер_: + +- `Some` представляет собой контейнер с одним элементом +- `None` не является контейнером, в нем ничего нет + +Если предпочтительнее думать об `Option` как о ящике, то `None` подобен пустому ящику. +Что-то в нём могло быть, но нет. + + +## Использование `Option` для замены `null` + +Возвращаясь к значениям `null`, место, где `null` значение может незаметно проникнуть в код, — класс, подобный этому: + +{% tabs fp=case-class-nulls %} + +{% tab 'Scala 2 и 3' %} +```scala +class Address( + var street1: String, + var street2: String, + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} + +{% endtabs %} + +Хотя каждый адрес имеет значение `street1`, значение `street2` не является обязательным. +В результате полю `street2` можно присвоить значение `null`: + +{% tabs fp-case-class-nulls-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + null, // <-- О! Значение null! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + null, // <-- О! Значение null! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% endtabs %} + +Исторически сложилось так, что в этой ситуации разработчики использовали пустые строки и значения `null`, +оба варианта это “костыль” для решения основной проблемы: `street2` - _необязательное_ поле. +В Scala и других современных языках правильное решение состоит в том, +чтобы заранее объявить, что `street2` является необязательным: + + +{% tabs fp-case-class-with-options %} + +{% tab 'Scala 2 и 3' %} +```scala +class Address( + var street1: String, + var street2: Option[String], // необязательное значение + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} + +{% endtabs %} + +Теперь можно написать более точный код: + +{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + None, // 'street2' не имеет значения + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + None, // 'street2' не имеет значения + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% endtabs %} + +или так: + +{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% endtabs %} + + + +## `Option` — не единственное решение + +В этом разделе основное внимание уделялось `Option` классам, но у Scala есть несколько других альтернатив. + +Например, три класса, известные как `Try`/`Success`/`Failure`, работают также, +но (а) эти классы в основном используются, когда код может генерировать исключения, +и (б) когда желательно использовать класс `Failure`, потому что он дает доступ к сообщению об исключении. +Например, классы `Try` обычно используются при написании методов, которые взаимодействуют с файлами, +базами данных или интернет-службами, поскольку эти функции могут легко создавать исключения. + + +## Краткое ревью + +Этот раздел был довольно большим, поэтому давайте подведем краткое ревью: + +- функциональные программисты не используют `null` значения +- основной заменой `null` значениям является использование классов `Option` +- функциональные методы не выдают исключений; вместо этого они возвращают такие значения, как `Option`, `Try` или `Either` +- распространенными способами работы со значениями `Option` являются выражения `match` и `for` +- `Option` можно рассматривать как контейнеры с одним элементом (`Some`) и без элементов (`None`) +- `Option` также можно использовать для необязательных параметров конструктора или метода diff --git a/_ru/scala3/book/fp-functions-are-values.md b/_ru/scala3/book/fp-functions-are-values.md new file mode 100644 index 0000000000..9a6cd6c423 --- /dev/null +++ b/_ru/scala3/book/fp-functions-are-values.md @@ -0,0 +1,161 @@ +--- +layout: multipage-overview +title: Функции — это значения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование функций в качестве значений в функциональном программировании. +language: ru +num: 45 +previous-page: fp-pure-functions +next-page: fp-functional-error-handling +--- + + +Хотя каждый когда-либо созданный язык программирования, вероятно, позволяет писать чистые функции, +вторая важная особенность ФП на Scala заключается в том, что _функции можно создавать как значения_, +точно так же, как создаются значения `String` и `Int`. + +Эта особенность даёт много преимуществ, опишем наиболее распространенные из них: +(a) можно определять методы, принимающие в качестве параметров функции +и (b) можно передавать функции в качестве параметров в методы. + +Такой подход можно было наблюдать в предыдущих главах, когда демонстрировались такие методы, как `map` и `filter`: + +{% tabs fp-function-as-values-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = (1 to 10).toList + +val doubles = nums.map(_ * 2) // удваивает каждое значение +val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4) +``` +{% endtab %} + +{% endtabs %} + +В этих примерах анонимные функции передаются в `map` и `filter`. + +> Анонимные функции также известны как _лямбды_ (_lambdas_). + +Помимо передачи анонимных функций в `filter` и `map`, в них также можно передать _методы_: + +{% tabs fp-function-as-values-defined %} + +{% tab 'Scala 2 и 3' %} +```scala +// два метода +def double(i: Int): Int = i * 2 +def underFive(i: Int): Boolean = i < 5 + +// передача этих методов в filter и map +val doubles = nums.filter(underFive).map(double) +``` +{% endtab %} + +{% endtabs %} + +Возможность обращаться с методами и функциями как со значениями — мощное свойство, +предоставляемое языками функционального программирования. + +> Технически функция, которая принимает другую функцию в качестве входного параметра, известна как _функция высшего порядка_. +> (Если вам нравится юмор, как кто-то однажды написал, это все равно, что сказать, +> что класс, который принимает экземпляр другого класса в качестве параметра конструктора, +> является классом высшего порядка.) + + +## Функции, анонимные функции и методы + +В примерах выше анонимная функция это: + +{% tabs fp-anonymous-function-short %} + +{% tab 'Scala 2 и 3' %} +```scala +_ * 2 +``` +{% endtab %} + +{% endtabs %} + +Как было показано в обсуждении [функций высшего порядка][hofs], `_ * 2` - сокращенная версия синтаксиса: + +{% tabs fp-anonymous-function-full %} + +{% tab 'Scala 2 и 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} + +{% endtabs %} + +Такие функции называются “анонимными”, потому что им не присваивается определенное имя. +Для того чтобы это имя задать, достаточно просто присвоить его переменной: + +{% tabs fp-function-assignement %} + +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 +``` +{% endtab %} + +{% endtabs %} + +Теперь появилась именованная функция, назначенная переменной `double`. +Можно использовать эту функцию так же, как используется метод: + +{% tabs fp-function-used-like-method %} + +{% tab 'Scala 2 и 3' %} +```scala +double(2) // 4 +``` +{% endtab %} + +{% endtabs %} + +В большинстве случаев не имеет значения, является ли `double` функцией или методом; +Scala позволяет обращаться с ними одинаково. +За кулисами технология Scala, которая позволяет обращаться с методами так же, +как с функциями, известна как [Eta Expansion][eta]. + +Эта способность беспрепятственно передавать функции в качестве переменных +является отличительной чертой функциональных языков программирования, таких как Scala. +И, как было видно на примерах `map` и `filter`, +возможность передавать функции в другие функции помогает создавать код, +который является кратким и при этом читабельным — _выразительным_. + +Вот еще несколько примеров: + +{% tabs fp-function-as-values-example %} + +{% tab 'Scala 2 и 3' %} +```scala +List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE) +List("bob", "joe").map(_.capitalize) // List(Bob, Joe) +List("plum", "banana").map(_.length) // List(4, 6) + +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(5, 1, 3, 11, 7) +nums.map(_ * 2) // List(10, 2, 6, 22, 14) +nums.filter(_ > 3) // List(5, 11, 7) +nums.takeWhile(_ < 6) // List(5, 1, 3) +nums.sortWith(_ < _) // List(1, 3, 5, 7, 11) +nums.sortWith(_ > _) // List(11, 7, 5, 3, 1) + +nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5) +``` +{% endtab %} + +{% endtabs %} + + +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fp-immutable-values.md b/_ru/scala3/book/fp-immutable-values.md new file mode 100644 index 0000000000..53f63d0b26 --- /dev/null +++ b/_ru/scala3/book/fp-immutable-values.md @@ -0,0 +1,109 @@ +--- +layout: multipage-overview +title: Неизменяемые значения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование неизменяемых значений в функциональном программировании. +language: ru +num: 43 +previous-page: fp-what-is-fp +next-page: fp-pure-functions +--- + +В чистом функциональном программировании используются только неизменяемые значения. +В Scala это означает: + +- все переменные создаются как поля `val` +- используются только неизменяемые классы коллекций, такие как `List`, `Vector` и неизменяемые классы `Map` и `Set` + +Использование только неизменяемых переменных поднимает интересный вопрос: если все статично, как вообще что-то меняется? + +Когда дело доходит до использования коллекций, один из ответов заключается в том, +что существующая коллекция не меняется; вместо этого функция применяется к коллекции, чтобы создать новую. +Именно здесь вступают в действие функции высшего порядка, такие как `map` и `filter`. + +Например, представим, что есть список имен в нижнем регистре — `List[String]`, +и необходимо найти все имена, начинающиеся с буквы `"j"`, чтобы затем сделать первые буквы заглавными. +В ФП код будет выглядеть так: + +{% tabs fp-list %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List("jane", "jon", "mary", "joe") +val b = a.filter(_.startsWith("j")) + .map(_.capitalize) +``` +{% endtab %} + +{% endtabs %} + +Как показано, исходный список `a` не меняется. +Вместо этого к `a` применяется функция фильтрации и преобразования, чтобы создать новую коллекцию, +и результат присваивается неизменяемой переменной `b`. + +Точно так же в ФП не используются классы с изменяемыми параметрами конструктора `var`. +В ФП создание такого класса не привествуется: + +{% tabs fp--class-variables %} + +{% tab 'Scala 2 и 3' %} +```scala +// не стоит этого делать в ФП +class Person(var firstName: String, var lastName: String) + --- --- +``` +{% endtab %} + +{% endtabs %} + +Вместо этого обычно создаются `case` классы, чьи параметры конструктора по умолчанию неизменяемые (`val`): + +{% tabs fp-immutable-case-class %} + +{% tab 'Scala 2 и 3' %} +```scala +case class Person(firstName: String, lastName: String) +``` +{% endtab %} + +{% endtabs %} + +Теперь можно создать экземпляр `Person` как поле `val`: + +{% tabs fp-case-class-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val reginald = Person("Reginald", "Dwight") +``` +{% endtab %} + +{% endtabs %} + +Затем, при необходимости внести изменения в данные, используется метод `copy`, +который поставляется с `case` классом, чтобы “обновлять данные через создание копии”, +например так: + +{% tabs fp-case-class-copy %} + +{% tab 'Scala 2 и 3' %} +```scala +val elton = reginald.copy( + firstName = "Elton", // обновить имя + lastName = "John" // обновить фамилию +) +``` +{% endtab %} + +{% endtabs %} + +Существуют множество других приёмов работы с неизменяемыми коллекциями и переменными. + +> В зависимости от задач вместо `case` классов можно создавать перечисления, trait-ы или классы. +> Для более подробной информации см. главу [“Моделирование данных”][modeling]. + + +[modeling]: {% link _overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_ru/scala3/book/fp-intro.md b/_ru/scala3/book/fp-intro.md new file mode 100644 index 0000000000..5c9b3f5fad --- /dev/null +++ b/_ru/scala3/book/fp-intro.md @@ -0,0 +1,30 @@ +--- +layout: multipage-overview +title: Функциональное программирование +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлено введение в функциональное программирование в Scala 3. +language: ru +num: 41 +previous-page: collections-summary +next-page: fp-what-is-fp +--- + +Scala позволяет писать код в стиле объектно-ориентированного программирования (ООП), +в стиле функционального программирования (ФП), а также в гибридном стиле, используя оба подхода в комбинации. +По словам Martin Odersky, +сущность Scala — это слияние функционального и объектно-ориентированного программирования в типизированной среде: + +- Функции для логики +- Объекты для модульности + +В этой главе предполагается, что вы знакомы с ООП и менее знакомы с ФП, +поэтому в ней представлено краткое введение в несколько основных концепций функционального программирования: + +- Что такое функциональное программирование? +- Неизменяемые значения +- Чистые функции +- Функции — это значения +- Функциональная обработка ошибок diff --git a/_ru/scala3/book/fp-pure-functions.md b/_ru/scala3/book/fp-pure-functions.md new file mode 100644 index 0000000000..47a277a858 --- /dev/null +++ b/_ru/scala3/book/fp-pure-functions.md @@ -0,0 +1,153 @@ +--- +layout: multipage-overview +title: Чистые функции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование чистых функций в функциональном программировании. +language: ru +num: 44 +previous-page: fp-immutable-values +next-page: fp-functions-are-values +--- + + +Еще одна концепция, которую Scala предлагает для помощи в написании функционального кода, — это возможность писать чистые функции. +_Чистая функция_ (_pure function_) может быть определена следующим образом: + +- функция `f` является чистой, если при одних и тех же входных данных `x` она всегда возвращает один и тот же результат `f(x)` +- результат функции зависит _только_ от входных данных и её реализации +- чистые функции только вычисляют результат, ничего не меняя за пределами этих функций + +Из этого следует: + +- чистая функция не изменяет свои входные параметры +- она не мутирует какое-либо скрытое состояние +- у неё нет “черных ходов”: он не читает данные из внешнего мира (включая консоль, веб-сервисы, базы данных, файлы и т.д.) + и не записывает данные вовне + +В результате этого определения каждый раз, когда вызывается чистая функция с одним и тем же входным значением (значениями), +всегда будет выдаваться один и тот же результат. +Например, можно вызывать функцию `double` бесконечное число раз с входным значением `2`, и всегда получать результат `4`. + + +## Примеры чистых функций + +Учитывая это определение, методы в пакете `scala.math._` являются чистыми функциями: + +- `abs` +- `ceil` +- `max` + +Эти методы `String` также являются чистыми функциями: + +- `isEmpty` +- `length` +- `substring` + +Большинство методов в классах коллекций Scala также работают как чистые функции, +включая `drop`, `filter`, `map` и многие другие. + +> В Scala _функции_ и _методы_ почти полностью взаимозаменяемы, +> поэтому, хотя здесь используется общепринятый отраслевой термин “чистая функция”, +> этот термин можно использовать как для описания функций, так и методов. +> Как методы могут использоваться подобно функциям описано в главе [Eta расширение][eta]. + + +## Примеры “грязных” функций + +И наоборот, следующие функции “_грязные_” (_impure_), потому что они нарушают определение pure function: + +- `println` — методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., “грязные” +- `currentTimeMillis` — все методы, связанные с датой и временем, “грязные”, + потому что их вывод зависит от чего-то другого, кроме входных параметров +- `sys.error` — методы генерации исключений “грязные”, потому что они не “просто возвращают результат” + +“Грязные” функции часто делают одно из следующего: + +- читают из скрытого состояния, т.е. обращаются к параметрам и данным, + не переданным в функцию явным образом в качестве входных параметров +- запись в скрытое состояние +- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе +- выполняют какой-либо ввод-вывод с внешним миром + +> В общем, следует остерегаться функций с возвращаемым типом `Unit`. +> Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, - +> это достижение какого-то побочного эффекта. +> Как следствие, часто использование этих функций является “грязным”. + + +## Но грязные функции все же необходимы ... + +Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, поэтому рекомендуется следующее: + +> Напишите ядро вашего приложения, используя только “чистые” функции, +> а затем напишите “грязную” “оболочку” вокруг этого ядра для взаимодействия с внешним миром. +> Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт. + +Важно отметить, что есть способы сделать “нечистое” взаимодействие с внешним миром более “чистым”. +Например, можно услышать об использовании `IO` монады для обработки ввода-вывода. +Эти темы выходят за рамки данного документа, поэтому для простоты можно думать, +что ФП приложения имеют ядро из “чистых” функций, +которые объединены с другими функциями для взаимодействия с внешним миром. + + +## Написание “чистых” функций + +**Примечание**: в этом разделе для обозначения методов Scala часто используется общепринятый в отрасли термин “чистая функция”. + +Для написания чистых функций на Scala, достаточно писать их, +используя синтаксис методов Scala (хотя также можно использовать и синтаксис функций Scala). +Например, вот чистая функция, которая удваивает заданное ей входное значение: + +{% tabs fp-pure-function %} + +{% tab 'Scala 2 и 3' %} +```scala +def double(i: Int): Int = i * 2 +``` +{% endtab %} + +{% endtabs %} + +Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии: + +{% tabs fp-pure-recursive-function class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(xs: List[Int]): Int = xs match { + case Nil => 0 + case head :: tail => head + sum(tail) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(xs: List[Int]): Int = xs match + case Nil => 0 + case head :: tail => head + sum(tail) +``` +{% endtab %} + +{% endtabs %} + +Вышеописанные функции соответствуют определению “чистых”. + + +## Ключевые моменты + +Первым ключевым моментом этого раздела является определение чистой функции: + +> _Чистая функция_ — это функция, которая зависит только от своих объявленных входных данных +> и своей реализации для получения результата. +> Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его. + +Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром. +Таким образом, упрощенный способ представления о функциональных программах состоит в том, +что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром. + + +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fp-summary.md b/_ru/scala3/book/fp-summary.md new file mode 100644 index 0000000000..0e18a20356 --- /dev/null +++ b/_ru/scala3/book/fp-summary.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Этот раздел суммирует предыдущие разделы функционального программирования. +language: ru +num: 47 +previous-page: fp-functional-error-handling +next-page: types-introduction +--- + + +В этой главе представлено общее введение в функциональное программирование на Scala. +Охвачены следующие темы: + +- Что такое функциональное программирование? +- Неизменяемые значения +- Чистые функции +- Функции — это значения +- Функциональная обработка ошибок + +Как уже упоминалось, функциональное программирование — обширная тема, +поэтому все, что мы можем сделать в этой книге, — это коснуться перечисленных вводных понятий. +Дополнительные сведения см. [в справочной документации][reference]. + + +[reference]: {{ site.scala3ref }}/overview.html + diff --git a/_ru/scala3/book/fp-what-is-fp.md b/_ru/scala3/book/fp-what-is-fp.md new file mode 100644 index 0000000000..8b624b5415 --- /dev/null +++ b/_ru/scala3/book/fp-what-is-fp.md @@ -0,0 +1,48 @@ +--- +layout: multipage-overview +title: Что такое функциональное программирование? +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Этот раздел дает ответ на вопрос, что такое функциональное программирование? +language: ru +num: 42 +previous-page: fp-intro +next-page: fp-immutable-values +--- + + +[Wikipedia](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) +определяет _функциональное программирование_ следующим образом: + +
+

+Функциональное программирование — парадигма программирования, +в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних. +Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных +и результатов других функций, и не предполагает явного хранения состояния программы. +Соответственно, не предполагает оно и изменяемость этого состояния. +

+

 

+

+В функциональном программировании функции рассматриваются как “граждане первого класса”, +что означает, что они могут быть привязаны к именам (включая локальные идентификаторы), +передаваться в качестве аргументов и возвращаться из других функций, как и любой другой тип данных. +Это позволяет писать программы в декларативном и составном стиле, где небольшие функции объединяются модульным образом. +

+
+ +Также полезно знать, что опытные функциональные программисты рассматривают свой код математически, +что объединение чистых функций вместе похоже на объединение ряда алгебраических уравнений. + +Когда пишется функциональный код, вы чувствуете себя математиком, и как только понимаете парадигму, +то хотите писать только чистые функции, которые всегда возвращают _значения_, а не исключения или null, +чтобы можно было комбинировать чистые функции вместе. +Ощущение, что вы пишете математические уравнения (выражения), является движущим желанием, +заставляющим использовать _только_ чистые функции и неизменяемые значения - +это то, что используется в алгебре и других формах математики. + +Функциональное программирование - это большая тема, и нет простого способа сжать её всю в одну главу. +В следующих разделах будет представлен обзор основных тем и показаны некоторые инструменты, +предоставляемые Scala для написания функционального кода. diff --git a/_ru/scala3/book/fun-anonymous-functions.md b/_ru/scala3/book/fun-anonymous-functions.md new file mode 100644 index 0000000000..98b7158e8f --- /dev/null +++ b/_ru/scala3/book/fun-anonymous-functions.md @@ -0,0 +1,214 @@ +--- +layout: multipage-overview +title: Анонимные функции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как использовать анонимные функции в Scala, включая примеры с функциями map и filter класса List. +language: ru +num: 29 +previous-page: fun-intro +next-page: fun-function-variables +--- + +Анонимная функция, также известная как _лямбда_, представляет собой блок кода, +который передается в качестве аргумента функции высшего порядка. +Википедия определяет [анонимную функцию](https://en.wikipedia.org/wiki/Anonymous_function) +как “определение функции, не привязанное к идентификатору”. + +Например, возьмем коллекцию: + +{% tabs fun-anonymous-1 %} +{% tab 'Scala 2 и 3' %} +```scala +val ints = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +Можно создать новый список, удвоив каждый элемент в целых числах, используя метод `map` класса `List` +и свою пользовательскую анонимную функцию: + +{% tabs fun-anonymous-2 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map(_ * 2) // List(2, 4, 6) +``` +{% endtab %} +{% endtabs %} + +Как видно из комментария, `doubleInts` содержит список `List(2, 4, 6)`. +В этом примере анонимной функцией является часть кода: + +{% tabs fun-anonymous-3 %} +{% tab 'Scala 2 и 3' %} +```scala +_ * 2 +``` +{% endtab %} +{% endtabs %} + +Это сокращенный способ сказать: “Умножить данный элемент на 2”. + +## Более длинные формы + +Когда вы освоитесь со Scala, то будете постоянно использовать эту форму для написания анонимных функций, +использующих одну переменную в одном месте функции. +Но при желании можете также написать их, используя более длинные формы, +поэтому в дополнение к написанию этого кода: + +{% tabs fun-anonymous-4 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +вы также можете написать его, используя такие формы: + +{% tabs fun-anonymous-5 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +val doubledInts = ints.map((i) => i * 2) +val doubledInts = ints.map(i => i * 2) +``` +{% endtab %} +{% endtabs %} + +Все эти строки имеют одно и то же значение: удваивайте каждый элемент `ints`, чтобы создать новый список, `doubledInts` +(синтаксис каждой формы объясняется ниже). + +Если вы знакомы с Java, вам будет полезно узнать, что эти примеры `map` эквивалентны следующему Java коду: + +{% tabs fun-anonymous-5-b %} +{% tab 'Java' %} +```java +List ints = List.of(1, 2, 3); +List doubledInts = ints.stream() + .map(i -> i * 2) + .collect(Collectors.toList()); +``` +{% endtab %} +{% endtabs %} + +## Сокращение анонимных функций + +Если необходимо явно указать анонимную функцию, можно использовать следующую длинную форму: + +{% tabs fun-anonymous-6 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +Анонимная функция в этом выражении такова: + +{% tabs fun-anonymous-7 %} +{% tab 'Scala 2 и 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +Если незнаком данный синтаксис, то можно воспринимать символ `=>` как преобразователь, +потому что выражение _преобразует_ список параметров в левой части символа (переменная `Int` с именем `i`) +в новый результат, используя алгоритм справа от символа `=>` +(в данном случае выражение, которое удваивает значение `Int`). + + +### Сокращение выражения + +Эту длинную форму можно сократить, как будет показано в следующих шагах. +Во-первых, вот снова самая длинная и явная форма: + +{% tabs fun-anonymous-8 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +Поскольку компилятор Scala может сделать вывод из данных в `ints` о том, что `i` - это `Int`, +`Int` объявление можно удалить: + +{% tabs fun-anonymous-9 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map((i) => i * 2) +``` +{% endtab %} +{% endtabs %} + +Поскольку есть только один аргумент, круглые скобки вокруг параметра `i` не нужны: + +{% tabs fun-anonymous-10 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map(i => i * 2) +``` +{% endtab %} +{% endtabs %} + +Поскольку Scala позволяет использовать символ `_` вместо имени переменной, +когда параметр появляется в функции только один раз, код можно упростить еще больше: + +{% tabs fun-anonymous-11 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +### Ещё короче + +В других примерах можно еще больше упростить анонимные функции. +Например, начиная с самой явной формы, можно распечатать каждый элемент в `ints`, +используя эту анонимную функцию с методом `foreach` класса `List`: + +{% tabs fun-anonymous-12 %} +{% tab 'Scala 2 и 3' %} +```scala +ints.foreach((i: Int) => println(i)) +``` +{% endtab %} +{% endtabs %} + +Как и раньше, объявление `Int` не требуется, а поскольку аргумент всего один, скобки вокруг `i` не нужны: + +{% tabs fun-anonymous-13 %} +{% tab 'Scala 2 и 3' %} +```scala +ints.foreach(i => println(i)) +``` +{% endtab %} +{% endtabs %} + +Поскольку `i` используется в теле функции только один раз, выражение можно еще больше упростить с помощью символа `_`: + +{% tabs fun-anonymous-14 %} +{% tab 'Scala 2 и 3' %} +```scala +ints.foreach(println(_)) +``` +{% endtab %} +{% endtabs %} + +Наконец, если анонимная функция состоит из одного вызова метода с одним аргументом, +нет необходимости явно называть и указывать аргумент, +можно написать только имя метода (здесь, `println`): + +{% tabs fun-anonymous-15 %} +{% tab 'Scala 2 и 3' %} +```scala +ints.foreach(println) +``` +{% endtab %} +{% endtabs %} diff --git a/_ru/scala3/book/fun-eta-expansion.md b/_ru/scala3/book/fun-eta-expansion.md new file mode 100644 index 0000000000..1cbbc3255a --- /dev/null +++ b/_ru/scala3/book/fun-eta-expansion.md @@ -0,0 +1,92 @@ +--- +layout: multipage-overview +title: Eta расширение +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице обсуждается Eta Expansion, технология Scala, которая автоматически и прозрачно преобразует методы в функции. +language: ru +num: 31 +previous-page: fun-function-variables +next-page: fun-hofs +--- + + +Если посмотреть на Scaladoc для метода `map` в классах коллекций Scala, +то можно увидеть, что метод определен для приема _функции_: + +```scala +def map[B](f: (A) => B): List[B] + ----------- +``` + +Действительно, в Scaladoc сказано: “`f` — это _функция_, применяемая к каждому элементу”. +Но, несмотря на это, каким-то образом в `map` можно передать _метод_, и он все еще работает: + +```scala +def times10(i: Int) = i * 10 // метод +List(1, 2, 3).map(times10) // List(10,20,30) +``` + +Как это работает? Как можно передать _метод_ в `map`, который ожидает _функцию_? + +Технология, стоящая за этим, известна как _Eta Expansion_. +Она преобразует выражение _типа метода_ в эквивалентное выражение _типа функции_, и делает это легко и незаметно. + + +## Различия между методами и функциями + +Исторически _методы_ были частью определения класса, хотя в Scala 3 методы могут быть вне классов, +такие как [определения верхнего уровня][toplevel] и [методы расширения][extension]. + +В отличие от методов, _функции_ сами по себе являются полноценными объектами, что делает их объектами первого класса. + +Их синтаксис также отличается. +В этом примере показано, как задать метод и функцию, которые выполняют одну и ту же задачу, +определяя, является ли заданное целое число четным: + +```scala +def isEvenMethod(i: Int) = i % 2 == 0 // метод +val isEvenFunction = (i: Int) => i % 2 == 0 // функция +``` + +Функция действительно является объектом, поэтому ее можно использовать так же, +как и любую другую переменную, например, помещая в список: + +```scala +val functions = List(isEvenFunction) +``` + +И наоборот, технически метод не является объектом, поэтому в Scala 2 метод нельзя было поместить в `List`, +по крайней мере, напрямую, как показано в этом примере: + +```scala +// В этом примере показано сообщение об ошибке в Scala 2 +val methods = List(isEvenMethod) + ^ +error: missing argument list for method isEvenMethod +Unapplied methods are only converted to functions when a function type is expected. +You can make this conversion explicit by writing `isEvenMethod _` or `isEvenMethod(_)` instead of `isEvenMethod`. +``` + +Как показано в этом сообщении об ошибке, в Scala 2 существует ручной способ преобразования метода в функцию, +но важной частью для Scala 3 является то, что технология Eta Expansion улучшена, +поэтому теперь, когда попытаться использовать метод в качестве переменной, +он просто работает — не нужно самостоятельно выполнять ручное преобразование: + +```scala +val functions = List(isEvenFunction) // работает +val methods = List(isEvenMethod) // работает +``` + +Для целей этой вводной книги важно знать следующее: + +- Eta Expansion — технология Scala, позволяющая использовать методы так же, как и функции +- Технология была улучшена в Scala 3, чтобы быть почти полностью бесшовной + +Дополнительные сведения о том, как это работает, см. на [странице Eta Expansion][eta_expansion] в справочной документации. + +[eta_expansion]: {{ site.scala3ref }}/changed-features/eta-expansion.html +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} diff --git a/_ru/scala3/book/fun-function-variables.md b/_ru/scala3/book/fun-function-variables.md new file mode 100644 index 0000000000..667d4e7c30 --- /dev/null +++ b/_ru/scala3/book/fun-function-variables.md @@ -0,0 +1,172 @@ +--- +layout: multipage-overview +title: Параметры функции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как использовать параметры функции в Scala. +language: ru +num: 30 +previous-page: fun-anonymous-functions +next-page: fun-eta-expansion +--- + + +Вернемся к примеру из предыдущего раздела: + +{% tabs fun-function-variables-1 %} +{% tab 'Scala 2 и 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +Анонимной функцией является следующая часть: + +{% tabs fun-function-variables-2 %} +{% tab 'Scala 2 и 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +Причина, по которой она называется _анонимной_ (_anonymous_), заключается в том, +что она не присваивается переменной и, следовательно, не имеет имени. + +Однако анонимная функция, также известная как _функциональный литерал_ (_function literal_), +может быть назначена переменной для создания _функциональной переменной_ (_function variable_): + +{% tabs fun-function-variables-3 %} +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +Код выше создает функциональную переменную с именем `double`. +В этом выражении исходный литерал функции находится справа от символа `=`: + +{% tabs fun-function-variables-4 %} +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 + ----------------- +``` +{% endtab %} +{% endtabs %} + +, а новое имя переменной - слева: + +{% tabs fun-function-variables-5 %} +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 + ------ +``` +{% endtab %} +{% endtabs %} + +список параметров функции подчеркнут: + +{% tabs fun-function-variables-6 %} +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 + -------- +``` +{% endtab %} +{% endtabs %} + +Как и список параметров для метода, список параметров функции означает, +что функция `double` принимает один параметр с типом `Int` и именем `i`. +Как можно видеть ниже, `double` имеет тип `Int => Int`, +что означает, что он принимает один параметр `Int` и возвращает `Int`: + +{% tabs fun-function-variables-7 %} +{% tab 'Scala 2 и 3' %} +```scala +scala> val double = (i: Int) => i * 2 +val double: Int => Int = ... +``` +{% endtab %} +{% endtabs %} + + +### Вызов функции + +Функция `double` может быть вызвана так: + +{% tabs fun-function-variables-8 %} +{% tab 'Scala 2 и 3' %} +```scala +val x = double(2) // 4 +``` +{% endtab %} +{% endtabs %} + +`double` также можно передать в вызов `map`: + +{% tabs fun-function-variables-9 %} +{% tab 'Scala 2 и 3' %} +```scala +List(1, 2, 3).map(double) // List(2, 4, 6) +``` +{% endtab %} +{% endtabs %} + +Кроме того, когда есть другие функции типа `Int => Int`: + +{% tabs fun-function-variables-10 %} +{% tab 'Scala 2 и 3' %} +```scala +val triple = (i: Int) => i * 3 +``` +{% endtab %} +{% endtabs %} + +можно сохранить их в `List` или `Map`: + +{% tabs fun-function-variables-11 %} +{% tab 'Scala 2 и 3' %} +```scala +val functionList = List(double, triple) + +val functionMap = Map( + "2x" -> double, + "3x" -> triple +) +``` +{% endtab %} +{% endtabs %} + +Если вы вставите эти выражения в REPL, то увидите, что они имеют следующие типы: + +{% tabs fun-function-variables-12 %} +{% tab 'Scala 2 и 3' %} +```` +// список, содержащий функции типа `Int => Int` +functionList: List[Int => Int] + +// Map, ключи которой имеют тип `String`, +// а значения имеют тип `Int => Int` +functionMap: Map[String, Int => Int] +```` +{% endtab %} +{% endtabs %} + + + +## Ключевые моменты + +Ключевыми моментами здесь являются: + +- чтобы создать функциональную переменную, достаточно присвоить имя переменной функциональному литералу +- когда есть функция, с ней можно обращаться как с любой другой переменной, то есть как со `String` или `Int` переменной + +А благодаря улучшенной функциональности [Eta Expansion][eta_expansion] в Scala 3 с _методами_ можно обращаться точно так же. + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fun-hofs.md b/_ru/scala3/book/fun-hofs.md new file mode 100644 index 0000000000..805f243533 --- /dev/null +++ b/_ru/scala3/book/fun-hofs.md @@ -0,0 +1,389 @@ +--- +layout: multipage-overview +title: Функции высшего порядка +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как создавать и использовать функции высшего порядка в Scala. +language: ru +num: 32 +previous-page: fun-eta-expansion +next-page: fun-write-map-function +--- + + +Функция высшего порядка (HOF - higher-order function) часто определяется как функция, которая + +- принимает другие функции в качестве входных параметров или +- возвращает функцию в качестве результата. + +В Scala HOF возможны, потому что функции являются объектами первого класса. + +В качестве важного примечания: хотя в этом документе используется общепринятый термин “функция высшего порядка”, +в Scala эта фраза применима как к методам, так и к функциям. +Благодаря [технологии Eta Expansion][eta_expansion] их, как правило, можно использовать в одних и тех же местах. + + +## От потребителя к разработчику + +В примерах, приведенных ранее в документации, было видно, как _пользоваться_ методами, +которые принимают другие функции в качестве входных параметров, например, `map` и `filter`. + +В следующих разделах будет показано, как _создавать_ HOF, в том числе: + +- как писать методы, принимающие функции в качестве входных параметров +- как возвращать функции из методов + +В процессе будет видно: + +- синтаксис, который используется для определения входных параметров функции +- как вызвать функцию, если есть на нее ссылка + +В качестве полезного побочного эффекта, как только синтаксис станет привычным, +его можно начать использовать для определения параметров функций, анонимных функций и функциональных переменных, +а также станет легче читать Scaladoc для функций высшего порядка. + + +## Понимание Scaladoc метода filter + +Чтобы понять, как работают функции высшего порядка, рассмотрим пример: +определим, какой тип функций принимает `filter`, взглянув на его Scaladoc. +Вот определение `filter` в классе `List[A]`: + +{% tabs filter-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def filter(p: A => Boolean): List[A] +``` +{% endtab %} +{% endtabs %} + +Это определение указывает на то, что `filter` - метод, который принимает параметр функции с именем `p`. +По соглашению, `p` обозначает _предикат_, который представляет собой просто функцию, возвращающую `Boolean`. +Таким образом, `filter` принимает предикат `p` в качестве входного параметра и возвращает `List[A]`, +где `A` - тип, содержащийся в списке; если `filter` вызывается для `List[Int]`, то `A` - это тип `Int`. + +На данный момент, если не учитывать назначение метода `filter`, +все, что известно, так это то, что алгоритм каким-то образом использует предикат `p` для создания и возврата `List[A]`. + +Если посмотреть конкретно на параметр функции `p`: + +```scala +p: A => Boolean +``` + +, то эта часть описания `filter` означает, что любая передаваемая функция +должна принимать тип `A` в качестве входного параметра и возвращать `Boolean`. +Итак, если список представляет собой список `List[Int]`, +то можно заменить универсальный тип `A` на `Int` и прочитать эту подпись следующим образом: + +```scala +p: Int => Boolean +``` + +Поскольку `isEven` имеет такой же тип — преобразует входное значение `Int` в результирующее `Boolean` — +его можно использовать с `filter`. + + +## Написание методов, которые принимают параметры функции + +Рассмотрим пример написания методов, которые принимают функции в качестве входных параметров. + +**Примечание:** для определенности, будем называть код, который пишется, _методом_, +а код, принимаемый в качестве входного параметра, — _функцией_. + +### Пример + +Чтобы создать метод, который принимает функцию в качестве параметра, необходимо: + +1. в списке параметров метода определить сигнатуру принимаемой функции +2. использовать эту функцию внутри метода + +Чтобы продемонстрировать это, вот метод, который принимает входной параметр с именем `f`, где `f` — функция: + + +{% tabs sayHello-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def sayHello(f: () => Unit): Unit = f() +``` +{% endtab %} +{% endtabs %} + +Эта часть кода — _сигнатура типа (type signature)_ — утверждает, что `f` является функцией, +и определяет типы функций, которые будет принимать метод `sayHello`: + +```scala +f: () => Unit +``` + +Как это работает: + +- `f` — имя входного параметра функции. + Аналогично тому, как параметр `String` обычно называется `s` или параметр `Int` - `i` +- сигнатура типа `f` определяет _тип_ функций, которые будет принимать метод +- часть `()` подписи `f` (слева от символа `=>`) указывает на то, что `f` не принимает входных параметров +- часть сигнатуры `Unit` (справа от символа `=>`) указывает на то, что функция `f` не должна возвращать осмысленный результат +- в теле метода `sayHello` (справа от символа `=`) оператор `f()` вызывает переданную функцию + +Теперь, когда `sayHello` определен, создадим функцию, соответствующую сигнатуре `f`, чтобы ее можно было проверить. +Следующая функция не принимает входных параметров и ничего не возвращает, поэтому она соответствует сигнатуре типа `f`: + +{% tabs helloJoe-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def helloJoe(): Unit = println("Hello, Joe") +``` +{% endtab %} +{% endtabs %} + + +Поскольку сигнатуры типов совпадают, можно передать `helloJoe` в `sayHello`: + +{% tabs sayHello-usage %} +{% tab 'Scala 2 и 3' %} +```scala +sayHello(helloJoe) // печатает "Hello, Joe" +``` +{% endtab %} +{% endtabs %} + +Если вы никогда этого не делали раньше, поздравляем: +был определен метод с именем `sayHello`, который принимает функцию в качестве входного параметра, +а затем вызывает эту функцию в теле своего метода. + + +### sayHello может принимать разные функции + +Важно знать, что преимущество этого подхода заключается не в том, +что `sayHello` может принимать одну функцию в качестве входного параметра; +преимущество в том, что `sayHello` может принимать любую функцию, соответствующую сигнатуре `f`. +Например, поскольку следующая функция не принимает входных параметров и ничего не возвращает, она также работает с `sayHello`: + +{% tabs bonjourJulien-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def bonjourJulien(): Unit = println("Bonjour, Julien") +``` +{% endtab %} +{% endtabs %} + +Вот что выводится в REPL: + +{% tabs bonjourJulien-usage %} +{% tab 'Scala 2 и 3' %} +```` +scala> sayHello(bonjourJulien) +Bonjour, Julien +```` +{% endtab %} +{% endtabs %} + +Это отличный старт. +Рассмотрим ещё несколько примеров того, как определять сигнатуры различных типов для параметров функции. + + +## Общий синтаксис для определения входных параметров функции + +В методе: + +{% tabs sayHello-definition-2 %} +{% tab 'Scala 2 и 3' %} +```scala +def sayHello(f: () => Unit): Unit +``` +{% endtab %} +{% endtabs %} + +сигнатурой типа для `f` является: + +```scala +() => Unit +``` + +Это сигнатура означает “функцию, которая не принимает входных параметров и не возвращает ничего значимого (`Unit`)”. + +Вот сигнатура функции, которая принимает параметр `String` и возвращает `Int`: + +```scala +f: String => Int +``` + +Какие функции принимают строку и возвращают целое число? +Например, такие, как “длина строки” и контрольная сумма. + +Эта функция принимает два параметра `Int` и возвращает `Int`: + +```scala +f: (Int, Int) => Int +``` + +Какие функции соответствуют данной сигнатуре? + +Любая функция, которая принимает два входных параметра `Int` и возвращает `Int`, +соответствует этой сигнатуре, поэтому все “функции” ниже (точнее, методы) подходят: + +{% tabs add-sub-mul-definitions %} +{% tab 'Scala 2 и 3' %} +```scala +def add(a: Int, b: Int): Int = a + b +def subtract(a: Int, b: Int): Int = a - b +def multiply(a: Int, b: Int): Int = a * b +``` +{% endtab %} +{% endtabs %} + +Из примеров выше можно сделать вывод, что общий синтаксис сигнатуры функций такой: + +```scala +variableName: (parameterTypes ...) => returnType +``` + +> Поскольку функциональное программирование похоже на создание и объединение ряда алгебраических уравнений, +> обычно _много думают_ о типах при разработке функций и приложений. +> Можно сказать, что “думают типами”. + + +## Параметр функции вместе с другими параметрами + +Чтобы HOFs стали действительно полезными, им также нужны некоторые данные для работы. +Для класса, подобного `List`, в его методе `map` уже есть данные для работы: элементы в `List`. +Но для автономного приложения, у которого нет собственных данных, +метод также должен принимать в качестве других входных параметров данные. + +Рассмотрим пример метода с именем `executeNTimes`, который имеет два входных параметра: функцию и `Int`: + +{% tabs executeNTimes-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for (i <- 1 to n) f() +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for i <- 1 to n do f() +``` +{% endtab %} +{% endtabs %} + +Как видно из кода, `executeNTimes` выполняет функцию `f` `n` раз. +Поскольку простой цикл `for`, подобный этому, не имеет возвращаемого значения, `executeNTimes` возвращает `Unit`. + +Чтобы протестировать `executeNTimes`, определим метод, соответствующий сигнатуре `f`: + +{% tabs helloWorld-definition %} +{% tab 'Scala 2 и 3' %} +```scala +// тип метода - `() => Unit` +def helloWorld(): Unit = println("Hello, world") +``` +{% endtab %} +{% endtabs %} + +Затем передадим этот метод в `executeNTimes` вместе с `Int`: + +{% tabs helloWorld-usage %} +{% tab 'Scala 2 и 3' %} +``` +scala> executeNTimes(helloWorld, 3) +Hello, world +Hello, world +Hello, world +``` +{% endtab %} +{% endtabs %} + + +Великолепно. +Метод `executeNTimes` трижды выполняет функцию `helloWorld`. + + +### Столько параметров, сколько необходимо + +Методы могут усложняться по мере необходимости. +Например, этот метод принимает функцию типа `(Int, Int) => Int` вместе с двумя входными параметрами: + +{% tabs executeAndPrint-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = + println(f(i, j)) +``` +{% endtab %} +{% endtabs %} + + +Поскольку методы `sum` и `multiply` соответствуют сигнатуре `f`, +их можно передать в `executeAndPrint` вместе с двумя значениями `Int`: + +{% tabs executeAndPrint-usage %} +{% tab 'Scala 2 и 3' %} +```scala +def sum(x: Int, y: Int) = x + y +def multiply(x: Int, y: Int) = x * y + +executeAndPrint(sum, 3, 11) // печатает 14 +executeAndPrint(multiply, 3, 9) // печатает 27 +``` +{% endtab %} +{% endtabs %} + + +## Согласованность подписи типа функции + +Самое замечательное в изучении сигнатур типов функций Scala заключается в том, +что синтаксис, используемый для определения входных параметров функции, — +это тот же синтаксис, что используется для написания литералов функций. + +Например, если необходимо написать функцию, вычисляющую сумму двух целых чисел, её можно было бы написать так: + +{% tabs f-val-definition %} +{% tab 'Scala 2 и 3' %} +```scala +val f: (Int, Int) => Int = (a, b) => a + b +``` +{% endtab %} +{% endtabs %} + +Этот код состоит из сигнатуры типа: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +входных параметров: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ------ +```` + +и тела функции: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----- +```` + +Согласованность Scala состоит в том, что тип функции: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +совпадает с сигнатурой типа, используемого для определения входного параметра функции: + +```` +def executeAndPrint(f: (Int, Int) => Int, ... + ----------------- +```` + +По мере освоения этого синтаксиса, становится привычным его использование для определения параметров функций, +анонимных функций и функциональных переменных, а также становится легче читать Scaladoc для функций высшего порядка. + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fun-intro.md b/_ru/scala3/book/fun-intro.md new file mode 100644 index 0000000000..01d2080096 --- /dev/null +++ b/_ru/scala3/book/fun-intro.md @@ -0,0 +1,17 @@ +--- +layout: multipage-overview +title: Функции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе рассматриваются темы, связанные с функциями в Scala 3. +language: ru +num: 28 +previous-page: methods-summary +next-page: fun-anonymous-functions +--- + +Если в предыдущей главе были представлены Scala _методы_, то в этой главе мы углубимся в _функции_. +Рассматриваемые темы включают анонимные функции, функциональные переменные и функции высшего порядка (HOF), +в том числе способы создания собственных HOF. diff --git a/_ru/scala3/book/fun-summary.md b/_ru/scala3/book/fun-summary.md new file mode 100644 index 0000000000..20391f5af9 --- /dev/null +++ b/_ru/scala3/book/fun-summary.md @@ -0,0 +1,41 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен обзор предыдущего раздела 'Функции'. +language: ru +num: 35 +previous-page: fun-write-method-returns-function +next-page: packaging-imports +--- + +Это была длинная глава, поэтому давайте рассмотрим ключевые моменты, которые мы прошли. + +Функция высшего порядка (HOF) часто определяется как функция, +которая принимает другие функции в качестве входных параметров или возвращает функцию в качестве своего значения. +В Scala это возможно, потому что функции являются объектами первого класса. + +Двигаясь по разделам, сначала вы узнали: + +- Как писать анонимные функции в виде небольших фрагментов кода. +- Как передать их десяткам HOF (методов) в классах коллекций, т.е. таким методам, как `filter`, `map` и т.д. +- Как с помощью этих небольших фрагментов кода и мощных HOF создавать множество функций с помощью небольшого кода. + +Изучив анонимные функции и HOF, вы узнали: + +- Функциональные переменные — это просто анонимные функции, привязанные к переменной. + +Увидев, как быть потребителем HOF, вы увидели, как стать создателем HOF. +В частности, вы узнали: + +- Как писать методы, принимающие функции в качестве входных параметров +- Как вернуть функцию из метода + +Полезным побочным эффектом этой главы является то, +что вы увидели много примеров того, как объявлять сигнатуры типов для функций. +Преимущество этого заключается в том, что вы используете один и тот же синтаксис +для определения параметров функций, анонимных функций и функциональных переменных, +а также становится легче читать Scaladoc для функций высшего порядка, таких как `map`, `filter` и другие. diff --git a/_ru/scala3/book/fun-write-map-function.md b/_ru/scala3/book/fun-write-map-function.md new file mode 100644 index 0000000000..f7b6a0f63e --- /dev/null +++ b/_ru/scala3/book/fun-write-map-function.md @@ -0,0 +1,141 @@ +--- +layout: multipage-overview +title: Собственный map +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице описано, как создать свой собственный метод map +language: ru +num: 33 +previous-page: fun-hofs +next-page: fun-write-method-returns-function +--- + + +Теперь, когда известно, как писать собственные функции высшего порядка, рассмотрим более реальный пример. + +Представим, что у класса `List` нет метода `map`, и есть необходимость его написать. +Первым шагом при создании функций является точное определение проблемы. +Сосредоточившись только на `List[Int]`, получаем: + +> Необходимо написать метод `map`, который можно использовать для применения функции к каждому элементу в `List[Int]`, +> возвращая преобразованные элементы в виде нового списка. + +Учитывая это утверждение, начнем писать сигнатуру метода. +Во-первых, известно, что функция должна приниматься в качестве параметра, +и эта функция должна преобразовать `Int` в какой-то общий тип `A`, поэтому получаем: + +{% tabs map-accept-func-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def map(f: (Int) => A) +``` +{% endtab %} +{% endtabs %} + +Синтаксис использования универсального типа требует объявления этого символа типа перед списком параметров, +поэтому добавляем объявление типа: + +{% tabs map-type-symbol-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def map[A](f: (Int) => A) +``` +{% endtab %} +{% endtabs %} + +Далее известно, что `map` также должен принимать `List[Int]`: + +{% tabs map-list-int-param-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]) +``` +{% endtab %} +{% endtabs %} + +Наконец, также известно, что `map` возвращает преобразованный список, содержащий элементы универсального типа `A`: + +{% tabs map-with-return-type-definition %} +{% tab 'Scala 2 и 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = ??? +``` +{% endtab %} +{% endtabs %} + +Теперь все, что нужно сделать, это написать тело метода. +Метод `map` применяет заданную им функцию к каждому элементу в заданном списке для создания нового преобразованного списка. +Один из способов сделать это - использовать выражение `for`: + +{% tabs for-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + +`for` выражения зачастую делают код удивительно простым, и в данном случае - это все тело метода. + +Объединив `for` с сигнатурой метода, получим автономный метод `map`, который работает с `List[Int]`: + +{% tabs map-function class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + + +### Обобщим метод map + +Обратим внимание, что выражение `for` не делает ничего, что зависит от типа `Int` внутри списка. +Следовательно, можно заменить `Int` в сигнатуре типа параметром универсального типа `B`: + +{% tabs map-function-full-generic class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + +Получился метод `map`, который работает с любым списком. + +Демонстрация работы получившегося `map`: + +{% tabs map-use-example %} +{% tab 'Scala 2 и 3' %} +```scala +def double(i : Int): Int = i * 2 +map(double, List(1, 2, 3)) // List(2, 4, 6) + +def strlen(s: String): Int = s.length +map(strlen, List("a", "bb", "ccc")) // List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +Теперь, когда рассмотрены методы, принимающие функции в качестве входных параметров, перейдем к методам, возвращающим функции. diff --git a/_ru/scala3/book/fun-write-method-returns-function.md b/_ru/scala3/book/fun-write-method-returns-function.md new file mode 100644 index 0000000000..a2bb66c69c --- /dev/null +++ b/_ru/scala3/book/fun-write-method-returns-function.md @@ -0,0 +1,176 @@ +--- +layout: multipage-overview +title: Создание метода, возвращающего функцию +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показано, как создавать методы, возвращающие функции, в Scala. +language: ru +num: 34 +previous-page: fun-write-map-function +next-page: fun-summary +--- + + +Благодаря согласованности Scala написание метода, возвращающего функцию, похоже на то, что было описано в предыдущих разделах. +Например, представьте, что вы хотите написать метод `greet`, возвращающий функцию. +Еще раз начнем с постановки проблемы: + +> Необходимо создать метод greet, возвращающий функцию. +> Эта функция должна принимать строковый параметр и печатать его с помощью `println`. +> Начнем с простого шага: `greet` не принимает никаких входных параметров, а просто создает функцию и возвращает её. + +Учитывая это утверждение, можно начать создавать `greet`. +Известно, что это будет метод: + +```scala +def greet() +``` + +Также известно, что этот метод должен возвращать функцию, которая (a) принимает параметр `String` и +(b) печатает эту строку с помощью `println`. + +Следовательно, эта функция имеет тип `String => Unit`: + +```scala +def greet(): String => Unit = ??? + ---------------- +``` + +Теперь нужно просто создать тело метода. +Известно, что метод должен возвращать функцию, и эта функция принимает `String` и печатает ее. +Эта анонимная функция соответствует следующему описанию: + +```scala +(name: String) => println(s"Hello, $name") +``` + +Теперь вы просто возвращаете эту функцию из метода: + +```scala +// метод, который возвращает функцию +def greet(): String => Unit = + (name: String) => println(s"Hello, $name") +``` + +Поскольку этот метод возвращает функцию, вы получаете функцию, вызывая `greet()`. +Это хороший шаг для проверки в REPL, потому что он проверяет тип новой функции: + +```` +scala> val greetFunction = greet() +val greetFunction: String => Unit = Lambda.... + ----------------------------- +```` + +Теперь можно вызвать `greetFunction`: + +```scala +greetFunction("Joe") // печатает "Hello, Joe" +``` + +Поздравляем, вы только что создали метод, возвращающий функцию, а затем запустили её. + + +## Доработка метода + +Метод `greet` был бы более полезным, если бы была возможность задавать приветствие. +Например, передать его в качестве параметра методу `greet` и использовать внутри `println`: + +```scala +def greet(theGreeting: String): String => Unit = + (name: String) => println(s"$theGreeting, $name") +``` + +Теперь, при вызове этого метода, процесс становится более гибким, потому что приветствие можно изменить. +Вот как это выглядит, когда создается функция из этого метода: + +```` +scala> val sayHello = greet("Hello") +val sayHello: String => Unit = Lambda..... + ------------------------ +```` + +Выходные данные подписи типа показывают, что `sayHello` — это функция, +которая принимает входной параметр `String` и возвращает `Unit` (ничего). +Так что теперь, при передаче `sayHello` строки, печатается приветствие: + +```scala +sayHello("Joe") // печатает "Hello, Joe" +``` + +Приветствие можно менять для создания новых функций: + +```scala +val sayCiao = greet("Ciao") +val sayHola = greet("Hola") + +sayCiao("Isabella") // печатает "Ciao, Isabella" +sayHola("Carlos") // печатает "Hola, Carlos" +``` + + +## Более реалистичный пример + +Этот метод может быть еще более полезным, когда возвращает одну из многих возможных функций, +например, фабрику пользовательских функций. + +Например, представим, что необходимо написать метод, который возвращает функции, приветствующие людей на разных языках. +Ограничим это функциями, которые приветствуют на английском или французском языках, +в зависимости от параметра, переданного в метод. + +Созданный метод должен: (a) принимать “желаемый язык” в качестве входных данных +и (b) возвращать функцию в качестве результата. +Кроме того, поскольку эта функция печатает заданную строку, известно, что она имеет тип `String => Unit`. +С помощью этой информации сигнатура метода должна выглядеть так: + +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = ??? +``` + +Далее, поскольку возвращаемые функции, берут строку и печатают ее, +можно прикинуть две анонимные функции для английского и французского языков: + +```scala +(name: String) => println(s"Hello, $name") +(name: String) => println(s"Bonjour, $name") +``` + +Для большей читабельности дадим этим анонимным функциям имена и назначим двум переменным: + +```scala +val englishGreeting = (name: String) => println(s"Hello, $name") +val frenchGreeting = (name: String) => println(s"Bonjour, $name") +``` + +Теперь все, что осталось, это (a) вернуть `englishGreeting`, если `desiredLanguage` — английский, +и (b) вернуть `frenchGreeting`, если `desiredLanguage` — французский. +Один из способов сделать это - выражение `match`: + +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = + val englishGreeting = (name: String) => println(s"Hello, $name") + val frenchGreeting = (name: String) => println(s"Bonjour, $name") + desiredLanguage match + case "english" => englishGreeting + case "french" => frenchGreeting +``` + +И это последний метод. +Обратите внимание, что возврат значения функции из метода ничем не отличается от возврата строкового или целочисленного значения. + +Вот как `createGreetingFunction` создает функцию приветствия на французском языке: + +```scala +val greetInFrench = createGreetingFunction("french") +greetInFrench("Jonathan") // печатает "Bonjour, Jonathan" +``` + +И вот как - на английском: + +```scala +val greetInEnglish = createGreetingFunction("english") +greetInEnglish("Joe") // печатает "Hello, Joe" +``` + +Если вам понятен этот код — поздравляю — теперь вы знаете, как писать методы, возвращающие функции. diff --git a/_ru/scala3/book/interacting-with-java.md b/_ru/scala3/book/interacting-with-java.md new file mode 100644 index 0000000000..86f3268d46 --- /dev/null +++ b/_ru/scala3/book/interacting-with-java.md @@ -0,0 +1,560 @@ +--- +layout: multipage-overview +title: Взаимодействие с Java +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице показано, как код Scala может взаимодействовать с Java и как код Java может взаимодействовать с кодом Scala. +language: ru +num: 72 +previous-page: tools-worksheets +next-page: +--- + +## Введение + +В этом разделе рассматривается, как использовать код Java в Scala и, наоборот, как использовать код Scala в Java. + +В целом, использование Java-кода в Scala довольно простое. +Есть лишь несколько моментов, +когда может появиться желание использовать утилиты Scala для преобразования концепций Java в Scala, +в том числе: + +- Классы коллекций Java +- Java класс `Optional` + +Аналогично, если вы пишете код Java и хотите использовать концепции Scala, +вам потребуется преобразовать коллекции Scala и Scala класс `Option`. + +В следующих разделах демонстрируются наиболее распространенные преобразования, которые вам могут понадобиться: + +- Как использовать коллекции Java в Scala +- Как использовать Java `Optional` в Scala +- Расширение Java интерфейсов в Scala +- Как использовать коллекции Scala в Java +- Как использовать Scala `Option` в Java +- Как использовать трейты Scala в Java +- Как обрабатывать методы Scala, которые вызывают исключения в коде Java +- Как использовать vararg-параметры Scala в Java +- Создание альтернативных имен для использования методов Scala в Java + +> Обратите внимание: примеры Java в этом разделе предполагают, что вы используете Java 11 или более позднюю версию. + +## Как использовать коллекции Java в Scala + +Когда вы пишете код на Scala, а API либо требует, либо создает класс коллекции Java (из пакета `java.util`), +тогда допустимо напрямую использовать или создавать коллекцию, как в Java. + +Однако для идиоматического использования в Scala, например, для циклов `for` по коллекции +или для применения функций высшего порядка, таких как `map` и `filter`, +вы можете создать прокси, который будет вести себя как коллекция Scala. + +Вот пример того, как это работает. +Учитывая следующий API, который возвращает `java.util.List[String]`: + +{% tabs foo-definition %} +{% tab Java %} + +```java +public interface Foo { + static java.util.List getStrings() { + return List.of("a", "b", "c"); + } +} +``` + +{% endtab %} +{% endtabs %} + +Вы можете преобразовать этот Java список в Scala `Seq`, +используя утилиты преобразования из Scala объекта `scala.jdk.CollectionConverters`: + +{% tabs foo-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.jdk.CollectionConverters._ +import scala.collection.mutable + +def testList() = { + println("Using a Java List in Scala") + val javaList: java.util.List[String] = Foo.getStrings() + val scalaSeq: mutable.Seq[String] = javaList.asScala + for (s <- scalaSeq) println(s) +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import scala.jdk.CollectionConverters.* +import scala.collection.mutable + +def testList() = + println("Using a Java List in Scala") + val javaList: java.util.List[String] = Foo.getStrings() + val scalaSeq: mutable.Seq[String] = javaList.asScala + for s <- scalaSeq do println(s) +``` + +{% endtab %} +{% endtabs %} + +В приведенном выше коде создается оболочка `javaList.asScala`, +которая адаптирует `java.util.List` к коллекции Scala `mutable.Seq`. + +## Как использовать Java `Optional` в Scala + +Когда вы взаимодействуете с API, который использует класс `java.util.Optional` в коде Scala, +его можно создавать и использовать, как в Java. + +Однако для идиоматического использования в Scala, например использования в `for`, +вы можете преобразовать его в Scala `Option`. + +Чтобы продемонстрировать это, вот Java API, который возвращает значение типа `Optional[String]`: + +{% tabs bar-definition %} +{% tab Java %} + +```java +public interface Bar { + static java.util.Optional optionalString() { + return Optional.of("hello"); + } +} +``` + +{% endtab %} +{% endtabs %} + +Сначала импортируйте всё из объекта `scala.jdk.OptionConverters`, +а затем используйте метод `toScala` для преобразования `Optional` значения в Scala `Option`: + +{% tabs bar-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import java.util.Optional +import scala.jdk.OptionConverters._ + +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import java.util.Optional +import scala.jdk.OptionConverters.* + +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala +``` + +{% endtab %} +{% endtabs %} + +## Расширение Java интерфейсов в Scala + +Если вам нужно использовать Java интерфейсы в коде Scala, расширяйте их так, как если бы они были трейтами Scala. +Например, учитывая эти три Java интерфейса: + +{% tabs animal-definition %} +{% tab Java %} + +```java +public interface Animal { + void speak(); +} + +public interface Wagging { + void wag(); +} + +public interface Running { + // an implemented method + default void run() { + System.out.println("I’m running"); + } +} +``` + +{% endtab %} +{% endtabs %} + +вы можете создать класс `Dog` в Scala так же, как если бы вы использовали трейты. +Поскольку у `run` есть реализация по умолчанию, вам нужно реализовать только методы `speak` и `wag`: + +{% tabs animal-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Dog extends Animal with Wagging with Running { + def speak = println("Woof") + def wag = println("Tail is wagging") +} + +def useJavaInterfaceInScala = { + val d = new Dog() + d.speak + d.wag + d.run +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Dog extends Animal, Wagging, Running: + def speak = println("Woof") + def wag = println("Tail is wagging") + +def useJavaInterfaceInScala = + val d = Dog() + d.speak + d.wag + d.run +``` + +{% endtab %} +{% endtabs %} + +Также обратите внимание, что в Scala методы Java, определенные с пустыми списками параметров, +можно вызывать либо так же, как в Java, `.wag()`, +либо вы можете отказаться от использования круглых скобок `.wag`. + +## Как использовать коллекции Scala в Java + +Если вам нужно использовать класс коллекции Scala в своем Java-коде, +используйте методы Scala объекта `scala.jdk.javaapi.CollectionConverters` в своем Java-коде, +для корректной работы конверсии. + +Например, предположим, что Scala API возвращает `List[String]`, как следующем примере: + +{% tabs baz-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object Baz { + val strings: List[String] = List("a", "b", "c") +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object Baz: + val strings: List[String] = List("a", "b", "c") +``` + +{% endtab %} +{% endtabs %} + +Вы можете получить доступ к Scala `List` в Java-коде следующим образом: + +{% tabs baz-usage %} +{% tab Java %} + +```java +import scala.jdk.javaapi.CollectionConverters; + +// получить доступ к методу `strings` с помощью `Baz.strings()` +scala.collection.immutable.List xs = Baz.strings(); + +java.util.List listOfStrings = CollectionConverters.asJava(xs); + +for (String s: listOfStrings) { + System.out.println(s); +} +``` + +{% endtab %} +{% endtabs %} + +Этот код можно сократить, но показаны полные шаги, чтобы продемонстрировать, как работает процесс. +Обязательно обратите внимание, что хотя `Baz` имеет поле с именем `strings`, +в Java оно отображается как метод, поэтому его следует вызывать в круглых скобках `.strings()`. + +## Как использовать Scala `Option` в Java + +Если вам нужно использовать Scala `Option` в коде Java, +вы можете преобразовать значение `Option` в значение Java `Optional`, +используя метод `toJava` объекта Scala `scala.jdk.javaapi.OptionConverters`. + +Например, предположим, что Scala API возвращает `Option[String]`, как следующем примере: + +{% tabs qux-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object Qux { + val optString: Option[String] = Option("hello") +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object Qux: + val optString: Option[String] = Option("hello") +``` + +{% endtab %} +{% endtabs %} + +Затем вы можете получить доступ к Scala `Option` в своем Java-коде следующим образом: + +{% tabs qux-usage %} +{% tab Java %} + +```java +import java.util.Optional; +import scala.Option; +import scala.jdk.javaapi.OptionConverters; + +Option scalaOptString = Qux.optString(); +Optional javaOptString = OptionConverters.toJava(scalaOptString); +``` + +{% endtab %} +{% endtabs %} + +Этот код можно сократить, но показаны полные шаги, чтобы продемонстрировать, как работает процесс. +Обязательно обратите внимание, что хотя `Qux` имеет поле с именем `optString`, +в Java оно отображается как метод, поэтому его следует вызывать в круглых скобках `.optString()`. + +## Как использовать трейты Scala в Java + +Начиная с Java 8, вы можете использовать трейт Scala точно так же, как Java интерфейс, +даже если этот трейт реализует методы. +Например, учитывая эти два трейта Scala, один с реализованным методом, а другой только с интерфейсом: + +{% tabs scala-trait-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait ScalaAddTrait { + def sum(x: Int, y: Int) = x + y // реализован +} + +trait ScalaMultiplyTrait { + def multiply(x: Int, y: Int): Int // абстрактный +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait ScalaAddTrait: + def sum(x: Int, y: Int) = x + y // реализован + +trait ScalaMultiplyTrait: + def multiply(x: Int, y: Int): Int // абстрактный +``` + +{% endtab %} +{% endtabs %} + +Класс Java может реализовать оба этих интерфейса и определить метод `multiply`: + +{% tabs scala-trait-usage %} +{% tab Java %} + +```java +class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait { + public int multiply(int a, int b) { + return a * b; + } +} + +JavaMath jm = new JavaMath(); +System.out.println(jm.sum(3,4)); // 7 +System.out.println(jm.multiply(3,4)); // 12 +``` + +{% endtab %} +{% endtabs %} + +## Как обрабатывать методы Scala, которые вызывают исключения в коде Java + +Когда вы пишете код на Scala, используя идиомы программирования Scala, +вы никогда не напишете метод, который генерирует исключение. +Но если по какой-то причине у вас есть метод Scala, который генерирует исключение, +и вы хотите, чтобы разработчики Java могли использовать этот метод, +добавьте аннотацию `@throws` к вашему методу Scala, +чтобы Java потребители знали, какие исключения он может генерировать. + +Например, следующий Scala метод `exceptionThrower` аннотирован, чтобы объявить, что он выдает `Exception`: + +{% tabs except-throw-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object SExceptionThrower { + @throws[Exception] + def exceptionThrower = + throw new Exception("Idiomatic Scala methods don’t throw exceptions") +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object SExceptionThrower: + @throws[Exception] + def exceptionThrower = + throw Exception("Idiomatic Scala methods don’t throw exceptions") +``` + +{% endtab %} +{% endtabs %} + +В результате вам придется обрабатывать исключение в своем Java-коде. +Например, этот код не скомпилируется из-за необработанного исключения: + +{% tabs except-throw-usage %} +{% tab Java %} + +```java +// не скомпилируется, потому что исключение не обработано +public class ScalaExceptionsInJava { + public static void main(String[] args) { + SExceptionThrower.exceptionThrower(); + } +} +``` + +{% endtab %} +{% endtabs %} + +Компилятор выдает следующую ошибку: + +```plain +[error] ScalaExceptionsInJava: unreported exception java.lang.Exception; + must be caught or declared to be thrown +[error] SExceptionThrower.exceptionThrower() +``` + +Хорошо — это то, что вы хотите: аннотация сообщает компилятору Java, что `exceptionThrower` может выдать исключение. +Теперь, когда вы пишете код на Java, вы должны обрабатывать исключение с помощью блока `try` +или объявлять, что ваш Java метод генерирует исключение. + +И наоборот, если вы укажите аннотацию Scala метода `exceptionThrower`, код Java _будет скомпилирован_. +Вероятно, это не то, что вам нужно, поскольку Java код может не учитывать метод Scala, выдающий исключение. + +## Как использовать vararg-параметры Scala в Java + +Если метод Scala имеет неопределенное количество параметров и вы хотите использовать этот метод в Java, +отметьте Scala метод аннотацией `@varargs`. +Например, метод `printAll` в этом Scala классе объявляет vararg-поле `String*`: + +{% tabs vararg-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.annotation.varargs + +object VarargsPrinter { + @varargs def printAll(args: String*): Unit = args.foreach(println) +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import scala.annotation.varargs + +object VarargsPrinter: + @varargs def printAll(args: String*): Unit = args.foreach(println) +``` + +{% endtab %} +{% endtabs %} + +Поскольку `printAll` объявлен с аннотацией `@varargs`, его можно вызвать из Java программы +с переменным количеством параметров, как показано в этом примере: + +{% tabs vararg-usage %} +{% tab Java %} + +```java +public class JVarargs { + public static void main(String[] args) { + VarargsPrinter.printAll("Hello", "world"); + } +} +``` + +{% endtab %} +{% endtabs %} + +Запуск кода приводит к следующему выводу: + +```plain +Hello +world +``` + +## Создание альтернативных имен для использования методов Scala в Java + +В Scala вы можете создать имя метода, используя символический знак: + +{% tabs add-definition %} +{% tab 'Scala 2 и 3' %} + +```scala +def +(a: Int, b: Int) = a + b +``` + +{% endtab %} +{% endtabs %} + +Такое имя метода корректно работать в Java не будет, +но в Scala вы можете предоставить "альтернативное" имя метода с аннотацией `targetName`, +которая будет именем метода при использовании из Java: + +{% tabs add-2-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.annotation.targetName + +object Adder { + @targetName("add") def +(a: Int, b: Int) = a + b +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import scala.annotation.targetName + +object Adder: + @targetName("add") def +(a: Int, b: Int) = a + b +``` + +{% endtab %} +{% endtabs %} + +Теперь в вашем Java-коде вы можете использовать псевдоним метода `add`: + +{% tabs add-2-usage %} +{% tab Java %} + +```java +int x = Adder.add(1,1); +System.out.printf("x = %d\n", x); +``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/scala3/book/introduction.md b/_ru/scala3/book/introduction.md new file mode 100644 index 0000000000..ae23a36516 --- /dev/null +++ b/_ru/scala3/book/introduction.md @@ -0,0 +1,38 @@ +--- +layout: multipage-overview +title: Введение +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице начинается обзорная документация по языку Scala 3. +language: ru +num: 1 +previous-page: +next-page: scala-features +--- + +Добро пожаловать в книгу по Scala 3. +Цель этой книги — предоставить неформальное введение в язык Scala. +Она относительно легко затрагивает все темы Scala. +Если в какой-то момент во время чтения этой книги вы захотите получить дополнительную информацию о конкретной функции, +можете воспользоваться ссылкой на нашу [Справочную документацию][reference], +в которой более подробно рассматриваются многие новые функции языка Scala. + +
+  Если вас интересует заархивированное издание книги для Scala 2, +доступ к нему можно получить здесь. +В настоящее время мы находимся в процессе слияния двух книг, и вы можете нам помочь. +
+ +В этой книге мы надеемся продемонстрировать, что Scala — это красивый, выразительный язык программирования с чистым современным синтаксисом, +который поддерживает и функциональное программирование (ФП), и объектно-ориентированное программирование (ООП), +а также обеспечивает безопасную статическую систему типов. +Синтаксис, грамматика и функции Scala были переосмыслены, открыто обсуждались и были обновлены в 2020 году, +чтобы стать яснее и проще для понимания, чем когда-либо прежде. + +Книга начинается с беглого обзора возможностей Scala в [разделе “Почувствуй Scala”][taste]. +После этого обзора в следующих разделах содержится более подробная информация о рассмотренных языковых функциях. + +[reference]: {{ site.scala3ref }}/overview.html +[taste]: {% link _overviews/scala3-book/taste-intro.md %} diff --git a/_ru/scala3/book/methods-intro.md b/_ru/scala3/book/methods-intro.md new file mode 100644 index 0000000000..de34fc1166 --- /dev/null +++ b/_ru/scala3/book/methods-intro.md @@ -0,0 +1,22 @@ +--- +layout: multipage-overview +title: Методы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлены методы в Scala 3. +language: ru +num: 24 +previous-page: domain-modeling-fp +next-page: methods-most +--- + + +В Scala 2 _методы_ могут быть определены внутри классов, трейтов, объектов, `case` классов и `case` объектов. +Но стало еще лучше: в Scala 3 они также могут быть определены вне любой из этих конструкций; +мы говорим, что это определения "верхнего уровня", поскольку они не вложены в другое определение. +Короче говоря, теперь методы можно определять где угодно. + +Многие особенности методов демонстрируются в следующем разделе. +Поскольку `main` методы требуют немного больше пояснений, они описаны в одном из следующих разделов отдельно. diff --git a/_ru/scala3/book/methods-main-methods.md b/_ru/scala3/book/methods-main-methods.md new file mode 100644 index 0000000000..cd6342216a --- /dev/null +++ b/_ru/scala3/book/methods-main-methods.md @@ -0,0 +1,205 @@ +--- +layout: multipage-overview +title: Main методы в Scala 3 +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице описывается, как основные методы и аннотация @main работают в Scala 3. +language: ru +num: 26 +previous-page: methods-most +next-page: methods-summary +--- + +
Написание однострочных программ только в Scala 3
+ +Scala 3 предлагает следующий способ определения программ, которые можно вызывать из командной строки: +добавление аннотации `@main` к методу превращает его в точку входа исполняемой программы: + +{% tabs method_1 %} +{% tab 'Только в Scala 3' for=method_1 %} + +```scala +@main def hello() = println("Hello, World") +``` + +{% endtab %} +{% endtabs %} + +Для запуска программы достаточно сохранить эту строку кода в файле с именем, например, _Hello.scala_ +(имя файла необязательно должно совпадать с именем метода) и запустить с помощью `scala`: + +```bash +$ scala Hello.scala +Hello, World +``` + +Аннотированный метод `@main` может быть написан либо на верхнем уровне (как показано), +либо внутри статически доступного объекта. +В любом случае имя программы - это имя метода без каких-либо префиксов объектов. + +Узнайте больше об аннотации `@main`, прочитав следующие разделы или посмотрев это видео: + +
+ +
+ +### Аргументы командной строки + +Метод `@main` может обрабатывать аргументы командной строки с различными типами. +Например, данный метод `@main`, который принимает параметры `Int`, `String` и дополнительные строковые параметры: + +{% tabs method_2 %} +{% tab 'Только в Scala 3' for=method_2 %} + +```scala +@main def happyBirthday(age: Int, name: String, others: String*) = + val suffix = (age % 100) match + case 11 | 12 | 13 => "th" + case _ => (age % 10) match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + + val sb = StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do sb.append(" and ").append(other) + println(sb.toString) +``` + +{% endtab %} +{% endtabs %} + +После компиляции кода создается основная программа с именем `happyBirthday`, которая вызывается следующим образом: + +``` +$ scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` + +Как показано, метод `@main` может иметь произвольное количество параметров. +Для каждого типа параметра должен существовать [given экземпляр][given] +класса типа `scala.util.CommandLineParser.FromString`, который преобразует аргумент из `String` в требуемый тип параметра. +Также, как показано, список параметров основного метода может заканчиваться повторяющимся параметром типа `String*`, +который принимает все оставшиеся аргументы, указанные в командной строке. + +Программа, реализованная с помощью метода `@main`, проверяет, +что в командной строке достаточно аргументов для заполнения всех параметров, +и что строки аргументов могут быть преобразованы в требуемые типы. +Если проверка завершается неудачей, программа завершается с сообщением об ошибке: + +``` +$ scala happyBirthday 22 +Illegal command line after first argument: more arguments expected + +$ scala happyBirthday sixty Fred +Illegal command line: java.lang.NumberFormatException: For input string: "sixty" +``` + +## Пользовательские типы как параметры + +Как упоминалось выше, компилятор ищет заданный экземпляр класса типов `scala.util.CommandLineParser.FromString` +для типа аргумента. Например, предположим, что у вас есть собственный тип `Color`, +который вы хотите использовать в качестве параметра. +Вы можете сделать это, как показано ниже: + +{% tabs method_3 %} +{% tab 'Только в Scala 3' for=method_3 %} + +```scala +enum Color: + case Red, Green, Blue + +given ComamndLineParser.FromString[Color] with + def fromString(value: String): Color = Color.valueOf(value) + +@main def run(color: Color): Unit = + println(s"The color is ${color.toString}") +``` + +{% endtab %} +{% endtabs %} + +Это работает одинаково для ваших собственных пользовательских типов в вашей программе, +а также для типов, которые можно использовать из другой библиотеки. + +## Детали + +Компилятор Scala генерирует программу из `@main` метода `f` следующим образом: + +- он создает класс с именем `f` в пакете, где был найден метод `@main`. +- класс имеет статический метод `main` с обычной сигнатурой Java `main` метода: + принимает `Array[String]` в качестве аргумента и возвращает `Unit`. +- сгенерированный `main` метод вызывает метод `f` с аргументами, + преобразованными с помощью методов в объекте `scala.util.CommandLineParser.FromString`. + +Например, приведенный выше метод `happyBirthday` генерирует дополнительный код, эквивалентный следующему классу: + +{% tabs method_4 %} +{% tab 'Только в Scala 3' for=method_4 %} + +```scala +final class happyBirthday { + import scala.util.{CommandLineParser as CLP} + def main(args: Array[String]): Unit = + try + happyBirthday( + CLP.parseArgument[Int](args, 0), + CLP.parseArgument[String](args, 1), + CLP.parseRemainingArguments[String](args, 2)*) + catch { + case error: CLP.ParseError => CLP.showError(error) + } +} +``` + +> Примечание: В этом сгенерированном коде модификатор `` выражает, +> что `main` метод генерируется как статический метод класса `happyBirthday`. +> Эта функция недоступна для пользовательских программ в Scala. +> Вместо неё обычные “статические” члены генерируются в Scala с использованием `object`. + +{% endtab %} +{% endtabs %} + +## Обратная совместимость со Scala 2 + +`@main` методы — это рекомендуемый способ создания программ, вызываемых из командной строки в Scala 3. +Они заменяют предыдущий подход, который заключался в создании `object`, расширяющего класс `App`: + +Прежняя функциональность `App`, основанная на "волшебном" `DelayedInit trait`, больше недоступна. +`App` все еще существует в ограниченной форме, но не поддерживает аргументы командной строки и будет объявлен устаревшим в будущем. + +Если программам необходимо выполнять перекрестную сборку между Scala 2 и Scala 3, +вместо этого рекомендуется использовать `object` с явным методом `main` и одним аргументом `Array[String]`: + +{% tabs method_5 %} +{% tab 'Scala 2 и 3' %} + +```scala +object happyBirthday { + private def happyBirthday(age: Int, name: String, others: String*) = { + ... // тоже, что и раньше + } + def main(args: Array[String]): Unit = + happyBirthday(args(0).toInt, args(1), args.drop(2).toIndexedSeq:_*) +} +``` + +> обратите внимание, что здесь мы используем `:_*` для передачи переменного числа аргументов, +> который остается в Scala 3 для обратной совместимости. + +{% endtab %} +{% endtabs %} + +Если вы поместите этот код в файл с именем _happyBirthday.scala_, то сможете скомпилировать его с `scalac` +и запустить с помощью `scala`, как показывалось ранее: + +```bash +$ scalac happyBirthday.scala + +$ scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` + +[given]: {% link _overviews/scala3-book/ca-context-parameters.md %} diff --git a/_ru/scala3/book/methods-most.md b/_ru/scala3/book/methods-most.md new file mode 100644 index 0000000000..b3d8f6ed81 --- /dev/null +++ b/_ru/scala3/book/methods-most.md @@ -0,0 +1,712 @@ +--- +layout: multipage-overview +title: Особенности методов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены методы Scala 3, включая main методы, методы расширения и многое другое. +language: ru +num: 25 +previous-page: methods-intro +next-page: methods-main-methods +--- + +В этом разделе представлены различные аспекты определения и вызова методов в Scala 3. + +## Определение методов + +В Scala методы обладают множеством особенностей, в том числе: + +- Generic (типовые) параметры +- Значения параметров по умолчанию +- Несколько групп параметров +- Контекстные параметры +- Параметры по имени +- и другие… + +Некоторые из этих функций демонстрируются в этом разделе, но когда вы определяете “простой” метод, +который не использует эти функции, синтаксис выглядит следующим образом: + +{% tabs method_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = { + // тело метода + // находится здесь +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // тело метода + // находится здесь +end methodName // опционально +``` + +{% endtab %} +{% endtabs %} + +В этом синтаксисе: + +- ключевое слово `def` используется для определения метода +- для наименования методов согласно стандартам Scala используется camel case convention +- у параметров метода необходимо всегда указывать тип +- возвращаемый тип метода указывать необязательно +- методы могут состоять как только из одной строки, так и из нескольких строк +- метку окончания метода `end methodName` указывать необязательно, её рекомендуется указывать только для длинных методов + +Вот два примера однострочного метода с именем `add`, который принимает два входных параметра `Int`. +Первая версия явно показывает возвращаемый тип метода - `Int`, а вторая - нет: + +{% tabs method_2 %} +{% tab 'Scala 2 и 3' for=method_2 %} + +```scala +def add(a: Int, b: Int): Int = a + b +def add(a: Int, b: Int) = a + b +``` + +{% endtab %} +{% endtabs %} + +У публичных методов рекомендуется всегда указывать тип возвращаемого значения. +Объявление возвращаемого типа может упростить его понимание при просмотре кода другого человека +или своего кода спустя некоторое время. + +## Вызов методов + +Вызов методов прост: + +{% tabs method_3 %} +{% tab 'Scala 2 и 3' for=method_3 %} + +```scala +val x = add(1, 2) // 3 +``` + +{% endtab %} +{% endtabs %} + +Коллекции Scala имеют десятки встроенных методов. +Эти примеры показывают, как их вызывать: + +{% tabs method_4 %} +{% tab 'Scala 2 и 3' for=method_4 %} + +```scala +val x = List(1, 2, 3) + +x.size // 3 +x.contains(1) // true +x.map(_ * 10) // List(10, 20, 30) +``` + +{% endtab %} +{% endtabs %} + +Внимание: + +- `size` не принимает аргументов и возвращает количество элементов в списке +- метод `contains` принимает один аргумент — значение для поиска +- `map` принимает один аргумент - функцию; в данном случае в него передается анонимная функция + +## Многострочные методы + +Если метод длиннее одной строки, начинайте тело метода со второй строки с отступом вправо: + +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = { + // представим, что это тело метода требует несколько строк + val sum = a + b + sum * 2 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_5 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = + // представим, что это тело метода требует несколько строк + val sum = a + b + sum * 2 +``` + +{% endtab %} +{% endtabs %} + +В этом методе: + +- `sum` — неизменяемая локальная переменная; к ней нельзя получить доступ вне метода +- последняя строка удваивает значение `sum` - именно это значение возвращается из метода + +Когда вы вставите этот код в REPL, то увидите, что он работает как требовалось: + +{% tabs method_6 %} +{% tab 'Scala 2 и 3' for=method_6 %} + +```scala +scala> addThenDouble(1, 1) +res0: Int = 4 +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что нет необходимости в операторе `return` в конце метода. +Поскольку почти все в Scala является _выражением_ — то это означает, +что каждая строка кода возвращает (или _вычисляет_) значение — нет необходимости использовать `return`. + +Это видно на примере того же метода, но в более сжатой форме: + +{% tabs method_7 %} +{% tab 'Scala 2 и 3' for=method_7 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = (a + b) * 2 +``` + +{% endtab %} +{% endtabs %} + +В теле метода можно использовать все возможности Scala: + +- `if`/`else` выражения +- `match` выражения +- циклы `while` +- циклы `for` и `for` выражения +- присвоение переменных +- вызовы других методов +- определения других методов + +В качестве ещё одного примера многострочного метода, +`getStackTraceAsString` преобразует свой входной параметр `Throwable` в правильно отформатированную строку: + +{% tabs method_8 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_8 %} + +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter() + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_8 %} + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = StringWriter() + t.printStackTrace(PrintWriter(sw)) + sw.toString +``` + +{% endtab %} +{% endtabs %} + +В этом методе: + +- в первой строке переменная `sw` принимает значение нового экземпляра `StringWriter` +- вторая строка сохраняет содержимое трассировки стека в `StringWriter` +- третья строка возвращает строковое представление трассировки стека + +## Значения параметров по умолчанию + +Параметры метода могут иметь значения по умолчанию. +В этом примере для параметров `timeout` и `protocol` заданы значения по умолчанию: + +{% tabs method_9 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_9 %} + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = { + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // здесь ещё какой-то код ... +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_9 %} + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // здесь ещё какой-то код ... +``` + +{% endtab %} +{% endtabs %} + +Поскольку параметры имеют значения по умолчанию, метод можно вызвать следующими способами: + +{% tabs method_10 %} +{% tab 'Scala 2 и 3' for=method_10 %} + +```scala +makeConnection() // timeout = 5000, protocol = http +makeConnection(2_000) // timeout = 2000, protocol = http +makeConnection(3_000, "https") // timeout = 3000, protocol = https +``` + +{% endtab %} +{% endtabs %} + +Вот несколько ключевых моментов об этих примерах: + +- В первом примере аргументы не предоставляются, поэтому метод использует значения параметров по умолчанию: `5_000` и `http` +- Во втором примере для параметра `timeout` указывается значение `2_000`, + поэтому оно используется вместе со значением по умолчанию для `protocol` +- В третьем примере значения указаны для обоих параметров, поэтому используются они. + +Обратите внимание, что при использовании значений параметров по умолчанию потребителю кажется, +что он может работать с тремя разными переопределенными методами. + +## Именованные параметры + +При желании вы также можете использовать имена параметров метода при его вызове. +Например, `makeConnection` может также вызываться следующими способами: + +{% tabs method_11 %} +{% tab 'Scala 2 и 3' for=method_11 %} + +```scala +makeConnection(timeout=10_000) +makeConnection(protocol="https") +makeConnection(timeout=10_000, protocol="https") +makeConnection(protocol="https", timeout=10_000) +``` + +{% endtab %} +{% endtabs %} + +В некоторых фреймворках именованные параметры используются постоянно. +Они также очень полезны, когда несколько параметров метода имеют один и тот же тип: + +{% tabs method_12 %} +{% tab 'Scala 2 и 3' for=method_12 %} + +```scala +engage(true, true, true, false) +``` + +{% endtab %} +{% endtabs %} + +Без помощи IDE этот код может быть трудночитаемым, но так он становится намного понятнее и очевиднее: + +{% tabs method_13 %} +{% tab 'Scala 2 и 3' for=method_13 %} + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` + +{% endtab %} +{% endtabs %} + +## Рекомендации о методах, которые не принимают параметров + +Когда метод не принимает параметров, говорят, что он имеет _arity_ уровень 0 (_arity-0_). +Аналогично, если метод принимает один параметр - это метод с _arity-1_. + +Когда создаются методы _arity-0_: + +- если метод выполняет побочные эффекты, такие как вызов `println`, метод объявляется с пустыми скобками. +- если метод не выполняет побочных эффектов, например, получение размера коллекции, + что аналогично доступу к полю в коллекции, круглые скобки опускаются. + +Например, этот метод выполняет побочный эффект, поэтому он объявлен с пустыми скобками: + +{% tabs method_14 %} +{% tab 'Scala 2 и 3' for=method_14 %} + +```scala +def speak() = println("hi") +``` + +{% endtab %} +{% endtabs %} + +При вызове метода нужно обязательно указывать круглые скобки, если он был объявлен с ними: + +{% tabs method_15 %} +{% tab 'Scala 2 и 3' for=method_15 %} + +```scala +speak // ошибка: "method speak must be called with () argument" +speak() // печатает "hi" +``` + +{% endtab %} +{% endtabs %} + +Хотя это всего лишь соглашение, его соблюдение значительно улучшает читаемость кода: +с первого взгляда становится понятно, что метод с arity-0 имеет побочные эффекты. + +## Использование `if` в качестве тела метода + +Поскольку выражения `if`/`else` возвращают значение, их можно использовать в качестве тела метода. +Вот метод с именем `isTruthy`, реализующий Perl-определения `true` и `false`: + +{% tabs method_16 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_16 %} + +```scala +def isTruthy(a: Any) = { + if (a == 0 || a == "" || a == false) + false + else + true +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_16 %} + +```scala +def isTruthy(a: Any) = + if a == 0 || a == "" || a == false then + false + else + true +``` + +{% endtab %} +{% endtabs %} + +Примеры показывают, как работает метод: + +{% tabs method_17 %} +{% tab 'Scala 2 и 3' for=method_17 %} + +```scala +isTruthy(0) // false +isTruthy("") // false +isTruthy("hi") // true +isTruthy(1.0) // true +``` + +{% endtab %} +{% endtabs %} + +## Использование `match` в качестве тела метода + +Довольно часто в качестве тела метода используются `match`-выражения. +Вот еще одна версия `isTruthy`, написанная с `match` выражением: + +{% tabs method_18 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_18 %} + +```scala +def isTruthy(a: Any) = a match { + case 0 | "" | false => false + case _ => true +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_18 %} + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` + +> Этот метод работает точно так же, как и предыдущий, в котором использовалось выражение `if`/`else`. +> Вместо `Any` в качестве типа параметра используется `Matchable`, чтобы принять любое значение, +> поддерживающее сопоставление с образцом (pattern matching). + +> См. дополнительную информацию о trait `Matchable` в [Справочной документации][reference_matchable]. + +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +{% endtab %} +{% endtabs %} + +## Контроль видимости методов в классах + +В классах, объектах, trait-ах и enum-ах методы Scala по умолчанию общедоступны, +поэтому созданный здесь экземпляр `Dog` может получить доступ к методу `speak`: + +{% tabs method_19 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_19 %} + +```scala +class Dog { + def speak() = println("Woof") +} + +val d = new Dog +d.speak() // печатает "Woof" +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_19 %} + +```scala +class Dog: + def speak() = println("Woof") + +val d = new Dog +d.speak() // печатает "Woof" +``` + +{% endtab %} +{% endtabs %} + +Также методы можно помечать как `private`. +Это делает их закрытыми в текущем классе, поэтому их нельзя вызвать или переопределить в подклассах: + +{% tabs method_20 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_20 %} +```scala +class Animal { + private def breathe() = println("I’m breathing") +} + +class Cat extends Animal { + // этот метод не скомпилируется + override def breathe() = println("Yo, I’m totally breathing") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_20 %} + +```scala +class Animal: + private def breathe() = println("I’m breathing") + +class Cat extends Animal: + // этот метод не скомпилируется + override def breathe() = println("Yo, I’m totally breathing") +``` + +{% endtab %} +{% endtabs %} + +Если необходимо сделать метод закрытым в текущем классе, но разрешить подклассам вызывать или переопределять его, +метод помечается как `protected`, как показано в примере с методом `speak`: + +{% tabs method_21 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_21 %} + +```scala +class Animal { + private def breathe() = println("I’m breathing") + def walk() = { + breathe() + println("I’m walking") + } + protected def speak() = println("Hello?") +} + +class Cat extends Animal { + override def speak() = println("Meow") +} + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // не скомпилируется, потому что private +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_21 %} + +```scala +class Animal: + private def breathe() = println("I’m breathing") + def walk() = + breathe() + println("I’m walking") + protected def speak() = println("Hello?") + +class Cat extends Animal: + override def speak() = println("Meow") + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // не скомпилируется, потому что private +``` + +{% endtab %} +{% endtabs %} + +Настройка `protected` означает: + +- к методу (или полю) могут обращаться другие экземпляры того же класса +- метод (или поле) не виден в текущем пакете +- он доступен для подклассов + +## Методы в объектах + +Ранее было показано, что trait-ы и классы могут иметь методы. +Ключевое слово `object` используется для создания одноэлементного класса, и объект также может содержать методы. +Это хороший способ сгруппировать набор “служебных” методов. +Например, этот объект содержит набор методов, которые работают со строками: + +{% tabs method_22 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_22 %} + +```scala +object StringUtils { + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=method_22 %} + +```scala +object StringUtils: + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +end StringUtils +``` + +{% endtab %} +{% endtabs %} + +## Методы расширения + +Есть много ситуаций, когда необходимо добавить функциональность к закрытым классам. +Например, представьте, что у вас есть класс `Circle`, но вы не можете изменить его исходный код. +Это может быть определено в сторонней библиотеке так: + +{% tabs method_23 %} +{% tab 'Scala 2 и 3' for=method_23 %} + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +{% endtab %} +{% endtabs %} + +Если вы хотите добавить методы в этот класс, то можете определить их как методы расширения, например: + +{% tabs method_24 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_24 %} + +```scala +implicit class CircleOps(c: Circle) { + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +} +``` +В Scala 2 используйте `implicit class`, подробности [здесь](/overviews/core/implicit-classes.html). + +{% endtab %} +{% tab 'Scala 3' for=method_24 %} + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` +В Scala 3 используйте новую конструкцию `extension`. +Дополнительные сведения см. [в главах этой книги][extension] или [в справочнике по Scala 3][reference-ext]. + +[reference-ext]: {{ site.scala3ref }}/contextual/extension-methods.html +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +{% endtab %} +{% endtabs %} + +Теперь, когда у вас есть экземпляр `Circle` с именем `aCircle`, +то вы можете вызывать эти методы следующим образом: + +{% tabs method_25 %} +{% tab 'Scala 2 и 3' for=method_25 %} + +```scala +aCircle.circumference +aCircle.diameter +aCircle.area +``` + +{% endtab %} +{% endtabs %} + +## Дальнейшее изучение + +Есть много чего, что можно узнать о методах, в том числе: + +- Вызов методов в суперклассах +- Определение и использование параметров по имени +- Написание метода, который принимает параметр функции +- Создание встроенных методов +- Обработка исключений +- Использование входных параметров vararg +- Написание методов с несколькими группами параметров (частично применяемые функции). +- Создание методов с параметрами универсального типа. + +Дополнительные сведения об этих функциях см. в других главах этой книги. + +[reference_extension_methods]: {{ site.scala3ref }}/contextual/extension-methods.html +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_ru/scala3/book/methods-summary.md b/_ru/scala3/book/methods-summary.md new file mode 100644 index 0000000000..029c4de687 --- /dev/null +++ b/_ru/scala3/book/methods-summary.md @@ -0,0 +1,30 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Эта страница подводит итог предыдущим разделам о методах в Scala 3. +language: ru +num: 27 +previous-page: methods-main-methods +next-page: fun-intro +--- + + +Есть ещё много чего, что можно узнать о методах, в том числе: + +- Вызов методов в суперклассах +- Определение и использование параметров по имени +- Создание метода, который принимает функцию в качестве параметра +- Создание встроенных (_inline_) методов +- Обработка исключений +- Использование переменного числа входных параметров +- Создание методов с несколькими группами параметров (частично применяемые функции) +- Создание методов с параметрами универсального типа + +Дополнительные сведения об этих функциях доступны в [справочной документации][reference]. + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_ru/scala3/book/packaging-imports.md b/_ru/scala3/book/packaging-imports.md new file mode 100644 index 0000000000..49b5da759d --- /dev/null +++ b/_ru/scala3/book/packaging-imports.md @@ -0,0 +1,418 @@ +--- +layout: multipage-overview +title: Пакеты и импорт +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: Обсуждение использования пакетов и импорта для организации кода, создания связанных модулей кода, управления областью действия и предотвращения конфликтов пространств имен. +language: ru +num: 36 +previous-page: fun-summary +next-page: collections-intro +--- + + +Scala использует _packages_ для создания пространств имен, которые позволяют модульно разбивать программы. +Scala поддерживает стиль именования пакетов, используемый в Java, а также нотацию пространства имен “фигурные скобки”, +используемую такими языками, как C++ и C#. + +Подход Scala к импорту похож на Java, но более гибкий. +С помощью Scala можно: + +- импортировать пакеты, классы, объекты, trait-ы и методы +- размещать операторы импорта в любом месте +- скрывать и переименовывать участников при импорте + +Эти особенности демонстрируются в следующих примерах. + + +## Создание пакета + +Пакеты создаются путем объявления одного или нескольких имен пакетов в начале файла Scala. +Например, если ваше доменное имя _acme.com_ и вы работаете с пакетом _model_ приложения с именем _myapp_, +объявление пакета выглядит следующим образом: + +```scala +package com.acme.myapp.model + +class Person ... +``` + +По соглашению все имена пакетов должны быть строчными, +а формальным соглашением об именах является _\.\.\.\_. + +Хотя это и не обязательно, имена пакетов обычно совпадают с именами иерархии каталогов. +Поэтому, если следовать этому соглашению, класс `Person` в этом проекте будет найден +в файле _MyApp/src/main/scala/com/acme/myapp/model/Person.scala_. + + +### Использование нескольких пакетов в одном файле + +Показанный выше синтаксис применяется ко всему исходному файлу: +все определения в файле `Person.scala` принадлежат пакету `com.acme.myapp.model` +в соответствии с предложением `package` в начале файла. + +В качестве альтернативы можно написать `package`, которые применяются только к содержащимся в них определениям: + +```scala +package users: + + package administrators: // полное имя пакета - users.administrators + class AdminUser // полное имя класса - users.administrators.AdminUser + + package normalusers: // полное имя пакета - users.normalusers + class NormalUser // полное имя класса - users.normalusers.NormalUser +``` + +Обратите внимание, что за именами пакетов следует двоеточие, а определения внутри пакета имеют отступ. + +Преимущество этого подхода заключается в том, что он допускает вложение пакетов +и обеспечивает более очевидный контроль над областью видимости и инкапсуляцией, особенно в пределах одного файла. + + +## Операторы импорта + +Операторы импорта используются для доступа к сущностям в других пакетах. +Операторы импорта делятся на две основные категории: + +- импорт классов, трейтов, объектов, функций и методов +- импорт `given` предложений + +Первая категория операторов импорта аналогична тому, что использует Java, +с немного другим синтаксисом, обеспечивающим большую гибкость. +Пример: + +```` +import users.* // импортируется все из пакета `users` +import users.User // импортируется только класс `User` +import users.{User, UserPreferences} // импортируются только два члена пакета +import users.{UserPreferences as UPrefs} // переименование импортированного члена +```` + +Эти примеры предназначены для того, чтобы дать представление о том, как работает первая категория операторов `import`. +Более подробно они объясняются в следующих подразделах. + +Операторы импорта также используются для импорта `given` экземпляров в область видимости. +Они обсуждаются в конце этой главы. + +> import не требуется для доступа к членам одного и того же пакета. + + +### Импорт одного или нескольких членов + +В Scala импортировать один элемент из пакета можно следующим образом: + +```scala +import scala.concurrent.Future +``` + +несколько: + +```scala +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.blocking +``` + +При импорте нескольких элементов их можно импортировать более лаконично: + +```scala +import scala.concurrent.{Future, Promise, blocking} +``` + +Если необходимо импортировать все из пакета _scala.concurrent_, используется такой синтаксис: + +```scala +import scala.concurrent.* +``` + + +### Переименование элементов при импорте + +Иногда необходимо переименовать объекты при их импорте, чтобы избежать конфликтов имен. +Например, если нужно использовать Scala класс `List` вместе с `java.util.List`, +то можно переименовать `java.util.List` при импорте: + +```scala +import java.util.{List as JavaList} +``` + +Теперь имя `JavaList` можно использовать для ссылки на класс `java.util.List` +и использовать `List` для ссылки на Scala класс `List`. + +Также можно переименовывать несколько элементов одновременно, используя следующий синтаксис: + +```scala +import java.util.{Date as JDate, HashMap as JHashMap, *} +``` + +В этой строке кода говорится следующее: “Переименуйте классы `Date` и `HashMap`, как показано, +и импортируйте все остальное из пакета `java.util`, не переименовывая”. + + +### Скрытие членов при импорте + +При импорте часть объектов можно _скрывать_. +Следующий оператор импорта скрывает класс `java.util.Random`, +в то время как все остальное в пакете `java.util` импортируется: + +```scala +import java.util.{Random as _, *} +``` + +Если попытаться получить доступ к классу `Random`, то выдается ошибка, +но есть доступ ко всем остальным членам пакета `java.util`: + +```scala +val r = new Random // не скомпилируется +new ArrayList // доступ есть +``` + +#### Скрытие нескольких элементов + +Чтобы скрыть в import несколько элементов, их можно перечислить перед использованием постановочного знака: + +```scala +scala> import java.util.{List as _, Map as _, Set as _, *} +``` + +Перечисленные классы скрыты, но можно использовать все остальное в _java.util_: + +```scala +scala> new ArrayList[String] +val res0: java.util.ArrayList[String] = [] +``` + +Поскольку эти Java классы скрыты, можно использовать классы Scala `List`, `Set` и `Map` без конфликта имен: + +```scala +scala> val a = List(1, 2, 3) +val a: List[Int] = List(1, 2, 3) + +scala> val b = Set(1, 2, 3) +val b: Set[Int] = Set(1, 2, 3) + +scala> val c = Map(1 -> 1, 2 -> 2) +val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2) +``` + + +### Импорт можно использовать в любом месте + +В Scala операторы `import` могут быть объявлены где угодно. +Их можно использовать в верхней части файла исходного кода: + +```scala +package foo + +import scala.util.Random + +class ClassA: + def printRandom(): Unit = + val r = new Random // класс Random здесь доступен + // ещё код... +``` + +Также операторы `import` можно использовать ближе к тому месту, где они необходимы: + +```scala +package foo + +class ClassA: + import scala.util.Random // внутри ClassA + def printRandom(): Unit = + val r = new Random + // ещё код... + +class ClassB: + // класс Random здесь невидим + val r = new Random // этот код не скомпилится +``` + + +### “Статический” импорт + +Если необходимо импортировать элементы способом, аналогичным подходу “статического импорта” в Java, +то есть для того, чтобы напрямую обращаться к членам класса, не добавляя к ним префикс с именем класса, +используется следующий подход. + +Синтаксис для импорта всех статических членов Java класса `Math`: + +```scala +import java.lang.Math.* +``` + +Теперь можно получить доступ к статическим методам класса `Math`, +таким как `sin` и `cos`, без необходимости предварять их именем класса: + +```scala +import java.lang.Math.* + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` + + +### Пакеты, импортированные по умолчанию + +Два пакета неявно импортируются во все файлы исходного кода: + +- `java.lang.*` +- `scala.*` + +Члены object `Predef` также импортируются по умолчанию. + +> Например, такие классы, как `List`, `Vector`, `Map` и т.д. можно использовать явно, не импортируя их - +> они доступны, потому что определены в object `Predef` + + +### Обработка конфликтов имен + +Если необходимо импортировать что-то из корня проекта и возникает конфликт имен, +достаточно просто добавить к имени пакета префикс `_root_`: + +``` +package accounts + +import _root_.accounts.* +``` + + +## Импорт экземпляров `given` + +Как будет показано в главе [“Контекстные абстракции”][contextual], +для импорта экземпляров `given` используется специальная форма оператора `import`. +Базовая форма показана в этом примере: + +```scala +object A: + class TC + given tc: TC + def f(using TC) = ??? + +object B: + import A.* // импорт всех non-given членов + import A.given // импорт экземпляров given +``` + +В этом коде предложение `import A.*` объекта `B` импортирует все элементы `A`, _кроме_ `given` экземпляра `tc`. +И наоборот, второй импорт, `import A.given`, импортирует _только_ `given` экземпляр. +Два предложения импорта также могут быть объединены в одно: + +```scala +object B: + import A.{given, *} +``` + +### Обсуждение + +Селектор с подстановочным знаком `*` помещает в область видимости все определения, кроме `given`, +тогда как селектор выше помещает в область действия все данные, включая те, которые являются результатом расширений. + +Эти правила имеют два основных преимущества: + +- более понятно, откуда берутся данные given. + В частности, невозможно скрыть импортированные given в длинном списке других импортируемых подстановочных знаков. +- есть возможность импортировать все given, не импортируя ничего другого. + Это особенно важно, поскольку given могут быть анонимными, поэтому обычное использование именованного импорта нецелесообразно. + + +### Импорт по типу + +Поскольку given-ы могут быть анонимными, не всегда практично импортировать их по имени, +и вместо этого обычно используется импорт подстановочных знаков. +_Импорт по типу_ предоставляет собой более конкретную альтернативу импорту с подстановочными знаками, +делая понятным то, что импортируется. + +```scala +import A.{given TC} +``` + +Этот код импортирует из `A` любой `given` тип, соответствующий `TC`. +Импорт данных нескольких типов `T1,...,Tn` выражается несколькими `given` селекторами: + +```scala +import A.{given T1, ..., given Tn} +``` + +Импорт всех `given` экземпляров параметризованного типа достигается аргументами с подстановочными знаками. +Например, есть такой `объект`: + +```scala +object Instances: + given intOrd: Ordering[Int] + given listOrd[T: Ordering]: Ordering[List[T]] + given ec: ExecutionContext = ... + given im: Monoid[Int] +``` + +Оператор `import` ниже импортирует экземпляры `intOrd`, `listOrd` и `ec`, но пропускает экземпляр `im`, +поскольку он не соответствует ни одному из указанных шаблонов: + +```scala +import Instances.{given Ordering[?], given ExecutionContext} +``` + +Импорт по типу можно смешивать с импортом по имени. +Если оба присутствуют в предложении import, импорт по типу идет последним. +Например, это предложение импорта импортирует `im`, `intOrd` и `listOrd`, но не включает `ec`: + +```scala +import Instances.{im, given Ordering[?]} +``` + + +### Пример + +В качестве конкретного примера представим, что у нас есть объект `MonthConversions`, +который содержит два определения `given`: + +```scala +object MonthConversions: + trait MonthConverter[A]: + def convert(a: A): String + + given intMonthConverter: MonthConverter[Int] with + def convert(i: Int): String = + i match + case 1 => "January" + case 2 => "February" + // остальные случаи здесь ... + + given stringMonthConverter: MonthConverter[String] with + def convert(s: String): String = + s match + case "jan" => "January" + case "feb" => "February" + // остальные случаи здесь ... +``` + +Чтобы импортировать эти given-ы в текущую область, используем два оператора `import`: + +```scala +import MonthConversions.* +import MonthConversions.{given MonthConverter[?]} +``` + +Теперь создаем метод, использующий эти экземпляры: + +```scala +def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = + monthConverter.convert(a) +``` + +Вызов метода: + +```scala +@main def main = + println(genericMonthConverter(1)) // January + println(genericMonthConverter("jan")) // January +``` + +Как уже упоминалось ранее, одно из ключевых преимуществ синтаксиса “import given” состоит в том, +чтобы прояснить, откуда берутся данные в области действия, +и в `import` операторах выше ясно, что данные поступают из объекта `MonthConversions`. + + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_ru/scala3/book/scala-features.md b/_ru/scala3/book/scala-features.md new file mode 100644 index 0000000000..5a0f3b50e5 --- /dev/null +++ b/_ru/scala3/book/scala-features.md @@ -0,0 +1,485 @@ +--- +layout: multipage-overview +title: Возможности Scala +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице рассматриваются основные возможности языка программирования Scala. +language: ru +num: 2 +previous-page: introduction +next-page: why-scala-3 +--- + + +Название _Scala_ происходит от слова _scalable_, и в соответствии с этим названием язык Scala используется для +поддержки загруженных веб-сайтов и анализа огромных наборов данных. +В этом разделе представлены функции, которые делают Scala масштабируемым языком. +Эти функции разделены на три раздела: + +- Функции высокоуровневого языка программирования +- Функции низкоуровневого языка программирования +- Особенности экосистемы Scala + + + +## Высокоуровневые функции + +Глядя на Scala с пресловутого “вида с высоты 30 000 фунтов”, вы можете сделать о нем следующие утверждения: + +- Это высокоуровневый язык программирования +- Он имеет краткий, читаемый синтаксис +- Он статически типизирован (но кажется динамичным) +- Имеет выразительную систему типов +- Это язык функционального программирования (ФП) +- Это язык объектно-ориентированного программирования (ООП) +- Он поддерживает слияние ФП и ООП +- Контекстные абстракции обеспечивают понятный способ реализации _вывода терминов_ (_term inference_) +- Он работает на JVM (и в браузере) +- Беспрепятственно взаимодействует с Java кодом +- Он используется для серверных приложений (включая микросервисы), приложений для работы с большими данными, а также может использоваться в браузере с помощью Scala.js + +Эти функции кратко рассматриваются в следующих разделах. + + +### Высокоуровневый язык + +Scala считается высокоуровневым языком как минимум по двум причинам. +Во-первых, подобно Java и многим другим современным языкам, вы не имеете дело с низкоуровневыми понятиями, +такими как указатели и управление памятью. + +Во-вторых, с использованием лямбда-выражений и функций высшего порядка вы пишете свой код на очень высоком уровне. +Как говорится в функциональном программировании, в Scala вы пишете то, _что_ хотите, а не то, _как_ этого добиться. +То есть мы не пишем императивный код вот так: + +{% tabs scala-features-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-1 %} +```scala +import scala.collection.mutable.ListBuffer + +def double(ints: List[Int]): List[Int] = { + val buffer = new ListBuffer[Int]() + for (i <- ints) { + buffer += i * 2 + } + buffer.toList +} + +val oldNumbers = List(1, 2, 3) +val newNumbers = double(oldNumbers) +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-1 %} +```scala +import scala.collection.mutable.ListBuffer + +def double(ints: List[Int]): List[Int] = + val buffer = new ListBuffer[Int]() + for i <- ints do + buffer += i * 2 + buffer.toList + +val oldNumbers = List(1, 2, 3) +val newNumbers = double(oldNumbers) +``` +{% endtab %} +{% endtabs %} + +Этот код шаг за шагом указывает компилятору, что делать. +Вместо этого мы пишем высокоуровневый функциональный код, используя функции высшего порядка и лямбда-выражения, +подобные этому, для вычисления того же результата: + +{% tabs scala-features-2 %} +{% tab 'Scala 2 и 3' for=scala-features-2 %} +```scala +val newNumbers = oldNumbers.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + + +Как видите, этот код намного лаконичнее, его легче читать и легче поддерживать. + + +### Лаконичный синтаксис + +Scala имеет краткий, удобочитаемый синтаксис. +Например, переменные создаются лаконично, а их типы понятны: + +{% tabs scala-features-3 %} +{% tab 'Scala 2 и 3' for=scala-features-3 %} +```scala +val nums = List(1,2,3) +val p = Person("Martin", "Odersky") +``` +{% endtab %} +{% endtabs %} + + +Функции высшего порядка и лямбда-выражения делают код кратким и удобочитаемым: + +{% tabs scala-features-4 %} +{% tab 'Scala 2 и 3' for=scala-features-4 %} +```scala +nums.map(i => i * 2) // длинная форма +nums.map(_ * 2) // краткая форма + +nums.filter(i => i > 1) +nums.filter(_ > 1) +``` +{% endtab %} +{% endtabs %} + +Трэйты, классы и методы определяются с помощью простого и легкого синтаксиса: + +{% tabs scala-features-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-5 %} +```scala mdoc +trait Animal { + def speak(): Unit +} + +trait HasTail { + def wagTail(): Unit +} + +class Dog extends Animal with HasTail { + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") +} +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-5 %} +```scala +trait Animal: + def speak(): Unit + +trait HasTail: + def wagTail(): Unit + +class Dog extends Animal, HasTail: + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") +``` +{% endtab %} +{% endtabs %} + + +Исследования показали, что время, которое разработчик тратит на _чтение_ и _написание_ кода, составляет как минимум 10:1, +поэтому важно писать краткий и читабельный код. + + +### Ощущение динамики + +Scala — это язык со статической типизацией, но благодаря своим возможностям вывода типов он кажется динамичным. +Все эти выражения выглядят как языки с динамической типизацией, такие как Python или Ruby, но это все Scala: + +{% tabs scala-features-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-6 %} +```scala +val s = "Hello" +val p = Person("Al", "Pacino") +val sum = nums.reduceLeft(_ + _) +val y = for (i <- nums) yield i * 2 +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-6 %} +```scala +val s = "Hello" +val p = Person("Al", "Pacino") +val sum = nums.reduceLeft(_ + _) +val y = for i <- nums yield i * 2 +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) +``` +{% endtab %} +{% endtabs %} + + +Как утверждает Heather Miller, Scala считается [сильным языком со статической типизацией](https://heather.miller.am/blog/types-in-scala.html), +и вы получаете все преимущества статических типов: + +- Корректность: вы обнаруживаете большинство ошибок во время компиляции +- Отличная поддержка IDE + - Надежное автодополнение кода + - Отлов ошибок во время компиляции означает отлов ошибок по мере написания + - Простой и надежный рефакторинг +- Вы можете уверенно рефакторить свой код +- Объявления типов методов сообщают читателям, что делает метод, и помогают служить документацией +- Масштабируемость и удобство обслуживания: типы помогают обеспечить корректность в произвольно больших приложениях и командах разработчиков +- Строгая типизация в сочетании с превосходным выводом типов позволяет использовать такие механизмы, как [контекстная абстракция]({{ site.scala3ref }}/contextual), которая позволяет вам опускать шаблонный код. Часто этот шаблонный код может быть выведен компилятором на основе определений типов и заданного контекста. + + +### Выразительная система типов + +Система типов в Scala во время компиляции обеспечивает безопасное и согласованное использование абстракций. +В частности, система типов поддерживает: + +- [Выводимые типы]({% link _overviews/scala3-book/types-inferred.md %}) +- [Generic классы]({% link _overviews/scala3-book/types-generics.md %}) +- [Аннотации вариантности]({% link _overviews/scala3-book/types-variance.md %}) +- [Верхняя](/tour/upper-type-bounds.html) и [нижняя](/tour/lower-type-bounds.html) границы типов +- [Полиморфные методы](/tour/polymorphic-methods.html) +- [Типы пересечения]({% link _overviews/scala3-book/types-intersection.md %}) +- [Типы объединения]({% link _overviews/scala3-book/types-union.md %}) +- [Лямбда-типы]({{ site.scala3ref }}/new-types/type-lambdas.html) +- [Экземпляры `given` и предложения `using`]({% link _overviews/scala3-book/ca-context-parameters.md %}) +- [Методы расширения]({% link _overviews/scala3-book/ca-extension-methods.md %}) +- [Типовые классы]({% link _overviews/scala3-book/ca-type-classes.md %}) +- [Многостороннее равенство]({% link _overviews/scala3-book/ca-multiversal-equality.md %}) +- [Псевдонимы непрозрачного типа]({% link _overviews/scala3-book/types-opaque-types.md %}) +- [Открытые классы]({{ site.scala3ref }}/other-new-features/open-classes.html) +- [Типы соответствия]({{ site.scala3ref }}/new-types/match-types.html) +- [Зависимые типы функций]({{ site.scala3ref }}/new-types/dependent-function-types.html) +- [Полиморфные функциональные типы]({{ site.scala3ref }}/new-types/polymorphic-function-types.html) +- [Контекстные границы]({{ site.scala3ref }}/contextual/context-bounds.html) +- [Контекстные функции]({{ site.scala3ref }}/contextual/context-functions.html) +- [Внутренние классы](/tour/inner-classes.html) и [элементы абстрактного типа](/tour/abstract-type-members.html) как элементы объекта + +В сочетании эти функции обеспечивают мощную основу для безопасного повторного использования программных абстракций +и для безопасного расширения программного обеспечения. + + +### Язык функционального программирования + +Scala — это язык функционального программирования (ФП), что означает: + +- Функции — это значения, и их можно передавать, как и любое другое значение +- Напрямую поддерживаются функции высшего порядка +- Встроенные лямбда +- Все в Scala — это выражение, возвращающее значение +- Синтаксически легко использовать неизменяемые переменные, и их использование приветствуется +- В стандартной библиотеке языка содержится множество неизменяемых классов коллекций +- Эти классы коллекций поставляются с десятками функциональных методов: они не изменяют коллекцию, вместо этого возвращая обновленную копию данных + + +### Объектно-ориентированный язык + +Scala — это язык объектно-ориентированного программирования (ООП). +Каждое значение — это экземпляр класса, а каждый “оператор” — это метод. + +В Scala все типы наследуются от класса верхнего уровня `Any`, чьими непосредственными дочерними элементами являются `AnyVal` (_типы значений_, такие как `Int` и `Boolean`) и `AnyRef` (_ссылочные типы_, как в Java). +Это означает, что различие в Java между примитивными и упакованными типами (например, `int` против `Integer`) отсутствует в Scala. +Упаковка и распаковка полностью прозрачны для пользователя. + + +### Поддерживает слияние ФП/ООП + + +Суть Scala заключается в слиянии функционального программирования и объектно-ориентированного программирования в типизированной среде: + +- Функции для логики +- Объекты для модульности + +[Как заявил Мартин Одерски](https://jaxenter.com/current-state-scala-odersky-interview-129495.html), “Scala был разработан, чтобы показать, что слияние функционального и объектно-ориентированного программирования возможно и практично”. + + +### Вывод терминов стал более понятным + +После Haskell Scala был вторым популярным языком, в котором была некоторая форма неявных (_implicits_) выражений. +В Scala 3 эти концепции были полностью переосмыслены и реализованы более четко. + +Основная идея заключается в _выводе терминов_: на основе заданного, компилятор синтезирует “канонический” термин, который имеет этот тип. +В Scala параметр контекста напрямую ведет к выводимому термину аргумента, который также может быть записан явно. + +Примеры использования этой концепции включают реализацию [типовых классов]({% link _overviews/scala3-book/ca-type-classes.md %}), +установление контекста, внедрение зависимостей, выражение возможностей, вычисление новых типов и доказательство отношений между ними. + +Scala 3 делает этот процесс более понятным, чем когда-либо прежде. +О контекстных абстракциях можно прочесть в [Справочной документации]({{ site.scala3ref }}/contextual). + + +### Клиент & сервер + +Код Scala работает на виртуальной машине Java (JVM), поэтому вы получаете все ее преимущества: + +- Безопасность +- Производительность +- Управление памятью +- Портативность и независимость от платформы +- Возможность использовать множество существующих Java и JVM библиотек + +Помимо работы на JVM, Scala также работает в браузере с помощью Scala.js (и сторонних инструментов с открытым исходным кодом для интеграции популярных библиотек JavaScript), а собственные исполняемые файлы могут быть созданы с помощью Scala Native и GraalVM. + + +### Беспрепятственное взаимодействие с Java + +Вы можете использовать Java классы и библиотеки в своих приложениях Scala, а также код Scala в приложениях Java. +Что касается второго пункта, большие библиотеки, такие как [Akka](https://akka.io) и [Play Framework](https://www.playframework.com) написаны на Scala и могут использоваться в приложениях Java. + +Что касается первого пункта, классы и библиотеки Java используются в приложениях Scala каждый день. +Например, в Scala вы можете читать файлы с помощью `BufferedReader` и `FileReader` из Java: + +{% tabs scala-features-7 %} +{% tab 'Scala 2 и 3' for=scala-features-7 %} +```scala +import java.io.* +val br = BufferedReader(FileReader(filename)) +// чтение файла в `br` ... +``` +{% endtab %} +{% endtabs %} + +Использование Java-кода в Scala, как правило, не вызывает затруднений. + +В Scala также можно использовать коллекции Java, и если вы хотите использовать с ними богатый набор методов классов коллекций Scala, +то можете преобразовать их с помощью всего нескольких строк кода: + +{% tabs scala-features-8 %} +{% tab 'Scala 2 и 3' for=scala-features-8 %} +```scala +import scala.jdk.CollectionConverters.* +val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq +``` +{% endtab %} +{% endtabs %} + + +### Богатство библиотек + +Как будет видно в третьем разделе этой страницы, библиотеки и фреймворки Scala, подобные нижеследующим, +были написаны для поддержки загруженных веб-сайтов и работы с огромными наборами данных: + +1. [Play Framework](https://www.playframework.com) — это легкая, без сохранения состояния, удобная для web, удобная для разработчиков архитектура для создания масштабируемых приложений +2. [Apache Spark](https://spark.apache.org) — это унифицированный аналитический механизм для обработки больших данных со встроенными модулями для потоковой передачи, SQL, машинного обучения и обработки графиков + +В [списке Awesome Scala](https://github.com/lauris/awesome-scala) представлены десятки дополнительных инструментов +с открытым исходным кодом, созданных разработчиками для создания приложений Scala. + +В дополнение к программированию на стороне сервера, [Scala.js](https://www.scala-js.org) представляет собой +строго типизированную замену для написания JavaScript со сторонними библиотеками с открытым исходным кодом, +которые включают инструменты для интеграции с библиотекой Facebook React, jQuery и т.д. + + +## Функции низкоуровневого языка + +Хотя в предыдущем разделе были рассмотрены высокоуровневые функции Scala, интересно отметить, +что на высоком уровне вы можете делать одни и те же утверждения как о Scala 2, так и о Scala 3. +Десять лет назад Scala начиналась с прочного фундамента желаемых функций, и вы увидите в этом разделе, +что в Scala 3 эти преимущества были улучшены. + +С точки зрения деталей “на уровне моря” — то есть функций языка, которые программисты используют каждый день — +Scala 3 имеет значительные преимущества по сравнению со Scala 2: + +- Возможность более лаконично создавать алгебраические типы данных (ADT) с перечислениями +- Еще более лаконичный и читаемый синтаксис: + - Синтаксис “тихой” структуры управления легче читать + - Опциональные фигурные скобки + - Меньшее количество символов в коде создает меньше визуального шума, что упрощает его чтение + - Ключевое слово `new` обычно больше не требуется при создании экземпляров класса + - Формальность объектов пакета была заменена более простыми определениями “верхнего уровня” +- Более понятная грамматика: + - Несколько различных вариантов использования ключевого слова `implicit` были удалены; это использование заменено более очевидными ключевыми словами, такими как `given`, `using`, и `extension`, фокусирующихся на намерении, а не механизме (подробности см. в разделе [Givens][givens]) + - [Методы расширения][extension] заменяют неявные классы более понятным и простым механизмом + - Добавление модификатора `open` для классов заставляет разработчика намеренно объявить, что класс открыт для модификации, тем самым ограничивая специальные расширения кодовой базы + - [Многостороннее равенство][multiversal] исключает бессмысленные сравнения с `==` и `!=` (т.е. попытки сравнить `Person` с `Planet`) + - Гораздо проще реализуются макросы + - Объединение и пересечение предлагают гибкий способ моделирования типов + - Параметры трейтов заменяют и упрощают ранние инициализаторы + - [Псевдонимы непрозрачных типов][opaque_types] заменяют большинство случаев использования классов значений, гарантируя при этом отсутствие упаковки + - Export предложения обеспечивают простой и общий способ выражения агрегации, который может заменить предыдущий шаблон фасада объектов пакета, наследуемых от классов + - Синтаксис procedure был удален, а синтаксис varargs - изменен, чтобы сделать язык более согласованным + - `@infix` аннотация делает очевидным желаемое применение метода + - Аннотация метода [`@targetName`]({{ site.scala3ref }}/other-new-features/targetName.html) определяет альтернативное имя метода, улучшая совместимость с Java и позволяя указывать псевдонимы для символических операторов + +Демонстрация всех этих функций заняла бы слишком много места, но перейдите по ссылкам в пунктах выше, чтобы увидеть эти функции в действии. +Все эти функции подробно обсуждаются на страницах *New*, *Changed* и *Dropped* функций в [обзорной документации][reference]. + + +## Экосистема Scala + + +У Scala динамичная экосистема с библиотеками и фреймворками под любые требования. +[Список “Awesome Scala”](https://github.com/lauris/awesome-scala) содержит список сотен проектов с открытым исходным кодом, +доступных разработчикам Scala, а [Scaladex](https://index.scala-lang.org) предоставляет доступный для поиска индекс библиотек Scala. +Некоторые из наиболее известных библиотек перечислены ниже. + + + +### Web разработка + +- [Play Framework](https://www.playframework.com) следует модели Ruby on Rails, чтобы стать легкой, не сохраняющей состояния, + удобной для разработчиков и web архитектурой для высокомасштабируемых приложений +- [Scalatra](https://scalatra.org) — небольшой высокопроизводительный асинхронный web framework, вдохновленный Sinatra +- [Finatra](https://twitter.github.io/finatra) — это сервисы Scala, построенные на TwitterServer и Finagle +- [Scala.js](https://www.scala-js.org) — это строго типизированная замена JavaScript, обеспечивающая более безопасный способ создания надежных интерфейсных web-приложений +- [ScalaJs-React](https://github.com/japgolly/scalajs-react) поднимает библиотеку Facebook React на Scala.js и пытается сделать ее максимально безопасной для типов и удобной для Scala + + +HTTP(S) библиотеки: + +- [Akka-http](https://akka.io) +- [Finch](https://github.com/finagle/finch) +- [Http4s](https://github.com/http4s/http4s) +- [Sttp](https://github.com/softwaremill/sttp) + +JSON библиотеки: + +- [Argonaut](https://github.com/argonaut-io/argonaut) +- [Circe](https://github.com/circe/circe) +- [Json4s](https://github.com/json4s/json4s) +- [Play-JSON](https://github.com/playframework/play-json) + +Сериализация: + +- [ScalaPB](https://github.com/scalapb/ScalaPB) + +### Наука и анализ данных: + +- [Algebird](https://github.com/twitter/algebird) +- [Spire](https://github.com/typelevel/spire) +- [Squants](https://github.com/typelevel/squants) + + +### Большие данные + +- [Apache Spark](https://github.com/apache/spark) +- [Apache Flink](https://github.com/apache/flink) + + +### ИИ, машинное обучение + +- [BigDL](https://github.com/intel-analytics/BigDL) (Распределенная среда глубокого обучения для Apache Spark) +- [TensorFlow Scala](https://github.com/eaplatanios/tensorflow_scala) + + +### Функциональное программирование & Функциональное реактивное программирование + +ФП: + +- [Cats](https://github.com/typelevel/cats) +- [Zio](https://github.com/zio/zio) + +Функциональное реактивное программирование (ФРП): + +- [fs2](https://github.com/typelevel/fs2) +- [monix](https://github.com/monix/monix) + + +### Инструменты сборки + +- [sbt](https://www.scala-sbt.org) +- [Gradle](https://gradle.org) +- [Mill](https://github.com/lihaoyi/mill) + + + +## Подведем итоги + +Как показано на этой странице, Scala обладает множеством замечательных функций высокоуровневого языка программирования, +низкоуровневого языка программирования и богатой экосистемой разработчиков. + + +[reference]: {{ site.scala3ref }}/overview.html +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %} +[opaque_types]: {% link _overviews/scala3-book/types-opaque-types.md %} + diff --git a/_ru/scala3/book/scala-tools.md b/_ru/scala3/book/scala-tools.md new file mode 100644 index 0000000000..e58bfb2e64 --- /dev/null +++ b/_ru/scala3/book/scala-tools.md @@ -0,0 +1,18 @@ +--- +layout: multipage-overview +title: Scala утилиты +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе рассматриваются два широко используемых инструмента Scala sbt и ScalaTest. +language: ru +num: 69 +previous-page: concurrency +next-page: tools-sbt +--- + +В этой главе представлены два способа написания и запуска программ в Scala: + +- создавая Scala проекты, возможно, содержащие несколько файлов, и определяя точку входа в программу, +- путем взаимодействия с worksheet, который представляет собой программу, определенную в одном файле и выполняемую построчно. diff --git a/_ru/scala3/book/string-interpolation.md b/_ru/scala3/book/string-interpolation.md new file mode 100644 index 0000000000..9e37b526fa --- /dev/null +++ b/_ru/scala3/book/string-interpolation.md @@ -0,0 +1,413 @@ +--- +layout: multipage-overview +title: Интерполяция строк +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице представлена дополнительная информация о создании строк и использовании интерполяции строк. +language: ru +num: 18 +previous-page: first-look-at-types +next-page: control-structures +--- + +## Введение + +Интерполяция строк позволяет использовать внутри строк переменные. +Например: + +{% tabs example-1 %} +{% tab 'Scala 2 и 3' for=example-1 %} + +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` + +{% endtab %} +{% endtabs %} + +Использование интерполяции строк заключается в том, что перед строковыми кавычками ставится символ `s`, +а перед любыми именами переменных ставится символ `$`. + +### Другие интерполяторы + +То `s`, что вы помещаете перед строкой, является лишь одним из возможных интерполяторов, предоставляемых Scala. + +Scala по умолчанию предоставляет три метода интерполяции строк: `s`, `f` и `raw`. +Кроме того, строковый интерполятор — это всего лишь специальный метод, и вы можете определить свой собственный. +Например, некоторые библиотеки баз данных определяют интерполятор `sql`, возвращающий запрос к базе данных. + +## Интерполятор `s` (`s`-строки) + +Добавление `s` перед любым строковым литералом позволяет использовать переменные непосредственно в строке. +Вы уже здесь видели пример: + +{% tabs example-2 %} +{% tab 'Scala 2 и 3' for=example-2 %} + +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` + +{% endtab %} +{% endtabs %} + +Здесь переменные `$name` и `$age` заменяются в строке результатами вызова `name.toString` и `age.toString` соответственно. +`s`-строка будет иметь доступ ко всем переменным, в настоящее время находящимся в области видимости. + +Хотя это может показаться очевидным, важно здесь отметить, +что интерполяция строк _не_ будет выполняться в обычных строковых литералах: + +{% tabs example-3 %} +{% tab 'Scala 2 и 3' for=example-3 %} + +```scala +val name = "James" +val age = 30 +println("$name is $age years old") // "$name is $age years old" +``` + +{% endtab %} +{% endtabs %} + +Строковые интерполяторы также могут принимать произвольные выражения. +Например: + +{% tabs example-4 %} +{% tab 'Scala 2 и 3' for=example-4 %} + +```scala +println(s"2 + 2 = ${2 + 2}") // "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // "x.abs = 1" +``` + +{% endtab %} +{% endtabs %} + +Любое произвольное выражение может быть встроено в `${}`. + +Некоторые специальные символы необходимо экранировать при встраивании в строку. +Чтобы указать символ "знак доллара", вы можете удвоить его `$$`, как показано ниже: + +{% tabs example-5 %} +{% tab 'Scala 2 и 3' for=example-5 %} + +```scala +println(s"New offers starting at $$14.99") // "New offers starting at $14.99" +``` + +{% endtab %} +{% endtabs %} + +Двойные кавычки также необходимо экранировать. +Это можно сделать с помощью тройных кавычек, как показано ниже: + +{% tabs example-6 %} +{% tab 'Scala 2 и 3' for=example-6 %} + +```scala +println(s"""{"name":"James"}""") // `{"name":"James"}` +``` + +{% endtab %} +{% endtabs %} + +Наконец, все многострочные строковые литералы также могут быть интерполированы. + +{% tabs example-7 %} +{% tab 'Scala 2 и 3' for=example-7 %} + +```scala +println(s"""name: "$name", + |age: $age""".stripMargin) +``` + +Строка будет напечатана следующим образом: + +``` +name: "James" +age: 30 +``` + +{% endtab %} +{% endtabs %} + +## Интерполятор `f` (`f`-строки) + +Добавление `f` к любому строковому литералу позволяет создавать простые отформатированные строки, +аналогичные `printf` в других языках. +При использовании интерполятора `f` за всеми ссылками на переменные должна следовать строка формата в стиле `printf`, например `%d`. +Давайте посмотрим на пример: + +{% tabs example-8 %} +{% tab 'Scala 2 и 3' for=example-8 %} + +```scala +val height = 1.9d +val name = "James" +println(f"$name%s is $height%2.2f meters tall") // "James is 1.90 meters tall" +``` + +{% endtab %} +{% endtabs %} + +Интерполятор `f` типобезопасен. +Если вы попытаетесь передать в строку формата, который работает только для целых чисел, +значение `double`, компилятор выдаст ошибку. Например: + +{% tabs f-interpolator-error class=tabs-scala-version %} + +{% tab 'Scala 2' for=f-interpolator-error %} + +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +:9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ +``` + +{% endtab %} + +{% tab 'Scala 3' for=f-interpolator-error %} + +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +-- Error: ---------------------------------------------------------------------- +1 |f"$height%4d" + | ^^^^^^ + | Found: (height : Double), Required: Int, Long, Byte, Short, BigInt +1 error found + +``` + +{% endtab %} +{% endtabs %} + +Интерполятор `f` использует утилиты форматирования строк, доступные в Java. +Форматы, разрешенные после символа `%`, описаны в [Formatter javadoc][java-format-docs]. +Если после определения переменной нет символа `%`, предполагается форматирование `%s` (`String`). + +Наконец, как и в Java, используйте `%%` для получения буквенного символа `%` в итоговой строке: + +{% tabs literal-percent %} +{% tab 'Scala 2 и 3' for=literal-percent %} + +```scala +println(f"3/19 is less than 20%%") // "3/19 is less than 20%" +``` + +{% endtab %} +{% endtabs %} + +### Интерполятор `raw` + +Интерполятор `raw` похож на интерполятор `s`, +за исключением того, что он не выполняет экранирование литералов внутри строки. +Вот пример обработанной строки: + +{% tabs example-9 %} +{% tab 'Scala 2 и 3' for=example-9 %} + +```scala +scala> s"a\nb" +res0: String = +a +b +``` + +{% endtab %} +{% endtabs %} + +Здесь строковый интерполятор `s` заменил символы `\n` символом переноса строки. +Интерполятор `raw` этого не делает. + +{% tabs example-10 %} +{% tab 'Scala 2 и 3' for=example-10 %} + +```scala +scala> raw"a\nb" +res1: String = a\nb +``` + +{% endtab %} +{% endtabs %} + +Интерполятор `raw` полезен тогда, когда вы хотите избежать преобразования таких выражений, как `\n`, в символ переноса строки. + +В дополнение к трем строковым интерполяторам пользователи могут определить свои собственные. + +## Расширенное использование + +Литерал `s"Hi $name"` анализируется Scala как _обрабатываемый_ строковый литерал. +Это означает, что компилятор выполняет некоторую дополнительную работу с этим литералом. +Особенности обработанных строк и интерполяции строк описаны в [SIP-11][sip-11]. +Вот краткий пример, который поможет проиллюстрировать, как они работают. + +### Пользовательские интерполяторы + +В Scala все обрабатываемые строковые литералы представляют собой простые преобразования кода. +Каждый раз, когда компилятор встречает обрабатываемый строковый литерал вида: + +{% tabs example-11 %} +{% tab 'Scala 2 и 3' for=example-11 %} + +```scala +id"string content" +``` + +{% endtab %} +{% endtabs %} + +он преобразует его в вызов метода (`id`) для экземпляра [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). +Этот метод также может быть доступен в неявной области видимости. +Чтобы определить собственную интерполяцию строк, нужно создать неявный класс (Scala 2) +или метод расширения (Scala 3), который добавляет новый метод для `StringContext`. + +В качестве простого примера предположим, что у нас есть простой класс `Point` +и мы хотим создать собственный интерполятор, который преобразует `p"a,b"` в объект `Point`. + +{% tabs custom-interpolator-1 %} +{% tab 'Scala 2 и 3' for=custom-interpolator-1 %} + +```scala +case class Point(x: Double, y: Double) + +val pt = p"1,-2" // Point(1.0,-2.0) +``` + +{% endtab %} +{% endtabs %} + +Мы бы создали собственный интерполятор `p`, +сначала внедрив расширение `StringContext`, например, так: + +{% tabs custom-interpolator-2 class=tabs-scala-version %} + +{% tab 'Scala 2' for=custom-interpolator-2 %} + +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Any*): Point = ??? +} +``` + +**Примечание**. Важно расширить `AnyVal` в Scala 2.x, +чтобы предотвратить создание экземпляра класса во время выполнения при каждой интерполяции. +Дополнительную информацию см. в документации по [value class]({% link _overviews/core/value-classes.md %}). + +{% endtab %} + +{% tab 'Scala 3' for=custom-interpolator-2 %} + +```scala +extension (sc: StringContext) + def p(args: Any*): Point = ??? +``` + +{% endtab %} + +{% endtabs %} + +Как только это расширение окажется в области видимости и компилятор Scala обнаружит `p"some string"`, +то превратит `some string` в токены String, а каждую встроенную переменную в аргументы выражения. + +Например, `p"1, $someVar"` превратится в: + +{% tabs extension-desugaring class=tabs-scala-version %} + +{% tab 'Scala 2' for=extension-desugaring %} + +```scala +new StringContext("1, ", "").p(someVar) +``` + +Затем неявный класс используется для перезаписи следующим образом: + +```scala +new PointHelper(new StringContext("1, ", "")).p(someVar) +``` + +{% endtab %} + +{% tab 'Scala 3' for=extension-desugaring %} + +```scala +StringContext("1, ", "").p(someVar) +``` + +{% endtab %} + +{% endtabs %} + +В результате каждый из фрагментов обработанной строки отображается в элементе `StringContext.parts`, +а любые значения выражений в строке передаются в параметр метода `args`. + +### Пример реализации + +Простая реализация метода интерполяции для нашего `Point` может выглядеть примерно так, как показано ниже, +хотя более детализированный метод может иметь более точный контроль +над обработкой строки `parts` и выражения `args` вместо повторного использования интерполятора `s`. + +{% tabs naive-implementation class=tabs-scala-version %} + +{% tab 'Scala 2' for=naive-implementation %} + +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Double*): Point = { + // переиспользование интерполятора `s` и затем разбиение по ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } +} + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` + +{% endtab %} + +{% tab 'Scala 3' for=naive-implementation %} + +```scala +extension (sc: StringContext) + def p(args: Double*): Point = { + // переиспользование интерполятора `s` и затем разбиение по ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` + +{% endtab %} +{% endtabs %} + +Хотя строковые интерполяторы изначально использовались для создания нескольких строковых форм, +использование пользовательских интерполяторов, как указано выше, +может обеспечить более мощное синтаксическое сокращение, +и сообщество уже использует этот синтаксис для таких вещей, +как расширение цвета терминала ANSI, выполнение SQL-запросов, +магические представления `$"identifier"` и многие другие. + +[java-format-docs]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#detail + +[value-class]: {% link _overviews/core/value-classes.md %} +[sip-11]: {% link _sips/sips/string-interpolation.md %} diff --git a/_ru/scala3/book/taste-collections.md b/_ru/scala3/book/taste-collections.md new file mode 100644 index 0000000000..672e5158af --- /dev/null +++ b/_ru/scala3/book/taste-collections.md @@ -0,0 +1,161 @@ +--- +layout: multipage-overview +title: Коллекции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен обзор основных коллекций в Scala 3. +language: ru +num: 13 +previous-page: taste-objects +next-page: taste-contextual-abstractions +--- + + +Библиотека Scala поставляется с богатым набором классов коллекций, и эти классы содержат множество методов. +Классы коллекций доступны как в неизменяемой, так и в изменяемой форме. + +## Создание списков + +Чтобы дать вам представление о том, как они работают, вот несколько примеров, в которых используется класс `List`, +являющийся неизменяемым классом связанного списка. +В этих примерах показаны различные способы создания заполненного `List`: + +{% tabs collection_1 %} +{% tab 'Scala 2 и 3' for=collection_1 %} + +```scala +val a = List(1, 2, 3) // a: List[Int] = List(1, 2, 3) + +// методы Range +val b = (1 to 5).toList // b: List[Int] = List(1, 2, 3, 4, 5) +val c = (1 to 10 by 2).toList // c: List[Int] = List(1, 3, 5, 7, 9) +val e = (1 until 5).toList // e: List[Int] = List(1, 2, 3, 4) +val f = List.range(1, 5) // f: List[Int] = List(1, 2, 3, 4) +val g = List.range(1, 10, 3) // g: List[Int] = List(1, 4, 7) +``` + +{% endtab %} +{% endtabs %} + +## Методы `List` + +В следующих примерах показаны некоторые методы, которые можно вызывать для заполненного списка. +Обратите внимание, что все эти методы являются функциональными, +а это означает, что они не изменяют коллекцию, на которой вызываются, +а вместо этого возвращают новую коллекцию с обновленными элементами. +Результат, возвращаемый каждым выражением, отображается в комментарии к каждой строке: + +{% tabs collection_2 %} +{% tab 'Scala 2 и 3' for=collection_2 %} + +```scala +// a sample list +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.drop(2) // List(30, 40, 10) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeWhile(_ < 30) // List(10, 20) + +// flatten +val a = List(List(1,2), List(3,4)) +a.flatten // List(1, 2, 3, 4) + +// map, flatMap +val nums = List("one", "two") +nums.map(_.toUpperCase) // List("ONE", "TWO") +nums.flatMap(_.toUpperCase) // List('O', 'N', 'E', 'T', 'W', 'O') +``` + +{% endtab %} +{% endtabs %} + +Эти примеры показывают, как методы “foldLeft” и “reduceLeft” используются +для суммирования значений в последовательности целых чисел: + +{% tabs collection_3 %} +{% tab 'Scala 2 и 3' for=collection_3 %} + +```scala +val firstTen = (1 to 10).toList // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +firstTen.reduceLeft(_ + _) // 55 +firstTen.foldLeft(100)(_ + _) // 155 (100 является “начальным” значением) +``` + +{% endtab %} +{% endtabs %} + +Для классов коллекций Scala доступно гораздо больше методов, +и они продемонстрированы в главе ["Коллекции"][collections] и в [API документации][api]. + +## Кортежи + +В Scala _кортеж_ (_tuple_) — это тип, позволяющий легко поместить набор различных типов в один и тот же контейнер. +Например, используя данный case класс `Person`: + +{% tabs collection_4 %} +{% tab 'Scala 2 и 3' for=collection_4 %} + +```scala +case class Person(name: String) +``` + +{% endtab %} +{% endtabs %} + +Вот как вы создаете кортеж, который содержит `Int`, `String` и пользовательское значение `Person`: + +{% tabs collection_5 %} +{% tab 'Scala 2 и 3' for=collection_5 %} + +```scala +val t = (11, "eleven", Person("Eleven")) +``` + +{% endtab %} +{% endtabs %} + +Когда у вас есть кортеж, вы можете получить доступ к его значениям, привязав их к переменным, +или получить к ним доступ по номеру: + +{% tabs collection_6 %} +{% tab 'Scala 2 и 3' for=collection_6 %} + +```scala +t(0) // 11 +t(1) // "eleven" +t(2) // Person("Eleven") +``` + +{% endtab %} +{% endtabs %} + +Вы также можете использовать этот метод _извлечения_, чтобы присвоить поля кортежа именам переменных: + +{% tabs collection_7 %} +{% tab 'Scala 2 и 3' for=collection_7 %} + +```scala +val (num, str, person) = t + +// в результате: +// val num: Int = 11 +// val str: String = eleven +// val person: Person = Person(Eleven) +``` + +{% endtab %} +{% endtabs %} + +Кортежи хороши в тех случаях, когда вы хотите поместить коллекцию разнородных типов в небольшую структуру, похожую на коллекцию. +Дополнительные сведения о кортежах см. ["в справочной документации"][reference]. + +[collections]: {% link _overviews/scala3-book/collections-intro.md %} +[api]: https://scala-lang.org/api/3.x/ +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_ru/scala3/book/taste-contextual-abstractions.md b/_ru/scala3/book/taste-contextual-abstractions.md new file mode 100644 index 0000000000..fea81bbb84 --- /dev/null +++ b/_ru/scala3/book/taste-contextual-abstractions.md @@ -0,0 +1,79 @@ +--- +layout: multipage-overview +title: Контекстные абстракции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в контекстные абстракции в Scala 3. +language: ru +num: 14 +previous-page: taste-collections +next-page: taste-toplevel-definitions +--- + + +При определенных обстоятельствах вы можете опустить некоторые параметры вызовов методов, которые считаются повторяющимися. + +Эти параметры называются _параметрами контекста_ (_Context Parameters_), +поскольку они выводятся компилятором из контекста, окружающего вызов метода. + +Например, рассмотрим программу, которая сортирует список адресов по двум критериям: +название города, а затем название улицы. + +{% tabs contextual_1 %} +{% tab 'Scala 2 и 3' for=contextual_1 %} + +```scala +val addresses: List[Address] = ... + +addresses.sortBy(address => (address.city, address.street)) +``` + +{% endtab %} +{% endtabs %} + +Метод `sortBy` принимает функцию, которая возвращает для каждого адреса значение, чтобы сравнить его с другими адресами. +В этом случае мы передаем функцию, которая возвращает пару, содержащую название города и название улицы. + +Обратите внимание, что мы только указываем, _что_ сравнивать, но не _как_ выполнять сравнение. +Откуда алгоритм сортировки знает, как сравнивать пары `String`? + +На самом деле метод `sortBy` принимает второй параметр — параметр контекста, который выводится компилятором. +Его нет в приведенном выше примере, поскольку он предоставляется компилятором. + +Этот второй параметр реализует _способ_ сравнения. +Его удобно опустить, потому что мы знаем, что `String`-и обычно сравниваются в лексикографическом порядке. + +Однако также возможно передать параметр явно: + +{% tabs contextual_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=contextual_2 %} + +```scala +addresses.sortBy(address => (address.city, address.street))(Ordering.Tuple2(Ordering.String, Ordering.String)) +``` + +{% endtab %} +{% tab 'Scala 3' for=contextual_2 %} + +```scala +addresses.sortBy(address => (address.city, address.street))(using Ordering.Tuple2(Ordering.String, Ordering.String)) +``` + +в Scala 3 `using` в списке аргументов сигнализирует `sortBy` о явной передаче параметра контекста, избегая двусмысленности. + +{% endtab %} +{% endtabs %} + +В этом случае экземпляр `Ordering.Tuple2(Ordering.String, Ordering.String)` — это именно тот экземпляр, +который в противном случае выводится компилятором. +Другими словами, оба примера создают одну и ту же программу. + +_Контекстные абстракции_ используются, чтобы избежать повторения кода. +Они помогают разработчикам писать фрагменты кода, которые являются расширяемыми и в то же время лаконичными. + +Дополнительные сведения см. в [главе "Контекстные абстракции"][contextual] этой книги, а также в [справочной документации][reference]. + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_ru/scala3/book/taste-control-structures.md b/_ru/scala3/book/taste-control-structures.md new file mode 100644 index 0000000000..7da0940a4f --- /dev/null +++ b/_ru/scala3/book/taste-control-structures.md @@ -0,0 +1,555 @@ +--- +layout: multipage-overview +title: Структуры управления +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Этот раздел демонстрирует структуры управления в Scala 3. +language: ru +num: 8 +previous-page: taste-vars-data-types +next-page: taste-modeling +--- + + +В Scala есть все структуры управления, которые вы найдете в других языках программирования, +а также мощные `for` и `match` выражения: + +- `if`/`else` +- `for` циклы и выражения +- `match` выражения +- `while` циклы +- `try`/`catch` + +Эти структуры демонстрируются в следующих примерах. + +## `if`/`else` + +В Scala структура управления `if`/`else` похожа на аналогичные структуры в других языках. + +{% tabs if-else class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else %} + +```scala +if (x < 0) { + println("negative") +} else if (x == 0) { + println("zero") +} else { + println("positive") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else %} + +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что это действительно _выражение_, а не _утверждение_. +Это означает, что оно возвращает значение, поэтому вы можете присвоить результат переменной: + +{% tabs if-else-expression class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else-expression %} + +```scala +val x = if (a < b) { a } else { b } +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else-expression %} + +```scala +val x = if a < b then a else b +``` + +{% endtab %} +{% endtabs %} + +Как вы увидите в этой книге, _все_ управляющие структуры Scala могут использоваться как выражения. + +> Выражение возвращает результат, а утверждение — нет. +> Утверждения обычно используются для их побочных эффектов, таких как использование `println` для печати на консоли. + +## `for` циклы и выражения + +Ключевое слово `for` используется для создания цикла `for`. +В этом примере показано, как напечатать каждый элемент в `List`: + +{% tabs for-loop class=tabs-scala-version %} +{% tab 'Scala 2' for=for-loop %} + +```scala +val ints = List(1, 2, 3, 4, 5) + +for (i <- ints) println(i) +``` + +> Код `i <- ints` называется _генератором_, а код, следующий за закрывающими скобками генератора, является _телом_ цикла. + +{% endtab %} + +{% tab 'Scala 3' for=for-loop %} + +```scala +val ints = List(1, 2, 3, 4, 5) + +for i <- ints do println(i) +``` + +> Код `i <- ints` называется _генератором_, а код, следующий за ключевым словом `do`, является _телом_ цикла. + +{% endtab %} +{% endtabs %} + +### Guards + +Вы также можете использовать одно или несколько `if` выражений внутри цикла `for`. +Их называют _ограничители_ (_guards_). +В этом примере выводятся все числа `ints`, большие `2`: + +{% tabs for-guards class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards %} + +```scala +for (i <- ints if i > 2) + println(i) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards %} + +```scala +for + i <- ints + if i > 2 +do + println(i) +``` + +{% endtab %} +{% endtabs %} + +Вы можете использовать несколько генераторов и стражников. +Этот цикл перебирает числа от `1` до `3`, и для каждого числа также перебирает символы от `a` до `c`. +Однако у него также есть два стражника, поэтому оператор печати вызывается только тогда, +когда `i` имеет значение `2` и `j` является символом `b`: + +{% tabs for-guards-multi class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards-multi %} + +```scala +for { + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +} { + println(s"i = $i, j = $j") // печатает: "i = 2, j = b" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards-multi %} + +```scala +for + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +do + println(s"i = $i, j = $j") // печатает: "i = 2, j = b" +``` + +{% endtab %} +{% endtabs %} + +### Выражения `for` + +Ключевое слово `for` содержит в себе еще большую силу: +когда вы используете ключевое слово `yield` вместо `do`, то создаете _выражения_ `for`, +которые используются для вычислений и получения результатов. + +Несколько примеров демонстрируют это. +Используя тот же список `ints`, что и в предыдущем примере, этот код создает новый список, +в котором значение каждого элемента в новом списке в два раза превышает значение элементов в исходном: + +{% tabs for-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expression_1 %} + +```` +scala> val doubles = for (i <- ints) yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +{% endtab %} + +{% tab 'Scala 3' for=for-expression_1 %} + +```` +scala> val doubles = for i <- ints yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +{% endtab %} +{% endtabs %} + +Синтаксис структуры управления Scala является гибким, +и это `for` выражение может быть записано несколькими другими способами, в зависимости от ваших предпочтений: + +{% tabs for-expressioni_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_2 %} + +```scala +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_2 %} + +```scala +val doubles = for i <- ints yield i * 2 // стиль показан выше +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +{% endtab %} +{% endtabs %} + +В этом примере показано, как сделать первый символ в каждой строке списка заглавными: + +{% tabs for-expressioni_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_3 %} + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for (name <- names) yield name.capitalize +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_3 %} + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for name <- names yield name.capitalize +``` + +{% endtab %} +{% endtabs %} + +Наконец, нижеследующее выражение `for` перебирает список строк +и возвращает длину каждой строки, но только если эта длина больше `4`: + +{% tabs for-expressioni_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_4 %} + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = + for (f <- fruits if f.length > 4) yield f.length + +// fruitLengths: List[Int] = List(5, 6, 6) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_4 %} + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = for + f <- fruits + if f.length > 4 +yield + // здесь можно использовать + // несколько строк кода + f.length + +// fruitLengths: List[Int] = List(5, 6, 6) +``` + +{% endtab %} +{% endtabs %} + +`for` циклы и выражения более подробно рассматриваются в разделах этой книги ["Структуры управления"][control] +и в [справочной документации]({{ site.scala3ref }}/other-new-features/control-syntax.html). + +## `match` выражения + +В Scala есть выражение `match`, которое в своем самом простом использовании похоже на `switch` оператор Java: + +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' for=match %} + +```scala +val i = 1 + +// позже в этом коде ... +i match { + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match %} + +```scala +val i = 1 + +// позже в этом коде ... +i match + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +``` + +{% endtab %} +{% endtabs %} + +Однако `match` на самом деле это выражение, означающее, +что оно возвращает результат на основе совпадения с шаблоном, который вы можете привязать к переменной: + +{% tabs match-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_1 %} + +```scala +val result = i match { + case 1 => "one" + case 2 => "two" + case _ => "other" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_1 %} + +```scala +val result = i match + case 1 => "one" + case 2 => "two" + case _ => "other" +``` + +{% endtab %} +{% endtabs %} + +`match` не ограничивается работой только с целочисленными значениями, его можно использовать с любым типом данных: + +{% tabs match-expression_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_2 %} + +```scala +val p = Person("Fred") + +// позже в этом коде ... +p match { + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_2 %} + +```scala +val p = Person("Fred") + +// позже в этом коде ... +p match + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +``` + +{% endtab %} +{% endtabs %} + +На самом деле `match` выражение можно использовать для проверки переменной на множестве различных типов шаблонов. +В этом примере показано (а) как использовать `match` выражение в качестве тела метода и (б) как сопоставить все показанные различные типы: + +{% tabs match-expression_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_3 %} + +```scala +// getClassAsString - метод, принимающий один параметр любого типа. +def getClassAsString(x: Any): String = x match { + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[_] => "List" + case _ => "Unknown" +} + +// примеры +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +Поскольку метод `getClassAsString` принимает значение параметра типа `Any`, его можно разложить по любому шаблону. + +{% endtab %} +{% tab 'Scala 3' for=match-expression_3 %} + +```scala +// getClassAsString - метод, принимающий один параметр любого типа. +def getClassAsString(x: Matchable): String = x match + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[?] => "List" + case _ => "Unknown" + +// примеры +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +Метод `getClassAsString` принимает в качестве параметра значение типа [Matchable]({{ site.scala3ref }}/other-new-features/matchable.html), +которое может быть любым типом, поддерживающим сопоставление с образцом +(некоторые типы не поддерживают сопоставление с образцом, поскольку это может нарушить инкапсуляцию). + +{% endtab %} +{% endtabs %} + +Сопоставление с образцом в Scala гораздо _шире_. +Шаблоны могут быть вложены друг в друга, результаты шаблонов могут быть связаны, +а сопоставление шаблонов может даже определяться пользователем. +Дополнительные сведения см. в примерах сопоставления с образцом в главе ["Структуры управления"][control]. + +## `try`/`catch`/`finally` + +Структура управления Scala `try`/`catch`/`finally` позволяет перехватывать исключения. +Она похожа на аналогичную структуру в Java, но её синтаксис соответствует `match` выражениям: + +{% tabs try class=tabs-scala-version %} +{% tab 'Scala 2' for=try %} + +```scala +try { + writeTextToFile(text) +} catch { + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +} finally { + println("Clean up your resources here.") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=try %} + +```scala +try + writeTextToFile(text) +catch + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +finally + println("Clean up your resources here.") +``` + +{% endtab %} +{% endtabs %} + +## Циклы `while` + +В Scala также есть конструкция цикла `while`. +Его однострочный синтаксис выглядит так: + +{% tabs while_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_1 %} + +```scala +while (x >= 0) { x = f(x) } +``` + +{% endtab %} + +{% tab 'Scala 3' for=while_1 %} + +```scala +while x >= 0 do x = f(x) +``` +Scala 3 по-прежнему поддерживает синтаксис Scala 2 для обратной совместимости. + +{% endtab %} +{% endtabs %} + +Синтаксис `while` многострочного цикла выглядит следующим образом: + +{% tabs while_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_2 %} + +```scala +var x = 1 + +while (x < 3) { + println(x) + x += 1 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=while_2 %} + +```scala +var x = 1 + +while + x < 3 +do + println(x) + x += 1 +``` + +{% endtab %} +{% endtabs %} + +## Пользовательские структуры управления + +Благодаря таким функциям, как параметры по имени, инфиксная нотация, плавные интерфейсы, необязательные круглые скобки, +методы расширения и функции высшего порядка, вы также можете создавать свой собственный код, +который работает так же, как управляющая структура. +Вы узнаете об этом больше в разделе ["Структуры управления"][control]. + +[control]: {% link _overviews/scala3-book/control-structures.md %} diff --git a/_ru/scala3/book/taste-functions.md b/_ru/scala3/book/taste-functions.md new file mode 100644 index 0000000000..f83da448e7 --- /dev/null +++ b/_ru/scala3/book/taste-functions.md @@ -0,0 +1,90 @@ +--- +layout: multipage-overview +title: Функции первого класса +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлено введение в функции в Scala 3. +language: ru +num: 11 +previous-page: taste-methods +next-page: taste-objects +--- + +Scala обладает большинством возможностей, которые вы ожидаете от функционального языка программирования, в том числе: + +- Lambdas (анонимные функции) +- Функции высшего порядка (HOF) +- Неизменяемые коллекции в стандартной библиотеке + +Лямбда-выражения, также известные как _анонимные функции_, играют важную роль в том, чтобы ваш код был кратким, но удобочитаемым. + +Метод `map` класса `List` является типичным примером функции высшего порядка — +функции, которая принимает функцию в качестве параметра. + +Эти два примера эквивалентны и показывают, как умножить каждое число в списке на `2`, передав лямбда в метод `map`: + + +{% tabs function_1 %} +{% tab 'Scala 2 и 3' for=function_1 %} +```scala +val a = List(1, 2, 3).map(i => i * 2) // List(2,4,6) +val b = List(1, 2, 3).map(_ * 2) // List(2,4,6) +``` +{% endtab %} +{% endtabs %} + +Примеры выше также эквивалентны следующему коду, в котором вместо лямбда используется метод `double`: + + +{% tabs function_2 %} +{% tab 'Scala 2 и 3' for=function_2 %} +```scala +def double(i: Int): Int = i * 2 + +val a = List(1, 2, 3).map(i => double(i)) // List(2,4,6) +val b = List(1, 2, 3).map(double) // List(2,4,6) +``` +{% endtab %} +{% endtabs %} + +> Если вы еще раньше не видели метод `map`, он применяет заданную функцию к каждому элементу в списке, +> создавая новый список, содержащий результирующие значения. + +Передача лямбда-выражений функциям высшего порядка в классах коллекций (таких, как `List`) — +это часть работы со Scala, которую вы будете делать каждый день. + + +## Неизменяемые коллекции + +Когда вы работаете с неизменяемыми коллекциями, такими как `List`, `Vector`, +а также с неизменяемыми классами `Map` и `Set`, важно знать, +что эти функции не изменяют коллекцию, для которой они вызываются; +вместо этого они возвращают новую коллекцию с обновленными данными. +В результате также принято объединять их вместе в “свободном” стиле для решения проблем. + +Например, в этом примере показано, как отфильтровать коллекцию дважды, +а затем умножить каждый элемент в оставшейся коллекции: + + +{% tabs function_3 %} +{% tab 'Scala 2 и 3' for=function_3 %} +```scala +// пример списка +val nums = (1 to 10).toList // List(1,2,3,4,5,6,7,8,9,10) + +// методы могут быть сцеплены вместе +val x = nums.filter(_ > 3) + .filter(_ < 7) + .map(_ * 10) + +// result: x == List(40, 50, 60) +``` +{% endtab %} +{% endtabs %} + +В дополнение к функциям высшего порядка, используемым в стандартной библиотеке, +вы также можете [создавать свои собственные функции][higher-order]. + +[higher-order]: {% link _overviews/scala3-book/fun-hofs.md %} diff --git a/_ru/scala3/book/taste-hello-world.md b/_ru/scala3/book/taste-hello-world.md new file mode 100644 index 0000000000..77442e43ca --- /dev/null +++ b/_ru/scala3/book/taste-hello-world.md @@ -0,0 +1,205 @@ +--- +layout: multipage-overview +title: Пример 'Hello, World!' +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом примере демонстрируется пример 'Hello, World!' на Scala 3. +language: ru +num: 5 +previous-page: taste-intro +next-page: taste-repl +--- + +> **Подсказка**: в следующих примерах попробуйте выбрать предпочтительную для вас версию Scala. +> + +## Ваша первая Scala-программа + + +Пример “Hello, World!” на Scala выглядит следующим образом. +Сначала поместите этот код в файл с именем _hello.scala_: + + +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-demo %} +```scala +object hello { + def main(args: Array[String]) = { + println("Hello, World!") + } +} +``` +> В этом коде мы определили метод с именем `main` внутри Scala `object`-а с именем `hello`. +> `object` в Scala похож на `class`, но определяет экземпляр singleton, который можно передать. +> `main` принимает входной параметр с именем `args`, который должен иметь тип `Array[String]` +> (`args` пока можно игнорировать). + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} +```scala +@main def hello() = println("Hello, World!") +``` +> В этом коде `hello` - это метод. +> Он определяется с помощью `def` и объявляется в качестве основного метода с помощью аннотации `@main`. +> Он выводит строку "Hello, World!" на стандартный вывод (STDOUT) с помощью метода `println`. + +{% endtab %} + +{% endtabs %} + + +Затем скомпилируйте код с помощью `scalac`: + +```bash +$ scalac hello.scala +``` + +Если вы переходите на Scala с Java: `scalac` похоже на `javac`, эта команда создает несколько файлов: + + +{% tabs hello-world-outputs class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-outputs %} +```bash +$ ls -1 +hello$.class +hello.class +hello.scala +``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-outputs %} +```bash +$ ls -1 +hello$package$.class +hello$package.class +hello$package.tasty +hello.scala +hello.class +hello.tasty +``` +{% endtab %} + +{% endtabs %} + + +Как и Java, файлы _.class_ представляют собой файлы байт-кода, и они готовы к запуску в JVM. + +Теперь вы можете запустить метод `hello` командой `scala`: + +```bash +$ scala hello +Hello, World! +``` + +Если запуск прошел успешно, поздравляем, вы только что скомпилировали и запустили свое первое приложение Scala. + +> Дополнительную информацию о sbt и других инструментах, упрощающих разработку на Scala, можно найти в главе [Инструменты Scala][scala_tools]. + +## Запрос пользовательского ввода + +В нашем следующем примере давайте спросим имя пользователя, прежде чем приветствовать его! + +Есть несколько способов прочитать ввод из командной строки, но самый простой способ — +использовать метод `readLine` из объекта _scala.io.StdIn_. +Чтобы использовать этот метод, вам нужно сначала его импортировать, например: + +{% tabs import-readline %} +{% tab 'Scala 2 и 3' for=import-readline %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +Чтобы продемонстрировать, как это работает, давайте создадим небольшой пример. +Поместите этот исходный код в файл с именем _helloInteractive.scala_: + + +{% tabs hello-world-interactive class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +object helloInteractive { + + def main(args: Array[String]) = { + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") + } + +} +``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +@main def helloInteractive() = + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") +``` +{% endtab %} + +{% endtabs %} + + +В этом коде мы сохраняем результат из `readLine` в переменную с именем `name`, +затем используем оператор над строками `+` для соединения `"Hello, "` с `name` и `"!"`, создавая одно единственное строковое значение. + +> Вы можете узнать больше об использовании val, прочитав главу [Переменные и типы данных](/scala3/book/taste-vars-data-types.html). + +Затем скомпилируйте код с помощью `scalac`: + +```bash +$ scalac helloInteractive.scala +``` + +Затем запустите его с помощью `scala helloInteractive`. На этот раз программа сделает паузу после запроса вашего имени +и подождет, пока вы не наберете имя и не нажмете клавишу возврата на клавиатуре. +Выглядит это так: + +```bash +$ scala helloInteractive +Please enter your name: +▌ +``` + +Когда вы вводите свое имя в "приглашении", окончательное взаимодействие должно выглядеть так: + +```bash +$ scala helloInteractive +Please enter your name: +Alvin Alexander +Hello, Alvin Alexander! +``` + +### Примечание об импорте + +Как вы ранее видели, иногда определенные методы или другие типы определений, которые мы увидим позже, недоступны, +если вы не используете подобное предложение `import`: + +{% tabs import-readline-2 %} +{% tab 'Scala 2 и 3' for=import-readline-2 %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +Импорт помогает писать и распределять код несколькими способами: + - вы можете поместить код в несколько файлов, чтобы избежать беспорядка и облегчить навигацию в больших проектах. + - вы можете использовать библиотеку кода, возможно, написанную кем-то другим, которая имеет полезную функциональность. + - вы видите, откуда берется определенное определение (особенно если оно не было записано в текущем файле). + +[scala_tools]: {% link _overviews/scala3-book/scala-tools.md %} diff --git a/_ru/scala3/book/taste-intro.md b/_ru/scala3/book/taste-intro.md new file mode 100644 index 0000000000..58e15d8e22 --- /dev/null +++ b/_ru/scala3/book/taste-intro.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: Почувствуй Scala +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлен общий обзор основных возможностей языка программирования Scala 3. +language: ru +num: 4 +previous-page: why-scala-3 +next-page: taste-hello-world +--- + + +В этой главе представлен краткий обзор основных возможностей языка программирования Scala 3. +После начального ознакомления остальная часть книги содержит более подробную информацию об описанных функциях, +а [справочная документация][reference] содержит массу подробностей. + +## Настройка Скала + +На протяжении этой главы и остальной части книги мы рекомендуем вам пробовать примеры, скопировав их или набрав вручную. +Инструменты, необходимые для работы с примерами на вашем компьютере, можно установить, +следуя нашему [руководству для началы работы со Scala][get-started]. + +> В качестве альтернативы вы можете запустить примеры в веб-браузере с помощью [Scastie](https://scastie.scala-lang.org), +> полного онлайн-редактора и исполнителя кода для Scala. + + +[reference]: {{ site.scala3ref }}/overview.html +[get-started]: {% link _overviews/getting-started/install-scala.md %} diff --git a/_ru/scala3/book/taste-methods.md b/_ru/scala3/book/taste-methods.md new file mode 100644 index 0000000000..c881761826 --- /dev/null +++ b/_ru/scala3/book/taste-methods.md @@ -0,0 +1,167 @@ +--- +layout: multipage-overview +title: Методы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в определение и использование методов в Scala 3. +language: ru +num: 10 +previous-page: taste-modeling +next-page: taste-functions +--- + + +## Методы в Scala + +Классы Scala, case-классы, трейты, перечисления и объекты могут содержать методы. +Синтаксис простого метода выглядит так: + +{% tabs method_1 %} +{% tab 'Scala 2 и 3' for=method_1 %} +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // тело метода + // находится здесь +``` +{% endtab %} +{% endtabs %} + +Вот несколько примеров: + +{% tabs method_2 %} +{% tab 'Scala 2 и 3' for=method_2 %} +```scala +def sum(a: Int, b: Int): Int = a + b +def concatenate(s1: String, s2: String): String = s1 + s2 +``` +{% endtab %} +{% endtabs %} + +Вам не нужно объявлять возвращаемый тип метода, поэтому можно написать эти методы следующим образом, если хотите: + +{% tabs method_3 %} +{% tab 'Scala 2 и 3' for=method_3 %} +```scala +def sum(a: Int, b: Int) = a + b +def concatenate(s1: String, s2: String) = s1 + s2 +``` +{% endtab %} +{% endtabs %} + +Вот как эти методы вызываются: + +{% tabs method_4 %} +{% tab 'Scala 2 и 3' for=method_4 %} +```scala +val x = sum(1, 2) +val y = concatenate("foo", "bar") +``` +{% endtab %} +{% endtabs %} + +Вот пример многострочного метода: + +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` +{% endtab %} + +{% tab 'Scala 3' for=method_5 %} +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +``` +{% endtab %} +{% endtabs %} + +Параметры метода также могут иметь значения по умолчанию. +В этом примере параметр `timeout` имеет значение по умолчанию `5000`: + +{% tabs method_6 %} +{% tab 'Scala 2 и 3' for=method_6 %} +```scala +def makeConnection(url: String, timeout: Int = 5000): Unit = + println(s"url=$url, timeout=$timeout") +``` +{% endtab %} +{% endtabs %} + +Поскольку в объявлении метода указано значение по умолчанию для `timeout`, метод можно вызывать двумя способами: + +{% tabs method_7 %} +{% tab 'Scala 2 и 3' for=method_7 %} +```scala +makeConnection("https://localhost") // url=http://localhost, timeout=5000 +makeConnection("https://localhost", 2500) // url=http://localhost, timeout=2500 +``` +{% endtab %} +{% endtabs %} + +Scala также поддерживает использование _именованных параметров_ при вызове метода, +поэтому вы можете вызвать этот метод, если хотите, вот так: + +{% tabs method_8 %} +{% tab 'Scala 2 и 3' for=method_8 %} +```scala +makeConnection( + url = "https://localhost", + timeout = 2500 +) +``` +{% endtab %} +{% endtabs %} + +Именованные параметры особенно полезны, когда несколько параметров метода имеют один и тот же тип. +Глядя на этот метод можно задаться вопросом, +какие параметры установлены в `true` или `false`: + +{% tabs method_9 %} +{% tab 'Scala 2 и 3' for=method_9 %} + +```scala +engage(true, true, true, false) +``` + +{% endtab %} +{% endtabs %} + +Ключевое слово `extension` объявляет о намерении определить один или несколько методов расширения для параметра, +заключенного в круглые скобки. +Как показано в этом примере, параметр `s` типа `String` можно затем использовать в теле методов расширения. + +В следующем примере показано, как добавить метод `makeInt` в класс `String`. +Здесь `makeInt` принимает параметр с именем `radix`. +Код не учитывает возможные ошибки преобразования строки в целое число, +но, опуская эту деталь, примеры показывают, как работают методы расширения: + +{% tabs extension %} +{% tab 'Только в Scala 3' %} + +```scala +extension (s: String) + def makeInt(radix: Int): Int = Integer.parseInt(s, radix) + +"1".makeInt(2) // Int = 1 +"10".makeInt(2) // Int = 2 +"100".makeInt(2) // Int = 4 +``` + +{% endtab %} +{% endtabs %} + +## Смотрите также + +Методы Scala могут быть гораздо более мощными: они могут принимать параметры типа и параметры контекста. +Методы подробно описаны в разделе ["Моделирование предметной области"][data-1]. + +[data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_ru/scala3/book/taste-modeling.md b/_ru/scala3/book/taste-modeling.md new file mode 100644 index 0000000000..deede29e6a --- /dev/null +++ b/_ru/scala3/book/taste-modeling.md @@ -0,0 +1,421 @@ +--- +layout: multipage-overview +title: Моделирование данных +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в моделирование данных в Scala 3. +language: ru +num: 9 +previous-page: taste-control-structures +next-page: taste-methods +--- + + +Scala поддерживает как функциональное программирование (ФП), так и объектно-ориентированное программирование (ООП), +а также слияние этих двух парадигм. В этом разделе представлен краткий обзор моделирования данных в ООП и ФП. + +## Моделирование данных в ООП + +При написании кода в стиле ООП двумя вашими основными инструментами для инкапсуляции данных будут _трейты_ и _классы_. + +### Трейты + +Трейты Scala можно использовать как простые интерфейсы, +но они также могут содержать абстрактные и конкретные методы и поля, а также параметры, как и классы. +Они предоставляют вам отличный способ организовать поведение в небольшие модульные блоки. +Позже, когда вы захотите создать конкретные реализации атрибутов и поведения, +классы и объекты могут расширять трейты, смешивая столько трейтов, +сколько необходимо для достижения желаемого поведения. + +В качестве примера того, как использовать трейты в качестве интерфейсов, +вот три трейта, которые определяют хорошо организованное и модульное поведение для животных, таких как собаки и кошки: + +{% tabs traits class=tabs-scala-version %} +{% tab 'Scala 2' for=traits %} + +```scala +trait Speaker { + def speak(): String // тело метода отсутствует, поэтому метод абстрактный +} + +trait TailWagger { + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") +} + +trait Runner { + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits %} + +```scala +trait Speaker: + def speak(): String // тело метода отсутствует, поэтому метод абстрактный + +trait TailWagger: + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") + +trait Runner: + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +``` + +{% endtab %} +{% endtabs %} + +Учитывая эти трейты, вот класс `Dog`, который их все расширяет, +обеспечивая при этом поведение для абстрактного метода `speak`: + +{% tabs traits-class class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-class %} + +```scala +class Dog(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Woof!" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-class %} + +```scala +class Dog(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Woof!" +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, как класс расширяет трейты с помощью ключевого слова `extends`. + +Точно так же вот класс `Cat`, реализующий те же трейты, +а также переопределяющий два конкретных метода, которые он наследует: + +{% tabs traits-override class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-override %} + +```scala +class Cat(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-override %} + +```scala +class Cat(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +``` + +{% endtab %} +{% endtabs %} + +Примеры ниже показывают, как используются эти классы: + +{% tabs traits-use class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-use %} + +```scala +val d = new Dog("Rover") +println(d.speak()) // печатает "Woof!" + +val c = new Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-use %} + +```scala +val d = Dog("Rover") +println(d.speak()) // печатает "Woof!" + +val c = Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} +{% endtabs %} + +Если этот код имеет смысл — отлично, вам удобно использовать трейты в качестве интерфейсов. +Если нет, не волнуйтесь, они более подробно описаны в главе ["Моделирование предметной области"][data-1]. + + +### Классы + +Классы Scala используются в программировании в стиле ООП. +Вот пример класса, который моделирует "человека". +В ООП поля обычно изменяемы, поэтому оба, `firstName` и `lastName` объявлены как `var` параметры: + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_1 %} + +```scala +class Person(var firstName: String, var lastName: String) { + def printFullName() = println(s"$firstName $lastName") +} + +val p = new Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_1 %} + +```scala +class Person(var firstName: String, var lastName: String): + def printFullName() = println(s"$firstName $lastName") + +val p = Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что объявление класса создает конструктор: + +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_2 %} + +```scala +// код использует конструктор из объявления класса +val p = new Person("John", "Stephens") +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_2 %} + +```scala +// код использует конструктор из объявления класса +val p = Person("John", "Stephens") +``` + +{% endtab %} +{% endtabs %} + +Конструкторы и другие темы, связанные с классами, рассматриваются в главе ["Моделирование предметной области"][data-1]. + +## Моделирование данных в ФП + +При написании кода в стиле ФП вы будете использовать следующие понятия: + +- Алгебраические типы данных для определения данных. +- Трейты для функциональности данных. + +### Перечисления и суммированные типы + +Суммированные типы (_sum types_) — это один из способов моделирования алгебраических типов данных (ADT) в Scala. + +Они используются, когда данные могут быть представлены с различными вариантами. + +Например, у пиццы есть три основных атрибута: + +- Размер корки +- Тип корки +- Начинки +- +Они кратко смоделированы с помощью перечислений, +которые представляют собой суммированные типы, содержащие только одноэлементные значения: + +{% tabs enum_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_1 %} + +В Scala 2 `sealed` классы и `case object` объединяются для определения перечисления: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_1 %} + +Scala 3 предлагает конструкцию `enum` для определения перечислений: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +Когда у вас есть перечисление, вы можете импортировать его элементы как обычные значения: + +{% tabs enum_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_2 %} + +```scala +import CrustSize._ +val currentCrustSize = Small + +// перечисления в сопоставлении с шаблоном +currentCrustSize match { + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") +} + +// перечисления в операторе `if` +if (currentCrustSize == Small) println("Small crust size") +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_2 %} + +```scala +import CrustSize.* +val currentCrustSize = Small + +// перечисления в сопоставлении с шаблоном +currentCrustSize match + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") + +// перечисления в операторе `if` +if currentCrustSize == Small then println("Small crust size") +``` + +{% endtab %} +{% endtabs %} + +Вот еще один пример того, как создать суммированные типы с помощью Scala, +это не будет называться перечислением, потому что у случая `Succ` есть параметры: + +{% tabs enum_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_3 %} + +```scala +sealed abstract class Nat +object Nat { + case object Zero extends Nat + case class Succ(pred: Nat) extends Nat +} +``` + +Суммированные типы подробно рассматриваются в разделе ["Моделирование предметной области"]({% link _overviews/scala3-book/domain-modeling-tools.md %}) этой книги. + +{% endtab %} +{% tab 'Scala 3' for=enum_3 %} + +```scala +enum Nat: + case Zero + case Succ(pred: Nat) +``` + +Перечисления подробно рассматриваются в разделе ["Моделирование предметной области"]({% link _overviews/scala3-book/domain-modeling-tools.md %}) этой книги +и в [справочной документации]({{ site.scala3ref }}/enums/enums.html). + +{% endtab %} +{% endtabs %} + +### Продуктовые типы + +Тип продукта — это алгебраический тип данных (ADT), который имеет только одну форму, +например, одноэлементный объект, представленный в Scala `case object`; +или неизменяемая структура с доступными полями, представленная `case class`. + +`case class` обладает всеми функциями класса, а также содержит встроенные дополнительные функции, +которые делают его полезным для функционального программирования. +Когда компилятор видит ключевое слово `case` перед `class`, то применяет следующие эффекты и преимущества: + +- Параметры конструктора `case class` по умолчанию являются общедоступными полями `val`, поэтому поля неизменяемы, + а методы доступа генерируются для каждого параметра. +- Генерируется метод `unapply`, который позволяет использовать `case class` в выражениях match различными способами. +- В классе создается метод `copy`. Он позволяет создавать копии объекта без изменения исходного. +- Создаются методы `equals` и `hashCode` для реализации структурного равенства. +- Генерируется метод по умолчанию `toString`, полезный для отладки. + +Вы _можете_ вручную добавить все эти методы в класс самостоятельно, +но, поскольку эти функции так часто используются в функциональном программировании, +использование case класса гораздо удобнее. + +Этот код демонстрирует несколько функций `case class`: + +{% tabs case-class %} +{% tab 'Scala 2 и 3' for=case-class %} + +```scala +// определение case class +case class Person( + name: String, + vocation: String +) + +// создание экземпляра case class +val p = Person("Reginald Kenneth Dwight", "Singer") + +// полезный метод toString +p // : Person = Person(Reginald Kenneth Dwight,Singer) + +// можно получить доступ к неизменяемым полям +p.name // "Reginald Kenneth Dwight" +p.name = "Joe" // error: can’t reassign a val field + +// при необходимости внести изменения используйте метод `copy` +// для “update as you copy” +val p2 = p.copy(name = "Elton John") +p2 // : Person = Person(Elton John,Singer) +``` + +{% endtab %} +{% endtabs %} + +Дополнительные сведения о `case` классах см. в разделах ["Моделирование предметной области"][data-1]. + +[data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_ru/scala3/book/taste-objects.md b/_ru/scala3/book/taste-objects.md new file mode 100644 index 0000000000..a244c7cfa2 --- /dev/null +++ b/_ru/scala3/book/taste-objects.md @@ -0,0 +1,153 @@ +--- +layout: multipage-overview +title: Одноэлементные объекты +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в использование одноэлементных объектов в Scala 3. +language: ru +num: 12 +previous-page: taste-functions +next-page: taste-collections +--- + + +В Scala ключевое слово `object` создает объект Singleton (паттерн проектирования "Одиночка"). +Другими словами, объект определяет класс, который имеет только один экземпляр. + +Объекты имеют несколько применений: + +- Они используются для создания коллекций служебных методов. +- _Сопутствующий объект_ — это объект с тем же именем, что и у класса, определенного в этом же файле. + В этой ситуации такой класс также называется _сопутствующим классом_. +- Они используются для реализации трейтов для создания _модулей_. + + +## “Полезные” методы + +Поскольку `object` является "одиночкой", к его методам можно обращаться так же, как к статичным методам в Java классе. +Например, этот объект `StringUtils` содержит небольшой набор методов, связанных со строками: + +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_1 %} +```scala +object StringUtils { + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +} +``` +{% endtab %} + +{% tab 'Scala 3' for=object_1 %} +```scala +object StringUtils: + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +``` +{% endtab %} +{% endtabs %} + +Поскольку `StringUtils` - это "одиночка", его методы можно вызывать непосредственно для объекта: + +{% tabs object_2 %} +{% tab 'Scala 2 и 3' for=object_2 %} +```scala +val x = StringUtils.isNullOrEmpty("") // true +val x = StringUtils.isNullOrEmpty("a") // false +``` +{% endtab %} +{% endtabs %} + +## Сопутствующие объекты + +Сопутствующие класс или объект могут получить доступ к закрытым членам своего компаньона. +Используйте сопутствующий объект для методов и значений, которые не относятся к экземплярам сопутствующего класса. + +В этом примере показано, как метод `area` в сопутствующем классе +может получить доступ к приватному методу `calculateArea` в своем сопутствующем объекте: + +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_3 %} +```scala +import scala.math._ + +class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` +{% endtab %} + +{% tab 'Scala 3' for=object_3 %} +```scala +import scala.math.* + +class Circle(radius: Double): + import Circle.* + def area: Double = calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` +{% endtab %} +{% endtabs %} + +## Создание модулей из трейтов + +Объекты также можно использовать для реализации трейтов для создания модулей. +Эта техника берет две трейта и объединяет их для создания конкретного `object`-а: + +{% tabs object_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_4 %} +```scala +trait AddService { + def add(a: Int, b: Int) = a + b +} + +trait MultiplyService { + def multiply(a: Int, b: Int) = a * b +} + +// реализация трейтов выше в качестве конкретного объекта +object MathService extends AddService with MultiplyService + +// использование объекта +import MathService._ +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` +{% endtab %} + +{% tab 'Scala 3' for=object_4 %} +```scala +trait AddService: + def add(a: Int, b: Int) = a + b + +trait MultiplyService: + def multiply(a: Int, b: Int) = a * b + +// реализация трейтов выше в качестве конкретного объекта +object MathService extends AddService, MultiplyService + +// использование объекта +import MathService.* +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` +{% endtab %} +{% endtabs %} diff --git a/_ru/scala3/book/taste-repl.md b/_ru/scala3/book/taste-repl.md new file mode 100644 index 0000000000..e45dc0e8cc --- /dev/null +++ b/_ru/scala3/book/taste-repl.md @@ -0,0 +1,93 @@ +--- +layout: multipage-overview +title: REPL +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в Scala REPL. +language: ru +num: 6 +previous-page: taste-hello-world +next-page: taste-vars-data-types +--- + +Scala REPL (“Read-Evaluate-Print-Loop”) - это интерпретатор командной строки, +который используется в качестве “игровой площадки” для тестирования Scala кода. +Для того чтобы запустить сеанс REPL, надо выполнить команду `scala` или `scala3` в зависимости от операционной системы, +затем будет выведено приглашение “Welcome”, подобное этому: + +{% tabs command-line class=tabs-scala-version %} + +{% tab 'Scala 2' for=command-line %} +```bash +$ scala +Welcome to Scala {{site.scala-version}} (OpenJDK 64-Bit Server VM, Java 1.8.0_342). +Type in expressions for evaluation. Or try :help. + +scala> _ +``` +{% endtab %} + +{% tab 'Scala 3' for=command-line %} +```bash +$ scala +Welcome to Scala {{site.scala-3-version}} (1.8.0_322, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> _ +``` +{% endtab %} + +{% endtabs %} + +REPL — это интерпретатор командной строки, поэтому он ждет, пока вы что-нибудь наберете. +Теперь можно вводить выражения Scala, чтобы увидеть, как они работают: + +{% tabs expression-one %} +{% tab 'Scala 2 и 3' for=expression-one %} +```` +scala> 1 + 1 +val res0: Int = 2 + +scala> 2 + 2 +val res1: Int = 4 +```` +{% endtab %} +{% endtabs %} + +Как показано в выводе, если не присваивать переменную результату выражения, +REPL автоматически создает для вас переменные с именами `res0`, `res1` и т.д. +Эти имена переменных можно использовать в последующих выражениях: + +{% tabs expression-two %} +{% tab 'Scala 2 и 3' for=expression-two %} +```` +scala> val x = res0 * 10 +val x: Int = 20 +```` +{% endtab %} +{% endtabs %} + +Обратите внимание, что в REPL output также показываются результаты выражений. + +В REPL можно проводить всевозможные эксперименты. +В этом примере показано, как создать, а затем вызвать метод `sum`: + +{% tabs expression-three %} +{% tab 'Scala 2 и 3' for=expression-three %} +```` +scala> def sum(a: Int, b: Int): Int = a + b +def sum(a: Int, b: Int): Int + +scala> sum(2, 2) +val res2: Int = 4 +```` +{% endtab %} +{% endtabs %} + +Также можно использовать игровую среду на основе браузера [scastie.scala-lang.org](https://scastie.scala-lang.org). + +Если вы предпочитаете писать код в текстовом редакторе, а не в консоли, то можно использовать [worksheet]. + +[worksheet]: {% link _overviews/scala3-book/tools-worksheets.md %} diff --git a/_ru/scala3/book/taste-summary.md b/_ru/scala3/book/taste-summary.md new file mode 100644 index 0000000000..f2ca4e86da --- /dev/null +++ b/_ru/scala3/book/taste-summary.md @@ -0,0 +1,35 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен краткий обзор предыдущих разделов 'Taste of Scala'. +language: ru +num: 16 +previous-page: taste-toplevel-definitions +next-page: first-look-at-types +--- + + +В предыдущих разделах вы видели: + +- Как использовать Scala REPL +- Как создавать переменные с помощью `val` и `var` +- Некоторые распространенные типы данных +- Структуры управления +- Как моделировать реальный мир, используя стили ООП и ФП +- Как создавать и использовать методы +- Как использовать лямбды (анонимные функции) и функции высшего порядка +- Как использовать объекты для нескольких целей +- Введение в [контекстную абстракцию][contextual] + +Мы также упоминали, что если вы предпочитаете использовать игровую среду на основе браузера вместо Scala REPL, +вы также можете использовать [Scastie](https://scastie.scala-lang.org/). + +Scala включает в себя еще больше возможностей, которые не рассматриваются в этом кратком обзоре. +Дополнительную информацию см. в оставшейся части этой книги и [в справочной документации][reference]. + +[reference]: {{ site.scala3ref }}/overview.html +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_ru/scala3/book/taste-toplevel-definitions.md b/_ru/scala3/book/taste-toplevel-definitions.md new file mode 100644 index 0000000000..3d15f774a8 --- /dev/null +++ b/_ru/scala3/book/taste-toplevel-definitions.md @@ -0,0 +1,79 @@ +--- +layout: multipage-overview +title: Верхнеуровневые определения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлено введение в определения верхнего уровня в Scala 3. +language: ru +num: 15 +previous-page: taste-contextual-abstractions +next-page: taste-summary +--- + + +В Scala 3 все виды определений могут быть записаны на “верхнем уровне” ваших файлов с исходным кодом. +Например, вы можете создать файл с именем _MyCoolApp.scala_ и поместить в него следующее содержимое: + +{% tabs toplevel_1 %} +{% tab 'Только в Scala 3' for=toplevel_1 %} +```scala +import scala.collection.mutable.ArrayBuffer + +enum Topping: + case Cheese, Pepperoni, Mushrooms + +import Topping.* +class Pizza: + val toppings = ArrayBuffer[Topping]() + +val p = Pizza() + +extension (s: String) + def capitalizeAllWords = s.split(" ").map(_.capitalize).mkString(" ") + +val hwUpper = "hello, world".capitalizeAllWords + +type Money = BigDecimal + +// по желанию здесь можно указать ещё больше определений ... + +@main def myApp = + p.toppings += Cheese + println("show me the code".capitalizeAllWords) +``` +{% endtab %} +{% endtabs %} + +Как показано, нет необходимости помещать эти определения внутрь конструкции `package`, `class` или иной конструкции. + +## Заменяет объекты пакета + +Если вы знакомы со Scala 2, этот подход заменяет _объекты пакета_ (_package objects_). +Но, будучи намного проще в использовании, они работают одинаково: +когда вы помещаете определение в пакет с именем `foo`, +вы можете получить доступ к этому определению во всех других пакетах в `foo`, например, в пакете `foo.bar`, +как в этом примере: + +{% tabs toplevel_2 %} +{% tab 'Только в Scala 3' for=toplevel_2 %} +```scala +package foo { + def double(i: Int) = i * 2 +} + +package foo { + package bar { + @main def fooBarMain = + println(s"${double(1)}") // это работает + } +} +``` +{% endtab %} +{% endtabs %} + +Фигурные скобки используются в этом примере, чтобы подчеркнуть вложенность пакета. + +Преимуществом такого подхода является то, что можно размещать определения в пакете с именем `com.acme.myapp`, +а затем можно ссылаться на эти определения в `com.acme.myapp.model`, `com.acme.myapp.controller` и т.д. diff --git a/_ru/scala3/book/taste-vars-data-types.md b/_ru/scala3/book/taste-vars-data-types.md new file mode 100644 index 0000000000..7409db07b4 --- /dev/null +++ b/_ru/scala3/book/taste-vars-data-types.md @@ -0,0 +1,276 @@ +--- +layout: multipage-overview +title: Переменные и типы данных +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе демонстрируются переменные val и var, а также некоторые распространенные типы данных Scala. +language: ru +num: 7 +previous-page: taste-repl +next-page: taste-control-structures +--- + + +В этом разделе представлен обзор переменных и типов данных Scala. + +## Два вида переменных + +Когда вы создаете новую переменную в Scala, то объявляете, является ли переменная неизменяемой или изменяемой: + + + + + + + + + + + + + + + + + + +
Тип переменнойОписание
valСоздает неизменяемую переменную — как final в Java. Вы всегда должны создавать переменную с val, если нет причины, по которой вам нужна изменяемая переменная.
varСоздает изменяемую переменную и должна использоваться только в том случае, если содержимое переменной будет меняться с течением времени.
+ +Эти примеры показывают, как создавать `val` и `var` переменные: + +{% tabs var-express-1 %} +{% tab 'Scala 2 и 3' %} + +```scala +// неизменяемая +val a = 0 + +// изменяемая +var b = 1 +``` +{% endtab %} +{% endtabs %} + +В программе `val` переназначить нельзя. +Появится ошибка компилятора, если попытаться её изменить: + +{% tabs var-express-2 %} +{% tab 'Scala 2 и 3' %} + +```scala +val msg = "Hello, world" +msg = "Aloha" // ошибка "reassignment to val"; этот код не скомпилируется +``` +{% endtab %} +{% endtabs %} + +И наоборот, `var` можно переназначить: + +{% tabs var-express-3 %} +{% tab 'Scala 2 и 3' %} + +```scala +var msg = "Hello, world" +msg = "Aloha" // этот код скомпилируется, потому что var может быть переназначена +``` +{% endtab %} +{% endtabs %} + +## Объявление типов переменных + +Когда вы создаете переменную, то можете явно объявить ее тип или позволить компилятору его вывести: + +{% tabs var-express-4 %} +{% tab 'Scala 2 и 3' %} + +```scala +val x: Int = 1 // явно +val x = 1 // неявно; компилятор выводит тип +``` +{% endtab %} +{% endtabs %} + +Вторая форма известна как _вывод типа_, и это отличный способ сделать кратким код такого типа. +Компилятор Scala обычно может определить тип данных за вас, как показано в выводе этих примеров REPL: + +{% tabs var-express-5 %} +{% tab 'Scala 2 и 3' %} + +```scala +scala> val x = 1 +val x: Int = 1 + +scala> val s = "a string" +val s: String = a string + +scala> val nums = List(1, 2, 3) +val nums: List[Int] = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +Вы всегда можете явно объявить тип переменной, если хотите, +но в простых присваиваниях, подобных нижеследующим, в этом нет необходимости: + +{% tabs var-express-6 %} +{% tab 'Scala 2 и 3' %} + +```scala +val x: Int = 1 +val s: String = "a string" +val p: Person = Person("Richard") +``` +{% endtab %} +{% endtabs %} + +Обратите внимание, что при таком подходе код кажется более многословным, чем необходимо. + +## Встроенные типы данных + +Scala поставляется со стандартными числовыми типами данных, которые вы ожидаете, +и все они являются полноценными экземплярами классов. +В Scala все является объектом. + +Эти примеры показывают, как объявлять переменные числовых типов: + +{% tabs var-express-7 %} +{% tab 'Scala 2 и 3' %} + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` +{% endtab %} +{% endtabs %} + +Поскольку `Int` и `Double` являются числовыми типами по умолчанию, то обычно они создаются без явного объявления типа: + +{% tabs var-express-8 %} +{% tab 'Scala 2 и 3' %} + +```scala +val i = 123 // по умолчанию Int +val j = 1.0 // по умолчанию Double +``` +{% endtab %} +{% endtabs %} + +В своем коде вы также можете добавлять символы `L`, `D` и `F` (и их эквиваленты в нижнем регистре) к числам, +чтобы указать, что они являются `Long`, `Double` или `Float` значениями: + +{% tabs var-express-9 %} +{% tab 'Scala 2 и 3' %} + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = 3.3F // val z: Float = 3.3 +``` +{% endtab %} +{% endtabs %} + +Когда вам нужны действительно большие числа, используйте типы `BigInt` и `BigDecimal`: + +{% tabs var-express-10 %} +{% tab 'Scala 2 и 3' %} + +```scala +var a = BigInt(1_234_567_890_987_654_321L) +var b = BigDecimal(123_456.789) +``` +{% endtab %} +{% endtabs %} + +Где `Double` и `Float` - это приблизительные десятичные числа, а `BigDecimal` используется для точной арифметики. + +В Scala также есть типы данных `String` и `Char`: + +{% tabs var-express-11 %} +{% tab 'Scala 2 и 3' %} + +```scala +val name = "Bill" // String +val c = 'a' // Char +``` +{% endtab %} +{% endtabs %} + +### Строки + +Строки Scala похожи на строки Java, но у них есть две замечательные дополнительные функции: + +- Они поддерживают интерполяцию строк +- Легко создавать многострочные строки + +#### Строковая интерполяция + +Интерполяция строк обеспечивает очень удобный способ использования переменных внутри строк. +Например, учитывая эти три переменные: + +{% tabs var-express-12 %} +{% tab 'Scala 2 и 3' %} + +```scala +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` +{% endtab %} +{% endtabs %} + +Вы можете объединить эти переменные в строку следующим образом: + +{% tabs var-express-13 %} +{% tab 'Scala 2 и 3' %} + +```scala +println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" +``` +{% endtab %} +{% endtabs %} + +Просто поставьте перед строкой букву `s`, а затем поставьте символ `$` перед именами переменных внутри строки. + +Чтобы вставить произвольные выражения в строку, заключите их в фигурные скобки: + +{% tabs var-express-14 %} +{% tab 'Scala 2 и 3' %} + +``` scala +println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" + +val x = -1 +println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +``` +{% endtab %} +{% endtabs %} + +Символ `s`, помещенный перед строкой, является лишь одним из возможных интерполяторов. +Если использовать `f` вместо `s`, можно использовать синтаксис форматирования в стиле `printf` в строке. +Кроме того, интерполятор строк - это всего лишь специальный метод, и его можно определить самостоятельно. +Например, некоторые библиотеки баз данных определяют очень мощный интерполятор `sql`. + +#### Многострочные строки + +Многострочные строки создаются путем включения строки в три двойные кавычки: + +{% tabs var-express-15 %} +{% tab 'Scala 2 и 3' %} + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` +{% endtab %} +{% endtabs %} + +> Дополнительные сведения о строковых интерполяторах и многострочных строках см. в главе [“Первое знакомство с типами”][first-look]. + +[first-look]: {% link _overviews/scala3-book/first-look-at-types.md %} diff --git a/_ru/scala3/book/tools-sbt.md b/_ru/scala3/book/tools-sbt.md new file mode 100644 index 0000000000..2dcb91b7de --- /dev/null +++ b/_ru/scala3/book/tools-sbt.md @@ -0,0 +1,488 @@ +--- +layout: multipage-overview +title: Сборка и тестирование проектов Scala с помощью Sbt +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматриваются широко используемый инструмент сборки sbt и библиотека тестирования ScalaTest. +language: ru +num: 70 +previous-page: scala-tools +next-page: tools-worksheets +--- + +В этом разделе будут показаны два инструмента, которые обычно используются в проектах Scala: + +- инструмент сборки [sbt](https://www.scala-sbt.org) +- [ScalaTest](https://www.scalatest.org) - среда тестирования исходного кода + +Начнем с использования sbt для создания Scala-проектов, а затем рассмотрим, как использовать sbt и ScalaTest вместе для тестирования. + +> Если вы хотите узнать об инструментах, которые помогут вам перенести код Scala 2 на Scala 3, +> ознакомьтесь с нашим [Руководством по миграции на Scala 3](/scala3/guides/migration/compatibility-intro.html). + +## Создание проектов Scala с помощью sbt + +Можно использовать несколько различных инструментов для создания проектов Scala, включая Ant, Maven, Gradle, Mill и другие. +Но инструмент под названием _sbt_ был первым инструментом сборки, специально созданным для Scala. + +> Чтобы установить sbt, см. [страницу загрузки](https://www.scala-sbt.org/download.html) или нашу страницу ["Начало работы"][getting_started]. + +### Создание проекта "Hello, world" + +Вы можете создать sbt проект "Hello, world" всего за несколько шагов. +Сначала создайте каталог для работы и перейдите в него: + +```bash +$ mkdir hello +$ cd hello +``` + +В каталоге `hello` создайте подкаталог `project`: + +```bash +$ mkdir project +``` + +Создайте файл с именем _build.properties_ в каталоге `project` со следующим содержимым: + +```text +sbt.version=1.10.11 +``` + +Затем создайте файл с именем _build.sbt_ в корневом каталоге проекта, содержащий следующую строку: + +```scala +scalaVersion := "{{ site.scala-3-version }}" +``` + +Теперь создайте файл с именем _Hello.scala_ (первая часть имени не имеет значения) со следующей строкой: + +```scala +@main def helloWorld = println("Hello, world") +``` + +Это все, что нужно сделать. + +Должна получиться следующая структура проекта: + +```bash +$ tree +. +├── build.sbt +├── Hello.scala +└── project + └── build.properties +``` + +Теперь запустите проект с помощью команды `sbt`: + +```bash +$ sbt run +``` + +Вы должны увидеть вывод, который выглядит следующим образом, включая `"Hello, world"` из программы: + +```bash +$ sbt run +[info] welcome to sbt 1.6.1 (AdoptOpenJDK Java 11.x) +[info] loading project definition from project ... +[info] loading settings for project from build.sbt ... +[info] compiling 1 Scala source to target/scala-3.0.0/classes ... +[info] running helloWorld +Hello, world +[success] Total time: 2 s +``` + +Программа запуска — средство командной строки `sbt` - загружает версию sbt, установленную в файле _project/build.properties_, +которая загружает версию компилятора Scala, установленную в файле _build.sbt_, +компилирует код в файле _Hello.scala_ и запускает результирующий байт-код. + +Если посмотреть на корневой каталог, то можно увидеть, что появилась папка с именем _target_. +Это рабочие каталоги, которые использует sbt. + +Создание и запуск небольшого проекта Scala с помощью sbt занимает всего несколько простых шагов. + +### Использование sbt в более крупных проектах + +Для небольшого проекта это все, что требует sbt для запуска. +Для более крупных проектов с большим количеством файлов исходного кода, зависимостей или плагинов, +потребуется создать организованную структуру каталогов. +Остальная часть этого раздела демонстрирует структуру, которую использует sbt. + +### Структура каталогов sbt + +Как и Maven, sbt использует стандартную структуру каталогов проекта. +Преимуществом стандартизации является то, что, как только структура станет привычной, +станет легко работать с другими проектами Scala/sbt. + +Первое, что нужно знать - это то, что под корневым каталогом проекта sbt ожидает структуру каталогов, +которая выглядит следующим образом: + +```text +. +├── build.sbt +├── project/ +│ └── build.properties +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ ├── resources/ +│ │ └── scala/ +│ └── test/ +│ ├── java/ +│ ├── resources/ +│ └── scala/ +└── target/ +``` + +Также в корневой каталог можно добавить каталог _lib_, +если необходимо в свой проект добавить внешние зависимости — файлы JAR. + +Если достаточно создать проект, который имеет только файлы исходного кода Scala и тесты, +но не будет использовать Java файлы и не нуждается в каких-либо "ресурсах" (встроенные изображения, файлы конфигурации и т.д.), +то в каталоге _src_ можно оставить только: + +```text +. +└── src/ + ├── main/ + │ └── scala/ + └── test/ + └── scala/ +``` + +### "Hello, world" со структурой каталогов sbt + +Создать такую структуру каталогов просто. +Существуют инструменты, которые сделают это за вас, но если вы используете систему Unix/Linux, +можно использовать следующие команды для создания структуры каталогов проекта sbt: + +```bash +$ mkdir HelloWorld +$ cd HelloWorld +$ mkdir -p src/{main,test}/scala +$ mkdir project target +``` + +После запуска этих команд, по запросу `find .` вы должны увидеть такой результат: + +```bash +$ find . +. +./project +./src +./src/main +./src/main/scala +./src/test +./src/test/scala +./target +``` + +Если вы это видите, отлично, вы готовы для следующего шага. + +> Существуют и другие способы создания файлов и каталогов для проекта sbt. +> Один из способов - использовать команду sbt new, которая [задокументирована на scala-sbt.org](https://www.scala-sbt.org/1.x/docs/Hello.html). +> Этот подход здесь не показан, поскольку некоторые из создаваемых им файлов более сложны, чем необходимо для такого введения. + +### Создание первого файла build.sbt + +На данный момент нужны еще две вещи для запуска проекта "Hello, world": + +- файл _build.sbt_ +- файл _Hello.scala_ + +Для такого небольшого проекта файлу _build.sbt_ нужна только запись `scalaVersion`, но мы добавим три строки: + +```scala +name := "HelloWorld" +version := "0.1" +scalaVersion := "{{ site.scala-3-version }}" +``` + +Поскольку проекты sbt используют стандартную структуру каталогов, sbt может найти все, что ему нужно. + +Теперь осталось просто добавить небольшую программу "Hello, world". + +### Программа "Hello, world" + +В больших проектах все файлы исходного кода будут находиться в каталогах _src/main/scala_ и _src/test/scala_, +но для небольшого примера, подобного этому, можно поместить файл исходного кода в корневой каталог. +Поэтому создайте файл с именем _HelloWorld.scala_ в корневом каталоге со следующим содержимым: + +```scala +@main def helloWorld = println("Hello, world") +``` + +Этот код определяет "main" метод, который печатает `"Hello, world"` при запуске. + +Теперь используйте команду `sbt run` для компиляции и запуска проекта: + +```bash +$ sbt run + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition +[info] loading settings for project root from build.sbt ... +[info] Compiling 1 Scala source ... +[info] running helloWorld +Hello, world +[success] Total time: 4 s +``` + +При первом запуске `sbt` загружает все, что ему нужно (это может занять несколько секунд), +но после первого раза запуск становится намного быстрее. + +Кроме того, после выполнения первого шага можно обнаружить, что гораздо быстрее запускать sbt в интерактивном режиме. +Для этого вначале отдельно запустите команду `sbt`: + +```bash +$ sbt + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition ... +[info] loading settings for project root from build.sbt ... +[info] sbt server started at + local:///${HOME}/.sbt/1.0/server/7d26bae822c36a31071c/sock +sbt:hello-world> _ +``` + +Затем внутри этой оболочки выполните команду `run`: + +``` +sbt:hello-world> run + +[info] running helloWorld +Hello, world +[success] Total time: 0 s +``` + +Так намного быстрее. + +Если вы наберете `help` в командной строке sbt, то увидите список других команд, доступных для запуска. +Введите `exit` (или нажмите `CTRL-D`), чтобы выйти из оболочки sbt. + +### Использование шаблонов проектов + +Ручное создание структуры проекта может быть утомительным. К счастью, sbt может создать структуру на основе шаблона. + +Чтобы создать проект Scala 3 из шаблона, выполните следующую команду в оболочке: + +``` +$ sbt new scala/scala3.g8 +``` + +Sbt загрузит шаблон, задаст несколько вопросов и создаст файлы проекта в подкаталоге: + +``` +$ tree scala-3-project-template +scala-3-project-template +├── build.sbt +├── project +│ └── build.properties +├── README.md +└── src + ├── main + │ └── scala + │ └── Main.scala + └── test + └── scala + └── Test1.scala +``` + +> Если вы хотите создать проект Scala 3, который кросс-компилируется со Scala 2, используйте шаблон `scala/scala3-cross.g8`: +> +> ``` +> $ sbt new scala/scala3-cross.g8 +> ``` + +Узнайте больше о `sbt new` и шаблонах проектов в [документации sbt](https://www.scala-sbt.org/1.x/docs/sbt-new-and-Templates.html#sbt+new+and+Templates). + +### Другие инструменты сборки для Scala + +Хотя sbt широко используется, есть и другие инструменты, которые можно использовать для создания проектов Scala: + +- [Ant](https://ant.apache.org/) +- [Gradle](https://gradle.org/) +- [Maven](https://maven.apache.org/) +- [Mill](https://com-lihaoyi.github.io/mill/) + +#### Coursier + +[Coursier](https://get-coursier.io/docs/overview) - это "преобразователь зависимостей", похожий по функциям на Maven и Ivy. +Он написан на Scala с нуля, "охватывает принципы функционального программирования" +и для быстроты параллельно загружает артефакты. +sbt использует Coursier для обработки большинства разрешений зависимостей, +а в качестве инструмента командной строки его можно использовать для простой установки таких инструментов, +как sbt, Java и Scala, как показано на странице ["С чего начать?"][getting_started]. + +Этот пример со страницы `launch` показывает, что команда `cs launch` может использоваться для запуска приложений из зависимостей: + +```scala +$ cs launch org.scalameta::scalafmt-cli:2.4.2 -- --help +scalafmt 2.4.2 +Usage: scalafmt [options] [...] + + -h, --help prints this usage text + -v, --version print version + more ... +``` + +Подробнее см. на странице [запуска Coursier](https://get-coursier.io/docs/cli-launch). + +## Использование sbt со ScalaTest + +[ScalaTest](https://www.scalatest.org) — одна из основных библиотек тестирования для проектов Scala. +В этом разделе рассмотрим шаги, необходимые для создания проекта Scala/sbt, использующего ScalaTest. + +### 1) Создание структуры каталогов проекта + +Как и в предыдущем уроке, создаем структуру каталогов sbt для проекта с именем _HelloScalaTest_ с помощью следующих команд: + +```bash +$ mkdir HelloScalaTest +$ cd HelloScalaTest +$ mkdir -p src/{main,test}/scala +$ mkdir project +``` + +### 2) Создание файлов build.properties и build.sbt + +Затем создаем файл _build.properties_ в подкаталоге _project/_ проекта с такой строкой: + +```text +sbt.version=1.10.11 +``` + +Создаем файл _build.sbt_ в корневом каталоге проекта со следующим содержимым: + +```scala +name := "HelloScalaTest" +version := "0.1" +scalaVersion := "{{site.scala-3-version}}" + +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.19" % Test +) +``` + +Первые три строки этого файла практически такие же, как и в первом примере. +Строки `libraryDependencies` сообщают sbt о включении зависимостей (файлов JAR), которые необходимы для добавления ScalaTest. + +> Документация по ScalaTest всегда была хорошей, и вы всегда можете найти актуальную информацию о том, +> как должны выглядеть эти строки, на странице ["Установка ScalaTest"](https://www.scalatest.org/install). + +### 3) Создание файла исходного кода Scala + +Затем создаем программу Scala, которую можно использовать для демонстрации ScalaTest. +Сначала создайте каталог в _src/main/scala_ с именем _math_: + +```bash +$ mkdir src/main/scala/math + ---- +``` + +Внутри этого каталога создайте файл _MathUtils.scala_ со следующим содержимым: + +```scala +package math + +object MathUtils: + def double(i: Int) = i * 2 +``` + +Этот метод обеспечивает простой способ демонстрации ScalaTest. + +### 4) Создание первых тестов ScalaTest + +ScalaTest очень гибок и предлагает несколько различных способов написания тестов. +Простой способ начать работу — написать тесты с помощью `AnyFunSuite`. +Для начала создайте каталог с именем _math_ в каталоге _src/test/scala_: + +```bash +$ mkdir src/test/scala/math + ---- +``` + +Затем создайте в этом каталоге файл с именем _MathUtilsTests.scala_ со следующим содержимым: + +```scala +package math + +import org.scalatest.funsuite.AnyFunSuite + +class MathUtilsTests extends AnyFunSuite: + + // test 1 + test("'double' should handle 0") { + val result = MathUtils.double(0) + assert(result == 0) + } + + // test 2 + test("'double' should handle 1") { + val result = MathUtils.double(1) + assert(result == 2) + } + + test("test with Int.MaxValue") (pending) + +end MathUtilsTests +``` + +Этот код демонстрирует `AnyFunSuite` подход. +Несколько важных моментов: + +- тестовый класс должен расширять `AnyFunSuite` +- тесты создаются, задавая каждому `test` уникальное имя +- в конце каждого теста необходимо вызвать `assert`, чтобы проверить, выполнено ли условие +- когда вы знаете, что хотите написать тест, но не хотите писать его прямо сейчас, + создайте тест как "pending" (ожидающий) с показанным синтаксисом + +Подобное использование ScalaTest напоминает JUnit, так что если вы переходите с Java на Scala, это должно показаться знакомым. + +Теперь можно запустить эти тесты с помощью команды `sbt test`. +Пропуская первые несколько строк вывода, результат выглядит следующим образом: + +``` +sbt:HelloScalaTest> test + +[info] Compiling 1 Scala source ... +[info] MathUtilsTests: +[info] - 'double' should handle 0 +[info] - 'double' should handle 1 +[info] - test with Int.MaxValue (pending) +[info] Total number of tests run: 2 +[info] Suites: completed 1, aborted 0 +[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1 +[info] All tests passed. +[success] Total time: 1 s +``` + +Если все работает хорошо, вы увидите примерно такой результат. +Добро пожаловать в мир тестирования приложений Scala с помощью sbt и ScalaTest. + +### Поддержка различных видов тестов + +В этом примере демонстрируется стиль тестирования, аналогичный стилю xUnit _Test-Driven Development_ (TDD), +с некоторыми преимуществами _Behavior-Driven Development_ (BDD). + +Как уже упоминалось, ScalaTest является гибким, и вы также можете писать тесты, используя другие стили, +такие как стиль, похожий на RSpec Ruby. +Вы также можете использовать моканные объекты, тестирование на основе свойств +и использовать ScalaTest для тестирования кода Scala.js. + +Дополнительные сведения о различных доступных стилях тестирования +см. в Руководстве пользователя на [веб-сайте ScalaTest](https://www.scalatest.org). + +## Что дальше? + +Дополнительные сведения о sbt и ScalaTest см. в следующих ресурсах: + +- [The sbt documentation](https://www.scala-sbt.org/1.x/docs/) +- [The ScalaTest website](https://www.scalatest.org/) + +[getting_started]: {{ site.baseurl }}/ru/getting-started/install-scala.html diff --git a/_ru/scala3/book/tools-worksheets.md b/_ru/scala3/book/tools-worksheets.md new file mode 100644 index 0000000000..5409c5ad40 --- /dev/null +++ b/_ru/scala3/book/tools-worksheets.md @@ -0,0 +1,63 @@ +--- +layout: multipage-overview +title: Рабочие листы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматриваются рабочие листы — альтернатива проектам Scala. +language: ru +num: 71 +previous-page: tools-sbt +next-page: interacting-with-java +--- + +Worksheet - это файл Scala, который вычисляется при сохранении, +и результат каждого выражения отображается в столбце справа от программы. +Рабочие листы похожи на [сеанс REPL][REPL session] на стероидах +и имеют поддержку редактора 1-го класса: завершение, гиперссылки, интерактивные ошибки при вводе и т.д. +Рабочие листы используют расширение `.worksheet.sc`. + +Далее покажем, как использовать рабочие листы в IntelliJ и в VS Code (с расширением Metals). + +1. Откройте проект Scala или создайте его: + - чтобы создать проект в IntelliJ, выберите "File" -> "New" -> "Project...", + выберите "Scala" в левой колонке и нажмите "Далее", чтобы задать название проекта и каталог. + - чтобы создать проект в VS Code, выполните команду "Metals: New Scala project", + выберите начальный `scala/scala3.g8`, задайте местоположение проекта, + откройте его в новом окне VS Code и импортируйте сборку. +1. Создайте файл с именем `hello.worksheet.sc` в каталоге `src/main/scala/`. + - в IntelliJ щелкните правой кнопкой мыши на каталоге `src/main/scala/` и выберите "New", а затем "File". + - в VS Code щелкните правой кнопкой мыши на каталоге `src/main/scala/` и выберите "New File". +1. Вставьте следующее содержимое в редактор: + + ``` + println("Hello, world!") + + val x = 1 + x + x + ``` + +1. Запустите worksheet: + + - в IntelliJ щелкните зеленую стрелку в верхней части редактора, чтобы запустить worksheet. + - в VS Code сохраните файл. + + Вы должны увидеть результат выполнения каждой строки на правой панели (IntelliJ) или в виде комментариев (VS Code). + +![]({{ site.baseurl }}/resources/images/scala3-book/intellij-worksheet.png) + +Рабочий лист, выполненный в IntelliJ. + +![]({{ site.baseurl }}/resources/images/scala3-book/metals-worksheet.png) + +Рабочий лист, выполненный в VS Code (с расширением Metals). + +Обратите внимание, что worksheet будет использовать версию Scala, +определенную проектом (обычно задается ключом `scalaVersion` в файле `build.sbt`). + +Также обратите внимание, что worksheet не имеют [точек входа в программу][program entry point]. +Вместо этого операторы и выражения верхнего уровня оцениваются сверху вниз. + +[REPL session]: {{ site.baseurl }}/ru/scala3/book/taste-repl.html +[program entry point]: {{ site.baseurl }}/ru/scala3/book/methods-main-methods.html diff --git a/_ru/scala3/book/types-adts-gadts.md b/_ru/scala3/book/types-adts-gadts.md new file mode 100644 index 0000000000..50091980e3 --- /dev/null +++ b/_ru/scala3/book/types-adts-gadts.md @@ -0,0 +1,223 @@ +--- +layout: multipage-overview +title: Алгебраические типы данных +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются алгебраические типы данных (ADT) в Scala 3. +language: ru +num: 53 +previous-page: types-union +next-page: types-variance +versionSpecific: true +--- + +Только в Scala 3 + +Алгебраические типы данных (ADT) могут быть созданы с помощью конструкции `enum`, +поэтому кратко рассмотрим перечисления, прежде чем рассматривать ADT. + +## Перечисления + +_Перечисление_ используется для определения типа, состоящего из набора именованных значений: + +```scala +enum Color: + case Red, Green, Blue +``` + +который можно рассматривать как сокращение для: + +```scala +enum Color: + case Red extends Color + case Green extends Color + case Blue extends Color +``` + +#### Параметры + +Перечисления могут быть параметризованы: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +Таким образом, каждый из различных вариантов содержит параметр `rgb`, +которому присваивается соответствующее значение: + +```scala +println(Color.Green.rgb) // выводит 65280 +``` + +#### Пользовательские определения + +Перечисления также могут содержать пользовательские определения: + +```scala +enum Planet(mass: Double, radius: Double): + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // остальные 5 или 6 планет ... +``` + +Подобно классам и `case` классам, вы также можете определить сопутствующий объект для перечисления: + +```scala +object Planet: + def main(args: Array[String]) = + val earthWeight = args(0).toDouble + val mass = earthWeight / Earth.surfaceGravity + for (p <- values) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") +``` + +## Алгебраические типы данных (ADTs) + +Концепция `enum` является достаточно общей, +чтобы также поддерживать _алгебраические типы данных_ (ADT) и их обобщенную версию (GADT). +Вот пример, показывающий, как тип `Option` может быть представлен в виде АТД: + +```scala +enum Option[+T]: + case Some(x: T) + case None +``` + +В этом примере создается перечисление `Option` с параметром ковариантного типа `T`, +состоящим из двух вариантов `Some` и `None`. +`Some` _параметризуется_ значением параметра `x`; +это сокращение для написания `case` класса, расширяющего `Option`. +Поскольку `None` не параметризуется, то он считается обычным enum значением. + +Предложения `extends`, которые были опущены в предыдущем примере, также могут быть указаны явно: + +```scala +enum Option[+T]: + case Some(x: T) extends Option[T] + case None extends Option[Nothing] +``` + +Как и в случае с обычным `enum` значениями, варианты enum определяются в его сопутствующем объекте, +поэтому они называются `Option.Some` и `Option.None` (если только определения не «вытягиваются» при импорте): + +```scala +scala> Option.Some("hello") +val res1: t2.Option[String] = Some(hello) + +scala> Option.None +val res2: t2.Option[Nothing] = None +``` + +Как и в других случаях использования перечисления, АТД могут определять дополнительные методы. +Например, вот снова `Option`, с методом `isDefined` и конструктором `Option(...)` в сопутствующем объекте: + +```scala +enum Option[+T]: + case Some(x: T) + case None + + def isDefined: Boolean = this match + case None => false + case Some(_) => true + +object Option: + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) +``` + +Перечисления и АТД используют одну и ту же синтаксическую конструкцию, +поэтому их можно рассматривать просто как два конца спектра, и вполне допустимо создавать гибриды. +Например, приведенный ниже код реализует `Color` либо с тремя значениями перечисления, +либо с параметризованным вариантом, принимающим значение RGB: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +``` + +#### Рекурсивные перечисления + +До сих пор все перечисления, которые мы определяли, состояли из различных вариантов значений или case классов. +Перечисления также могут быть рекурсивными, как показано в приведенном ниже примере кодирования натуральных чисел: + +```scala +enum Nat: + case Zero + case Succ(n: Nat) +``` + +Например, значение `Succ(Succ(Zero))` представляет число `2` в унарной кодировке. +Списки могут быть определены похожим образом: + +```scala +enum List[+A]: + case Nil + case Cons(head: A, tail: List[A]) +``` + +## Обобщенные алгебраические типы данных (GADT) + +Приведенная выше нотация для перечислений очень краткая +и служит идеальной отправной точкой для моделирования ваших типов данных. +Поскольку мы всегда можем быть более подробными, то можем выразить гораздо более мощные типы: +обобщенные алгебраические типы данных (GADT). + +Вот пример GADT, в котором параметр типа (`T`) указывает на тип содержимого, хранящегося в `Box`: + +```scala +enum Box[T](contents: T): + case IntBox(n: Int) extends Box[Int](n) + case BoolBox(b: Boolean) extends Box[Boolean](b) +``` + +Сопоставление с образцом с конкретным конструктором (`IntBox` или `BoolBox`) восстанавливает информацию о типе: + +```scala +def extract[T](b: Box[T]): T = b match + case IntBox(n) => n + 1 + case BoolBox(b) => !b +``` + +Безопасно возвращать `Int` в первом случае, так как мы знаем из сопоставления с образцом, что ввод был `IntBox`. + +## Дешугаризация перечислений + +_Концептуально_ перечисления можно рассматривать как определение запечатанного класса вместе с сопутствующим ему объектом. +Давайте посмотрим на дешугаризацию нашего перечисления `Color`: + +```scala +sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum +object Color: + case object Red extends Color(0xFF0000) { def ordinal = 0 } + case object Green extends Color(0x00FF00) { def ordinal = 1 } + case object Blue extends Color(0x0000FF) { def ordinal = 2 } + case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 } + + def fromOrdinal(ordinal: Int): Color = ordinal match + case 0 => Red + case 1 => Green + case 2 => Blue + case _ => throw new NoSuchElementException(ordinal.toString) +``` + +Заметьте, что вышеописанная дешугаризация упрощена, и мы намеренно опускаем [некоторые детали][desugar-enums]. + +В то время как перечисления можно кодировать вручную с помощью других конструкций, +использование перечислений является более кратким, +а также включает несколько дополнительных утилит (таких, как метод `fromOrdinal`). + +[desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html diff --git a/_ru/scala3/book/types-dependent-function.md b/_ru/scala3/book/types-dependent-function.md new file mode 100644 index 0000000000..88750e2ec5 --- /dev/null +++ b/_ru/scala3/book/types-dependent-function.md @@ -0,0 +1,179 @@ +--- +layout: multipage-overview +title: Зависимые типы функций +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются зависимые типы функций в Scala 3. +language: ru +num: 57 +previous-page: types-structural +next-page: types-others +versionSpecific: true +--- + +_Зависимый тип функции_ (_dependent function type_) описывает типы функций, +где тип результата может зависеть от значений параметров функции. +Концепция зависимых типов и типов зависимых функций является более продвинутой, +и обычно с ней сталкиваются только при разработке собственных библиотек или использовании расширенных библиотек. + +## Зависимые типы методов + +Рассмотрим следующий пример гетерогенной базы данных, в которой могут храниться значения разных типов. +Ключ содержит информацию о типе соответствующего значения: + +```scala +trait Key { type Value } + +trait DB { + def get(k: Key): Option[k.Value] // зависимый метод +} +``` + +Получив ключ, метод `get` предоставляет доступ к карте и потенциально возвращает сохраненное значение типа `k.Value`. +Мы можем прочитать этот _path-dependent type_ как: +"в зависимости от конкретного типа аргумента `k` возвращается соответствующее значение". + +Например, у нас могут быть следующие ключи: + +```scala +object Name extends Key { type Value = String } +object Age extends Key { type Value = Int } +``` + +Вызовы метода `get` теперь будут возвращать такие типы: + +```scala +val db: DB = ... +val res1: Option[String] = db.get(Name) +val res2: Option[Int] = db.get(Age) +``` + +Вызов метода `db.get(Name)` возвращает значение типа `Option[String]`, +а вызов `db.get(Age)` возвращает значение типа `Option[Int]`. +Тип возвращаемого значения _зависит_ от конкретного типа аргумента, переданного для `get` — отсюда и название _dependent type_. + +## Зависимые типы функций + +Как видно выше, в Scala 2 уже была поддержка зависимых типов методов. +Однако создание значений типа `DB` довольно громоздко: + +```scala +// создание пользователя DB +def user(db: DB): Unit = + db.get(Name) ... db.get(Age) + +// создание экземпляра DB и передача его `user` +user(new DB { + def get(k: Key): Option[k.Value] = ... // реализация DB +}) +``` + +Необходимо вручную создать анонимный внутренний класс `DB`, реализующий метод `get`. +Для кода, основанного на создании множества различных экземпляров `DB`, это очень утомительно. + +Трейт `DB` имеет только один абстрактный метод `get`. +Было бы неплохо использовать в этом месте лямбда-синтаксис? + +```scala +user { k => + ... // реализация DB +} +``` + +На самом деле, в Scala 3 теперь это возможно! Можно определить `DB` как _зависимый тип функции_: + +```scala +type DB = (k: Key) => Option[k.Value] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// зависимый тип функции +``` + +Учитывая это определение `DB`, можно использовать приведенный выше вызов `user`. + +Подробнее о внутреннем устройстве зависимых типов функций можно прочитать в [справочной документации][ref]. + +## Практический пример: числовые выражения + +Предположим, что необходимо определить модуль, который абстрагируется от внутреннего представления чисел. +Это может быть полезно, например, для реализации библиотек для автоматического дифференцирования. + +Начнем с определения модуля для чисел: + +```scala +trait Nums: + // тип Num оставлен абстрактным + type Num + + // некоторые операции над числами + def lit(d: Double): Num + def add(l: Num, r: Num): Num + def mul(l: Num, r: Num): Num +``` + +> Здесь опускается конкретная реализация `Nums`, но в качестве упражнения можно реализовать `Nums`, +> назначив тип `Num = Double` и реализуя соответствующие методы. + +Программа, использующая числовую абстракцию, теперь имеет следующий тип: + +```scala +type Prog = (n: Nums) => n.Num => n.Num + +val ex: Prog = nums => x => nums.add(nums.lit(0.8), x) +``` + +Тип функции, которая вычисляет производную, наподобие `ex`: + +```scala +def derivative(input: Prog): Double +``` + +Учитывая удобство зависимых типов функций, вызов этой функции в разных программах прост: + +```scala +derivative { nums => x => x } +derivative { nums => x => nums.add(nums.lit(0.8), x) } +// ... +``` + +Напомним, что та же программа в приведенной выше кодировке будет выглядеть так: + +```scala +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = x +}) +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x) +}) +// ... +``` + +#### Комбинация с контекстными функциями + +Комбинация методов расширения, [контекстных функций][ctx-fun] и зависимых функций обеспечивает мощный инструмент для разработчиков библиотек. +Например, мы можем уточнить нашу библиотеку, как указано выше, следующим образом: + +```scala +trait NumsDSL extends Nums: + extension (x: Num) + def +(y: Num) = add(x, y) + def *(y: Num) = mul(x, y) + +def const(d: Double)(using n: Nums): n.Num = n.lit(d) + +type Prog = (n: NumsDSL) ?=> n.Num => n.Num +// ^^^ +// prog теперь - контекстная функция, +// которая неявно предполагает NumsDSL в контексте вызова + +def derivative(input: Prog): Double = ... + +// теперь нам не нужно упоминать Nums в приведенных ниже примерах +derivative { x => const(1.0) + x } +derivative { x => x * x + const(2.0) } +// ... +``` + +[ref]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[ctx-fun]: {{ site.scala3ref }}/contextual/context-functions.html diff --git a/_ru/scala3/book/types-generics.md b/_ru/scala3/book/types-generics.md new file mode 100644 index 0000000000..5ece10b356 --- /dev/null +++ b/_ru/scala3/book/types-generics.md @@ -0,0 +1,103 @@ +--- +layout: multipage-overview +title: Параметризованные типы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены параметризованные типы в Scala 3. +language: ru +num: 50 +previous-page: types-inferred +next-page: types-intersection +--- + +Универсальные (_generic_) классы (или trait-ы) принимают тип в качестве _параметра_ в квадратных скобках `[...]`. +Для обозначения параметров типа согласно конвенции Scala используется одна заглавная буква (например, `A`). +Затем этот тип можно использовать внутри класса по мере необходимости +для параметров экземпляра метода или для возвращаемых типов: + +{% tabs stack class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +```scala +// здесь мы объявляем параметр типа A +// v +class Stack[A] { + private var elements: List[A] = Nil + // ^ + // здесь мы ссылаемся на этот тип + // v + def push(x: A): Unit = + elements = elements.prepended(x) + def peek: A = elements.head + def pop(): A = { + val currentTop = peek + elements = elements.tail + currentTop + } +} +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +// здесь мы объявляем параметр типа A +// v +class Stack[A]: + private var elements: List[A] = Nil + // ^ + // здесь мы ссылаемся на этот тип + // v + def push(x: A): Unit = + elements = elements.prepended(x) + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` + +{% endtab %} +{% endtabs %} + +Эта реализация класса `Stack` принимает любой тип в качестве параметра. +Прелесть параметризованных типов состоит в том, +что теперь можно создавать `Stack[Int]`, `Stack[String]` и т.д., +что позволяет повторно использовать реализацию `Stack` для произвольных типов элементов. + +Пример создания и использования `Stack[Int]`: + +{% tabs stack-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val stack = new Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // выводит 2 +println(stack.pop()) // выводит 1 +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // выводит 2 +println(stack.pop()) // выводит 1 +``` + +{% endtab %} +{% endtabs %} + +> Подробности о том, как выразить вариантность с помощью универсальных типов, +> см. в разделе ["Вариантность"][variance]. + +[variance]: {% link _overviews/scala3-book/types-variance.md %} diff --git a/_ru/scala3/book/types-inferred.md b/_ru/scala3/book/types-inferred.md new file mode 100644 index 0000000000..cadedc1ca0 --- /dev/null +++ b/_ru/scala3/book/types-inferred.md @@ -0,0 +1,65 @@ +--- +layout: multipage-overview +title: Определение типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются выводимые типы в Scala 3. +language: ru +num: 49 +previous-page: types-introduction +next-page: types-generics +--- + +Как и в других статически типизированных языках программирования, +в Scala тип можно _объявить_ при создании новой переменной: + +{% tabs xy %} +{% tab 'Scala 2 и 3' %} + +```scala +val x: Int = 1 +val y: Double = 1 +``` + +{% endtab %} +{% endtabs %} + +В этих примерах типы _явно_ объявлены как `Int` и `Double` соответственно. +Однако в Scala обычно необязательно указывать тип при объявлении переменной: + +{% tabs abm %} +{% tab 'Scala 2 и 3' %} + +```scala +val a = 1 +val b = List(1, 2, 3) +val m = Map(1 -> "one", 2 -> "two") +``` + +{% endtab %} +{% endtabs %} + +Когда вы это сделаете, Scala сама _выведет_ типы, как показано в следующей сессии REPL: + +{% tabs abm2 %} +{% tab 'Scala 2 и 3' %} + +```scala +scala> val a = 1 +val a: Int = 1 + +scala> val b = List(1, 2, 3) +val b: List[Int] = List(1, 2, 3) + +scala> val m = Map(1 -> "one", 2 -> "two") +val m: Map[Int, String] = Map(1 -> one, 2 -> two) +``` + +{% endtab %} +{% endtabs %} + +Действительно, большинство переменных определяются без указания типа, +и способность Scala автоматически определять его — это одна из особенностей, +которая делает Scala _похожим_ на язык с динамической типизацией. diff --git a/_ru/scala3/book/types-intersection.md b/_ru/scala3/book/types-intersection.md new file mode 100644 index 0000000000..759855824b --- /dev/null +++ b/_ru/scala3/book/types-intersection.md @@ -0,0 +1,76 @@ +--- +layout: multipage-overview +title: Пересечение типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены пересечение типов в Scala 3. +language: ru +num: 51 +previous-page: types-generics +next-page: types-union +--- + +Только в Scala 3 + +Используемый для типов оператор `&` создает так называемый _тип пересечения_ (_intersection type_). +Тип `A & B` представляет собой значения, которые **одновременно** относятся как к типу `A`, так и к типу `B`. +Например, в следующем примере используется тип пересечения `Resettable & Growable[String]`: + +{% tabs intersection-reset-grow %} + +{% tab 'Только в Scala 3' %} + +```scala +trait Resettable: + def reset(): Unit + +trait Growable[A]: + def add(a: A): Unit + +def f(x: Resettable & Growable[String]): Unit = + x.reset() + x.add("first") +``` + +{% endtab %} + +{% endtabs %} + +В методе `f` в этом примере параметр `x` должен быть _одновременно_ как `Resettable`, так и `Growable[String]`. + +Все _члены_ типа пересечения `A & B` являются типом `A` и типом `B`. +Следовательно, как показано, для `Resettable & Growable[String]` доступны методы `reset` и `add`. + +Пересечение типов может быть полезно для _структурного_ описания требований. +В примере выше для `f` мы прямо заявляем, что нас устраивает любое значение для `x`, +если оно является подтипом как `Resettable`, так и `Growable`. +**Нет** необходимости создавать _номинальный_ вспомогательный trait, подобный следующему: + +{% tabs normal-trait class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Both[A] extends Resettable with Growable[A] +def f(x: Both[String]): Unit +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait Both[A] extends Resettable, Growable[A] +def f(x: Both[String]): Unit +``` + +{% endtab %} +{% endtabs %} + +Существует важное различие между двумя вариантами определения `f`: +в то время как оба позволяют вызывать `f` с экземплярами `Both`, +только первый позволяет передавать экземпляры, +которые являются подтипами `Resettable` и `Growable[String]`, _но не_ `Both[String]`. + +> Обратите внимание, что `&` _коммутативно_: `A & B` имеет тот же тип, что и `B & A`. diff --git a/_ru/scala3/book/types-introduction.md b/_ru/scala3/book/types-introduction.md new file mode 100644 index 0000000000..65ecf50ebf --- /dev/null +++ b/_ru/scala3/book/types-introduction.md @@ -0,0 +1,68 @@ +--- +layout: multipage-overview +title: Типы и система типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлено введение в типы и систему типов Scala 3. +language: ru +num: 48 +previous-page: fp-summary +next-page: types-inferred +--- + +Scala — уникальный язык, поскольку он статически типизирован, но часто кажется гибким и динамичным. +Например, благодаря выводу типов можно писать код без явного указания типов переменных: + +{% tabs hi %} +{% tab 'Scala 2 и 3' %} + +```scala +val a = 1 +val b = 2.0 +val c = "Hi!" +``` + +{% endtab %} +{% endtabs %} + +Это делает код динамически типизированным. +А благодаря новым функциям в Scala 3, таким как [объединение типов][union-types], +также можно писать код, подобный следующему, +который кратко выражает, какие значения ожидаются в качестве аргументов и какие типы возвращаются: + +{% tabs union-example %} +{% tab 'Только в Scala 3' %} + +```scala +def isTruthy(a: Boolean | Int | String): Boolean = ??? +def dogCatOrWhatever(): Dog | Plant | Car | Sun = ??? +``` + +{% endtab %} +{% endtabs %} + +Как видно из примера, при использовании объединения типы необязательно должны иметь общую иерархию, +и их по-прежнему можно принимать в качестве аргументов или возвращать из метода. + +При разработке приложений такие функции, как вывод типов, +используются каждый день, а generics - каждую неделю. +При чтении Scaladoc для классов и методов, также необходимо иметь некоторое представление о _ковариантности_. +Использование типов может быть относительно простым, +а также обеспечивает большую выразительность, гибкость и контроль для разработчиков библиотек. + +## Преимущества типов + +Языки программирования со статической типизацией предлагают ряд преимуществ, в том числе: + +- помощь IDE в обеспечении надежной поддержки +- устранение многих классов потенциальных ошибок во время компиляции +- помощь в рефакторинге +- предоставление надежной документации, которая не может быть нерелевантной, поскольку проверена на тип + +## Знакомство с особенностями системы типов в Scala + +Учитывая это краткое введение, в следующих разделах представлен обзор особенностей системы типов в Scala. + +[union-types]: {% link _overviews/scala3-book/types-union.md %} diff --git a/_ru/scala3/book/types-opaque-types.md b/_ru/scala3/book/types-opaque-types.md new file mode 100644 index 0000000000..f6942738a9 --- /dev/null +++ b/_ru/scala3/book/types-opaque-types.md @@ -0,0 +1,178 @@ +--- +layout: multipage-overview +title: Непрозрачные типы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются непрозрачные типы в Scala 3. +language: ru +num: 55 +previous-page: types-variance +next-page: types-structural +versionSpecific: true +--- + +_Непрозрачные псевдонимы типов_ (_opaque type aliases_) обеспечивают абстракцию типов без каких-либо **накладных расходов**. + +В Scala 2 аналогичный результат можно получить с помощью [классов значений][value-classes]. + +## Накладные расходы на абстракцию + +Предположим, что необходимо определить модуль, +предлагающий арифметические операции над числами, которые представлены их логарифмами. +Это может быть полезно для повышения точности, когда числовые значения очень большие или близкие к нулю. + +Поскольку важно отличать "обычные" `Double` от чисел, хранящихся в виде их логарифмов, введем класс `Logarithm`: + +```scala +class Logarithm(protected val underlying: Double): + def toDouble: Double = math.exp(underlying) + def + (that: Logarithm): Logarithm = + // здесь используется метод apply сопутствующего объекта + Logarithm(this.toDouble + that.toDouble) + def * (that: Logarithm): Logarithm = + new Logarithm(this.underlying + that.underlying) + +object Logarithm: + def apply(d: Double): Logarithm = new Logarithm(math.log(d)) +``` + +Метод `apply` сопутствующего объекта позволяет создавать значения типа `Logarithm`, +которые можно использовать следующим образом: + +```scala +val l2 = Logarithm(2.0) +val l3 = Logarithm(3.0) +println((l2 * l3).toDouble) // выводит 6.0 +println((l2 + l3).toDouble) // выводит 4.999... +``` + +В то время как класс `Logarithm` предлагает хорошую абстракцию для значений `Double`, +которые хранятся в этой конкретной логарифмической форме, +это накладывает серьезные накладные расходы на производительность: +для каждой отдельной математической операции нужно извлекать значение `underlying`, +а затем снова обернуть его в новый экземпляр `Logarithm`. + +## Модульные абстракции + +Рассмотрим другой подход к реализации той же библиотеки. +На этот раз вместо того, чтобы определять `Logarithm` как класс, определяем его с помощью _псевдонима типа_. +Во-первых, зададим абстрактный интерфейс модуля: + +```scala +trait Logarithms: + + type Logarithm + + // операции на Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm + def mul(x: Logarithm, y: Logarithm): Logarithm + + // функции конвертации между Double и Logarithm + def make(d: Double): Logarithm + def extract(x: Logarithm): Double + + // методы расширения, для вызова `add` и `mul` в качестве "методов" на Logarithm + extension (x: Logarithm) + def toDouble: Double = extract(x) + def + (y: Logarithm): Logarithm = add(x, y) + def * (y: Logarithm): Logarithm = mul(x, y) +``` + +Теперь давайте реализуем этот абстрактный интерфейс, задав тип `Logarithm` равным `Double`: + +```scala +object LogarithmsImpl extends Logarithms: + + type Logarithm = Double + + // операции на Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble) + def mul(x: Logarithm, y: Logarithm): Logarithm = x + y + + // функции конвертации между Double и Logarithm + def make(d: Double): Logarithm = math.log(d) + def extract(x: Logarithm): Double = math.exp(x) +``` + +В рамках реализации `LogarithmsImpl` уравнение `Logarithm = Double` позволяет реализовать различные методы. + +#### Дырявые абстракции + +Однако эта абстракция немного "дырява". +Мы должны убедиться, что всегда программируем _только_ с абстрактным интерфейсом `Logarithms` +и никогда не используем `LogarithmsImpl` напрямую. +Прямое использование `LogarithmsImpl` сделало бы равенство `Logarithm = Double` видимым для пользователя, +который может случайно использовать `Double` там, где ожидается логарифмическое удвоение. +Например: + +```scala +import LogarithmsImpl.* +val l: Logarithm = make(1.0) +val d: Double = l // проверка типов ДОЗВОЛЯЕТ равенство! +``` + +Необходимость разделения модуля на абстрактный интерфейс и реализацию может быть полезной, +но также требует больших усилий, чтобы просто скрыть детали реализации `Logarithm`. +Программирование с использованием абстрактного модуля `Logarithms` может быть очень утомительным +и часто требует использования дополнительных функций, таких как типы, зависящие от пути, как в следующем примере: + +```scala +def someComputation(L: Logarithms)(init: L.Logarithm): L.Logarithm = ... +``` + +#### Накладные расходы упаковки/распаковки + +Абстракции типов, такие как `type Logarithm`, [стираются](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure) +в соответствии с их привязкой (`Any` - в нашем случае). +То есть, хотя нам не нужно вручную переносить и разворачивать значение `Double`, +все равно будут некоторые накладные расходы, связанные с упаковкой примитивного типа `Double`. + +## Непрозрачные типы + +Вместо того чтобы вручную разбивать компонент `Logarithms` на абстрактную часть и на конкретную реализацию, +можно просто использовать opaque типы для достижения аналогичного эффекта: + +```scala +object Logarithms: +//vvvvvv это важное различие! + opaque type Logarithm = Double + + object Logarithm: + def apply(d: Double): Logarithm = math.log(d) + + extension (x: Logarithm) + def toDouble: Double = math.exp(x) + def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) + def * (y: Logarithm): Logarithm = x + y +``` + +Тот факт, что `Logarithm` совпадает с `Double`, известен только в области, где он определен, +которая в приведенном выше примере соответствует объекту `Logarithms`. +Равенство `Logarithm = Double` может использоваться для реализации методов (например, `*` и `toDouble`). + +Однако вне модуля тип `Logarithm` полностью инкапсулирован или «непрозрачен». +Для пользователей `Logarithm`-а невозможно обнаружить, что `Logarithm` на самом деле реализован как `Double`: + +```scala +import Logarithms.* +val log2 = Logarithm(2.0) +val log3 = Logarithm(3.0) +println((log2 * log3).toDouble) // выводит 6.0 +println((log2 + log3).toDouble) // выводит 4.999... + +val d: Double = log2 // ERROR: Found Logarithm required Double +``` + +Несмотря на то, что мы абстрагировались от `Logarithm`, абстракция предоставляется бесплатно: +поскольку существует только одна реализация, во время выполнения не будет накладных расходов +на упаковку для примитивных типов, таких как `Double`. + +### Обзор непрозрачных типов + +Непрозрачные типы предлагают надежную абстракцию над деталями реализации, не накладывая расходов на производительность. +Как показано выше, непрозрачные типы удобны в использовании и очень хорошо интегрируются с [функцией методов расширения][extension]. + +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[value-classes]: {% link _overviews/core/value-classes.md %} diff --git a/_ru/scala3/book/types-others.md b/_ru/scala3/book/types-others.md new file mode 100644 index 0000000000..131bdd403b --- /dev/null +++ b/_ru/scala3/book/types-others.md @@ -0,0 +1,30 @@ +--- +layout: multipage-overview +title: Другие типы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе упоминаются другие расширенные типы в Scala 3. +language: ru +num: 58 +previous-page: types-dependent-function +next-page: ca-contextual-abstractions-intro +versionSpecific: true +--- + +В Scala есть несколько других расширенных типов, которые не показаны в этой книге, в том числе: + +- Лямбда-типы +- Типы соответствия +- Экзистенциальные типы +- Типы высшего порядка +- Синглтон-типы +- Типы уточнения +- Вид полиморфизма + +Дополнительные сведения об этих типах см. в [Справочной документации Scala 3][reference]. +Для singleton типов см. раздел [literal types](https://scala-lang.org/files/archive/spec/3.4/03-types.html#literal-types) +спецификации Scala 3, а для уточненных типов — раздел [refined types](https://scala-lang.org/files/archive/spec/3.4/03-types.html). + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_ru/scala3/book/types-structural.md b/_ru/scala3/book/types-structural.md new file mode 100644 index 0000000000..103e6db389 --- /dev/null +++ b/_ru/scala3/book/types-structural.md @@ -0,0 +1,120 @@ +--- +layout: multipage-overview +title: Структурные типы +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются структурные типы в Scala 3. +language: ru +num: 56 +previous-page: types-opaque-types +next-page: types-dependent-function +versionSpecific: true +--- + +_Scala 2 содержит более слабую форму структурных типов, основанную на Java reflection, +достигаемую с помощью `import scala.language.reflectiveCalls`_. + +## Введение + +Некоторые варианты использования, такие как моделирование доступа к базе данных, +более удобны в динамически типизированных языках, чем в статически типизированных языках. +С динамически типизированными языками естественно моделировать строку как запись или объект +и выбирать записи с помощью простых точечных обозначений, например `row.columnName`. + +Достижение того же результата в статически типизированном языке требует определения класса для каждой возможной строки, +возникающей в результате манипуляций с базой данных, включая строки, возникающие в результате `join` и проектирования, +и настройки схемы для сопоставления между строкой и представляющим ее классом. + +Это требует большого количества шаблонов, +что заставляет разработчиков менять преимущества статической типизации на более простые схемы, +в которых имена столбцов представляются в виде строк и передаются другим операторам, например `row.select("columnName")`. +Этот подход лишен преимуществ статической типизации и все еще не так естественен, как динамически типизируемая версия. + +Структурные типы (structural types) помогают в ситуациях, +когда желательно поддерживать простую точечную нотацию в динамических контекстах, не теряя преимуществ статической типизации. +Они также позволяют разработчикам настраивать, как должны определяться поля и методы. + +## Пример + +Вот пример структурного типа `Person`: + +```scala +class Record(elems: (String, Any)*) extends Selectable: + private val fields = elems.toMap + def selectDynamic(name: String): Any = fields(name) + +type Person = Record { + val name: String + val age: Int +} +``` + +Тип `Person` добавляет _уточнение_ (_refinement_) к своему родительскому типу `Record`, которое определяет поля `name` и `age`. +Говорится, что уточнение носит _структурный_ (_structural_) характер, +поскольку `name` и `age` не определены в родительском типе. +Но тем не менее они существуют как члены класса `Person`. +Например, следующая программа напечатала бы `"Emma is 42 years old."`: + +```scala +val person = Record( + "name" -> "Emma", + "age" -> 42 +).asInstanceOf[Person] + +println(s"${person.name} is ${person.age} years old.") +``` + +Родительский тип `Record` в этом примере представляет собой универсальный класс, +который может в своем аргументе `elems` принимать произвольные записи. +Этот аргумент - последовательность пар ключей типа `String` и значений типа `Any`. +Когда создается `Person` как `Record`, необходимо с помощью приведения типов задать, +что запись определяет правильные поля правильных типов. +Сама `Record` слишком слабо типизирована, поэтому компилятор не может знать об этом без помощи пользователя. +На практике связь между структурным типом и его базовым общим представлением, скорее всего, +будет выполняться на уровне базы данных и, следовательно, не будет беспокоить конечного пользователя. + +`Record` расширяет маркер `trait scala.Selectable` и определяет метод `selectDynamic`, +который сопоставляет имя поля с его значением. +Выбор элемента структурного типа выполняется путем вызова соответствующего метода. +`person.name` и `person.age` преобразуются компилятором Scala в: + +```scala +person.selectDynamic("name").asInstanceOf[String] +person.selectDynamic("age").asInstanceOf[Int] +``` + +## Второй пример + +Чтобы закрепить сказанное, вот еще один структурный тип с именем `Book`, представляющий книгу, доступную в базе данных: + +```scala +type Book = Record { + val title: String + val author: String + val year: Int + val rating: Double +} +``` + +Как и в случае с `Person`, экземпляр `Book` создается следующим образом: + +```scala +val book = Record( + "title" -> "The Catcher in the Rye", + "author" -> "J. D. Salinger", + "year" -> 1951, + "rating" -> 4.5 +).asInstanceOf[Book] +``` + +## Класс Selectable + +Помимо `selectDynamic` класс `Selectable` иногда также определяет метод `applyDynamic`, +который можно использовать для замены вызовов функций на вызов структурных элементов. +Таким образом, если `a` является экземпляром `Selectable`, структурный вызов типа `a.f(b, c)` преобразуется в: + +```scala +a.applyDynamic("f")(b, c) +``` diff --git a/_ru/scala3/book/types-union.md b/_ru/scala3/book/types-union.md new file mode 100644 index 0000000000..5c3a089488 --- /dev/null +++ b/_ru/scala3/book/types-union.md @@ -0,0 +1,110 @@ +--- +layout: multipage-overview +title: Объединение типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены объединение типов в Scala 3. +language: ru +num: 52 +previous-page: types-intersection +next-page: types-adts-gadts +versionSpecific: true +--- + +Используемый для типов `|` оператор создает так называемый _тип объединения_ (_union type_). +Тип `А | B` представляет значения, которые относятся **либо** к типу `A`, **либо** к типу `B`. + +В следующем примере метод `help` принимает параметр с именем `id` типа объединения `Username | Password`, +который может быть либо `Username`, либо `Password`: + +```scala +case class Username(name: String) +case class Password(hash: Hash) + +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // дальнейший код ... +``` + +Мы реализуем метод `help`, разделяя две альтернативы с использованием сопоставления с образцом. + +Этот код является гибким и типобезопасным решением. +Если попытаться передать тип, отличный от `Username` или `Password`, компилятор пометит это как ошибку: + +```scala +help("hi") // error: Found: ("hi" : String) + // Required: Username | Password +``` + +Ошибка также будет получена, если попытаться добавить `case` в выражение `match`, +которое не соответствует типам `Username` или `Password`: + +```scala +case 1.0 => ??? // Ошибка: это строка не компилируется +``` + +### Альтернатива объединенным типам + +Как показано, объединенные типы могут использоваться для представления вариантов нескольких разных типов, +не требуя, чтобы эти типы были частью специально созданной иерархии классов. + +#### Предварительное планирование иерархии классов + +Другие языки требуют предварительного планирования иерархии классов, как показано в следующем примере: + +```scala +trait UsernameOrPassword +case class Username(name: String) extends UsernameOrPassword +case class Password(hash: Hash) extends UsernameOrPassword +def help(id: UsernameOrPassword) = ... +``` + +Предварительное планирование не очень хорошо масштабируется, +поскольку, например, требования пользователей API могут быть непредсказуемыми. +Кроме того, загромождение иерархии типов маркерами типа `UsernameOrPassword` затрудняет чтение кода. + +#### Теговые объединения + +Другой альтернативой является задание отдельного типа перечисления, например: + +```scala +enum UsernameOrPassword: + case IsUsername(u: Username) + case IsPassword(p: Password) +``` + +Перечисление `UsernameOrPassword` представляет собой _помеченное_ (_tagged_) объединение `Username` и `Password`. +Однако этот способ моделирования объединения требует _явной упаковки и распаковки_, +и, например, `Username` **не** является подтипом `UsernameOrPassword`. + +### Вывод типов объединения + +Компилятор присваивает типу объединения выражение, _только если_ такой тип явно задан. +Например, рассмотрим такие значения: + +```scala +val name = Username("Eve") // name: Username = Username(Eve) +val password = Password(123) // password: Password = Password(123) +``` + +В этом REPL примере показано, +как можно использовать тип объединения при привязке переменной к результату выражения `if`/`else`: + +``` +scala> val a = if true then name else password +val a: Object = Username(Eve) + +scala> val b: Password | Username = if true then name else password +val b: Password | Username = Username(Eve) +``` + +Типом `a` является `Object`, который является супертипом `Username` и `Password`, +но не _наименьшим_ супертипом, `Password | Username`. +Если необходим наименьший супертип, его нужно указать явно, как это делается для `b`. + +> Типы объединения являются двойственными типам пересечения. +> И как `&` с типами пересечения, `|` также коммутативен: `A | B` того же типа, что и `B | А`. diff --git a/_ru/scala3/book/types-variance.md b/_ru/scala3/book/types-variance.md new file mode 100644 index 0000000000..fa2d409364 --- /dev/null +++ b/_ru/scala3/book/types-variance.md @@ -0,0 +1,283 @@ +--- +layout: multipage-overview +title: Вариантность +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлена и демонстрируется вариантность в Scala 3. +language: ru +num: 54 +previous-page: types-adts-gadts +next-page: types-opaque-types +--- + +_Вариантность_ (_variance_) параметра типа управляет подтипом параметризованных типов (таких, как классы или трейты). + +Чтобы объяснить вариантность, давайте рассмотрим следующие определения типов: + +{% tabs types-variance-1 %} +{% tab 'Scala 2 и 3' %} + +```scala +trait Item { def productNumber: String } +trait Buyable extends Item { def price: Int } +trait Book extends Buyable { def isbn: String } + +``` + +{% endtab %} +{% endtabs %} + +Предположим также следующие параметризованные типы: + +{% tabs types-variance-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-2 %} + +```scala +// пример инвариантного типа +trait Pipeline[T] { + def process(t: T): T +} + +// пример ковариантного типа +trait Producer[+T] { + def make: T +} + +// пример контрвариантного типа +trait Consumer[-T] { + def take(t: T): Unit +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-2 %} + +```scala +// пример инвариантного типа +trait Pipeline[T]: + def process(t: T): T + +// пример ковариантного типа +trait Producer[+T]: + def make: T + +// пример контрвариантного типа +trait Consumer[-T]: + def take(t: T): Unit +``` + +{% endtab %} +{% endtabs %} + +В целом существует три режима вариантности (variance): + +- **инвариант** (invariant) — значение по умолчанию, написанное как `Pipeline[T]` +- **ковариантный** (covariant) — помечен знаком `+`, например `Producer[+T]` +- **контравариантный** (contravariant) — помечен знаком `-`, как в `Consumer[-T]` + +Подробнее рассмотрим, что означает и как используется эта аннотация. + +### Инвариантные типы + +По умолчанию такие типы, как `Pipeline`, инвариантны в своем аргументе типа (в данном случае `T`). +Это означает, что такие типы, как `Pipeline[Item]`, `Pipeline[Buyable]` и `Pipeline[Book]`, _не являются подтипами_ друг друга. + +И это правильно! Предположим, что следующий метод использует два значения (`b1`, `b2`) типа `Pipeline[Buyable]` +и передает свой аргумент `b` методу `process` при его вызове на `b1` и `b2`: + +{% tabs types-variance-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-3 %} + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = { + val b1 = p1.process(b) + val b2 = p2.process(b) + if (b1.price < b2.price) b1 else b2 + } +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-3 %} + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = + val b1 = p1.process(b) + val b2 = p2.process(b) + if b1.price < b2.price then b1 else b2 +``` + +{% endtab %} +{% endtabs %} + +Теперь вспомните, что у нас есть следующие _отношения подтипов_ между нашими типами: + +{% tabs types-variance-4 %} +{% tab 'Scala 2 и 3' %} + +```scala +Book <: Buyable <: Item +``` + +{% endtab %} +{% endtabs %} + +Мы не можем передать `Pipeline[Book]` методу `oneOf`, +потому что в реализации `oneOf` мы вызываем `p1` и `p2` со значением типа `Buyable`. +`Pipeline[Book]` ожидает `Book`, что потенциально может вызвать ошибку времени выполнения. + +Мы не можем передать `Pipeline[Item]`, потому что вызов `process` обещает вернуть `Item`; +однако мы должны вернуть `Buyable`. + +#### Почему Инвариант? + +На самом деле тип `Pipeline` должен быть инвариантным, +так как он использует свой параметр типа `T` _и в качестве_ аргумента, _и в качестве_ типа возвращаемого значения. +По той же причине некоторые типы в библиотеке коллекций Scala, такие как `Array` или `Set`, также являются _инвариантными_. + +### Ковариантные типы + +В отличие от `Pipeline`, который является инвариантным, +тип `Producer` помечается как **ковариантный** (covariant) путем добавления к параметру типа префикса `+`. +Это допустимо, так как параметр типа используется только в качестве типа _возвращаемого_ значения. + +Пометка типа как ковариантного означает, что мы можем передать (или вернуть) `Producer[Book]` там, +где ожидается `Producer[Buyable]`. И на самом деле, это разумно. +Тип `Producer[Buyable].make` только обещает _вернуть_ `Buyable`. +Но для пользователей `make`, так же допустимо принять `Book`, который является подтипом `Buyable`, +то есть это _по крайней мере_ `Buyable`. + +Это иллюстрируется следующим примером, где функция `makeTwo` ожидает `Producer[Buyable]`: + +{% tabs types-variance-5 %} +{% tab 'Scala 2 и 3' %} + +```scala +def makeTwo(p: Producer[Buyable]): Int = + p.make.price + p.make.price +``` + +{% endtab %} +{% endtabs %} + +Допустимо передать в `makeTwo` производителя книг: + +{% tabs types-variance-6 %} +{% tab 'Scala 2 и 3' %} + +```scala +val bookProducer: Producer[Book] = ??? +makeTwo(bookProducer) +``` + +{% endtab %} +{% endtabs %} + +Вызов `price` в рамках `makeTwo` по-прежнему действителен и для `Book`. + +#### Ковариантные типы для неизменяемых контейнеров + +Ковариантность чаще всего встречается при работе с неизменяемыми контейнерами, такими как `List`, `Seq`, `Vector` и т.д. + +Например, `List` и `Vector` определяются приблизительно так: + +{% tabs types-variance-7 %} +{% tab 'Scala 2 и 3' %} + +```scala +class List[+A] ... +class Vector[+A] ... +``` + +{% endtab %} +{% endtabs %} + +Таким образом, можно использовать `List[Book]` там, где ожидается `List[Buyable]`. +Это также интуитивно имеет смысл: если ожидается коллекция вещей, которые можно купить, +то вполне допустимо получить коллекцию книг. +В примере выше у книг есть дополнительный метод `isbn`, но дополнительные возможности можно игнорировать. + +### Контравариантные типы + +В отличие от типа `Producer`, который помечен как ковариантный, +тип `Consumer` помечен как **контравариантный** (contravariant) путем добавления к параметру типа префикса `-`. +Это допустимо, так как параметр типа используется только _в позиции аргумента_. + +Пометка его как контравариантного означает, что можно передать (или вернуть) `Consumer[Item]` там, +где ожидается `Consumer[Buyable]`. +То есть у нас есть отношение подтипа `Consumer[Item] <: Consumer[Buyable]`. +Помните, что для типа `Producer` все было наоборот, и у нас был `Producer[Buyable] <: Producer[Item]`. + +И в самом деле, это разумно. Метод `Consumer[Item].take` принимает `Item`. +Как вызывающий `take`, мы также можем предоставить `Buyable`, который будет с радостью принят `Consumer[Item]`, +поскольку `Buyable` — это подтип `Item`, то есть, _по крайней мере_, `Item`. + +#### Контравариантные типы для потребителей + +Контравариантные типы встречаются гораздо реже, чем ковариантные типы. +Как и в нашем примере, вы можете думать о них как о «потребителях». +Наиболее важным типом, помеченным как контравариантный, с которым можно столкнуться, является тип функций: + +{% tabs types-variance-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-8 %} + +```scala +trait Function[-A, +B] { + def apply(a: A): B +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-8 %} + +```scala +trait Function[-A, +B]: + def apply(a: A): B +``` + +{% endtab %} +{% endtabs %} + +Тип аргумента `A` помечен как контравариантный `A` — он использует значения типа `A`. +Тип результата `B`, напротив, помечен как ковариантный — он создает значения типа `B`. + +Вот несколько примеров, иллюстрирующих отношения подтипов, вызванные аннотациями вариантности функций: + +{% tabs types-variance-9 %} +{% tab 'Scala 2 и 3' %} + +```scala +val f: Function[Buyable, Buyable] = b => b + +// OK - допустимо вернуть Buyable там, где ожидается Item +val g: Function[Buyable, Item] = f + +// OK - допустимо передать аргумент Book туда, где ожидается Buyable +val h: Function[Book, Buyable] = f +``` + +{% endtab %} +{% endtabs %} + +## Резюме + +В этом разделе были рассмотрены три различных вида вариантности: + +- **Producers** обычно ковариантны и помечают свой параметр типа со знаком `+`. + Это справедливо и для неизменяемых коллекций. +- **Consumers** обычно контравариантны и помечают свой параметр типа со знаком `-`. +- Типы, которые являются **одновременно** производителями и потребителями, + должны быть инвариантными и не требуют какой-либо маркировки для параметра своего типа. + В эту категорию, в частности, попадают изменяемые коллекции, такие как `Array`. diff --git a/_ru/scala3/book/why-scala-3.md b/_ru/scala3/book/why-scala-3.md new file mode 100644 index 0000000000..6f2d7eb1a1 --- /dev/null +++ b/_ru/scala3/book/why-scala-3.md @@ -0,0 +1,492 @@ +--- +layout: multipage-overview +title: Почему Scala 3? +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице описаны преимущества языка программирования Scala 3. +language: ru +num: 3 +previous-page: scala-features +next-page: taste-intro +--- + +Использование Scala, и Scala 3 в частности, дает много преимуществ. +Трудно перечислить их все, но “топ десять” может выглядеть так: + +1. Scala сочетает в себе функциональное программирование (ФП) и объектно-ориентированное программирование (ООП) +2. Scala статически типизирован, но часто ощущается как язык с динамической типизацией +3. Синтаксис Scala лаконичен, но все же удобочитаем; его часто называют _выразительным_ +4. _Implicits_ в Scala 2 были определяющей функцией, а в Scala 3 они были улучшены и упрощены +5. Scala легко интегрируется с Java, поэтому вы можете создавать проекты со смешанным кодом Scala и Java, а код Scala легко использует тысячи существующих библиотек Java +6. Scala можно использовать на сервере, а также в браузере со [Scala.js](https://www.scala-js.org) +7. Стандартная библиотека Scala содержит десятки готовых функциональных методов, позволяющих сэкономить ваше время и значительно сократить потребность в написании пользовательских циклов `for` и алгоритмов +8. “Best practices”, встроенные в Scala, поддерживают неизменность, анонимные функции, функции высшего порядка, сопоставление с образцом, классы, которые не могут быть расширены по умолчанию, и многое другое +9. Экосистема Scala предлагает самые современные ФП библиотеки в мире +10. Сильная система типов + + +## 1) Слияние ФП/ООП + +Больше, чем любой другой язык, Scala поддерживает слияние парадигм ФП и ООП. +Как заявил Мартин Одерски, сущность Scala — это слияние функционального и объектно-ориентированного программирования в типизированной среде, где: + +- Функции для логики +- Объекты для модульности + +Возможно, одними из лучших примеров модульности являются классы стандартной библиотеки. +Например, `List` определяется как класс---технически это абстрактный класс---и новый экземпляр создается следующим образом: + +{% tabs list %} +{% tab 'Scala 2 и 3' for=list %} +```scala +val x = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +Однако то, что кажется программисту простым `List`, на самом деле построено из комбинации нескольких специализированных типов, +включая трейты с именами `Iterable`, `Seq` и `LinearSeq`. +Эти типы также состоят из других небольших модульных единиц кода. + +В дополнение к построению типа наподобие `List` из серии модульных трейтов, +`List` API также состоит из десятков других методов, многие из которых являются функциями высшего порядка: + +{% tabs list-methods %} +{% tab 'Scala 2 и 3' for=list-methods %} +```scala +val xs = List(1, 2, 3, 4, 5) + +xs.map(_ + 1) // List(2, 3, 4, 5, 6) +xs.filter(_ < 3) // List(1, 2) +xs.find(_ > 3) // Some(4) +xs.takeWhile(_ < 3) // List(1, 2) +``` +{% endtab %} +{% endtabs %} + +В этих примерах значения в списке не могут быть изменены. +Класс `List` неизменяем, поэтому все эти методы возвращают новые значения, как показано в каждом комментарии. + +## 2) Ощущение динамики + +_Вывод типов_ (_type inference_) в Scala часто заставляет язык чувствовать себя динамически типизированным, даже если он статически типизирован. +Это верно для объявления переменной: + +{% tabs dynamic %} +{% tab 'Scala 2 и 3' for=dynamic %} +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3,4,5) +val stuff = ("fish", 42, 1_234.5) +``` +{% endtab %} +{% endtabs %} + +Это также верно при передаче анонимных функций функциям высшего порядка: + +{% tabs dynamic-hof %} +{% tab 'Scala 2 и 3' for=dynamic-hof %} +```scala +list.filter(_ < 4) +list.map(_ * 2) +list.filter(_ < 4) + .map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +и при определении методов: + +{% tabs dynamic-method %} +{% tab 'Scala 2 и 3' for=dynamic-method %} +```scala +def add(a: Int, b: Int) = a + b +``` +{% endtab %} +{% endtabs %} + +Это как никогда верно для Scala 3, например, при использовании [типов объединения][union-types]: + +{% tabs union %} +{% tab 'Только в Scala 3' for=union %} +```scala +// параметр типа объединения +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // дальнейший код ... + +// значение типа объединения +val b: Password | Username = if (true) name else password +``` +{% endtab %} +{% endtabs %} + +## 3) Лаконичный синтаксис + +Scala — это неформальный, “краткий, но все же читабельный“ язык. Например, объявление переменной лаконично: + +{% tabs concise %} +{% tab 'Scala 2 и 3' for=concise %} +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3) +``` +{% endtab %} +{% endtabs %} + +Создание типов, таких как трейты, классы и перечисления, является кратким: + +{% tabs enum %} +{% tab 'Только в Scala 3' for=enum %} +```scala +trait Tail: + def wagTail(): Unit + def stopTail(): Unit + +enum Topping: + case Cheese, Pepperoni, Sausage, Mushrooms, Onions + +class Dog extends Animal, Tail, Legs, RubberyNose + +case class Person( + firstName: String, + lastName: String, + age: Int +) +``` +{% endtab %} +{% endtabs %} + +Функции высшего порядка кратки: + +{% tabs list-hof %} +{% tab 'Scala 2 и 3' for=list-hof %} + +```scala +list.filter(_ < 4) +list.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +Все эти и многие другие выражения кратки и при этом очень удобочитаемы: то, что мы называем _выразительным_ (_expressive_). + +## 4) Implicits, упрощение + +Implicits в Scala 2 были главной отличительной особенностью дизайна. +Они представляли собой фундаментальный способ абстрагирования от контекста с единой парадигмой, +обслуживающей множество вариантов использования, среди которых: + +- Реализация [типовых классов]({% link _overviews/scala3-book/ca-type-classes.md %}) +- Установление контекста +- Внедрение зависимости +- Выражение возможностей + +С тех пор другие языки внедрили аналогичные концепции, все из которых являются вариантами основной идеи _вывода терминов_: +при заданном типе компилятор синтезирует “канонический” термин этого типа. + +Хотя implicits были определяющей функцией в Scala 2, их дизайн был значительно улучшен в Scala 3: + +- Есть единственный способ определить значения “given” +- Есть единственный способ ввести неявные параметры и аргументы +- Есть отдельный способ импорта givens, который не позволяет им потеряться в море обычного импорта +- Существует единственный способ определить неявное преобразование, которое четко обозначено как таковое и не требует специального синтаксиса + +К преимуществам этих изменений относятся: + +- Новый дизайн позволяет избежать взаимодействия функциональностей и делает язык более согласованным +- Это делает implicits более простыми для изучения и более сложными для злоупотребления +- Это значительно улучшает ясность 95% программ Scala, использующих implicits +- У него есть потенциал, чтобы сделать вывод терминов принципиальным способом, который также доступен и удобен + +Эти возможности подробно расписаны в соответствующих разделах, таких как [введение в контекстную абстракцию][contextual], а также раздел о [`given` и предложениях `using`][given] для получения более подробной информации. + +## 5) Полная интеграция с Java + +Взаимодействие между Scala и Java не вызывает затруднений во многих ситуациях. +Например: + +- Вы можете использовать все тысячи библиотек Java, доступных в ваших проектах Scala +- Scala `String` — это, по сути, Java `String` с дополнительными возможностями +- Scala легко использует классы даты/времени из Java пакета *java.time._* + +Вы также можете использовать классы коллекций Java в Scala, а для придания им большей функциональности Scala включает методы, +позволяющие преобразовывать их в коллекции Scala. + +Несмотря на то, что почти каждое взаимодействие является бесшовным, +в [главе “Взаимодействие с Java”][java] показано, как лучше использовать некоторые функции вместе, +в том числе как использовать: + +- Коллекции Java в Scala +- Java `Optional` в Scala +- Интерфейсы Java в Scala +- Коллекции Scala в Java +- Scala `Option` в Java +- Scala traits в Java +- Методы Scala, вызывающие исключения в Java коде +- Scala varargs параметры в Java + +Подробнее об этих функциях см. в этой главе. + +## 6) Клиент & сервер + +Scala можно использовать на стороне сервера с потрясающими фреймворками: + +- [Play Framework](https://www.playframework.com) позволяет создавать масштабируемые серверные приложения и микросервисы +- [Akka Actors](https://akka.io) позволяет использовать модель акторов для значительного упрощения распределенных и параллельных программных приложений + +Scala также можно использовать в браузере с [проектом Scala.js](https://www.scala-js.org), который является безопасной заменой JavaScript. +В экосистеме Scala.js есть [десятки библиотек](https://www.scala-js.org/libraries), позволяющих использовать React, Angular, jQuery +и многие другие библиотеки JavaScript и Scala в браузере. + +В дополнение к этим инструментам проект [Scala Native](https://github.com/scala-native/scala-native) +“представляет собой оптимизирующий опережающий компилятор и облегченную управляемую среду выполнения, разработанную специально для Scala”. +Он позволяет создавать бинарные исполняемые приложения в “системном” стиле с помощью простого кода Scala, а также позволяет использовать низкоуровневые примитивы. + +## 7) Стандартные библиотечные методы + +Вам довольно редко понадобится писать пользовательский цикл `for`, +потому что десятки готовых функциональных методов в стандартной библиотеке Scala сэкономят ваше время +и помогут сделать код более согласованным в разных приложениях. + +В следующих примерах показаны некоторые из встроенных методов коллекций, а также многие другие. +Хотя все они используют класс `List`, одни и те же методы работают с другими классами коллекций, +такими как `Seq`, `Vector`, `LazyList`, `Set`, `Map`, `Array` и `ArrayBuffer`. + +Вот некоторые примеры: + +{% tabs list-more %} +{% tab 'Scala 2 и 3' for=list-more %} +```scala +List.range(1, 3) // List(1, 2) +List.range(start = 1, end = 6, step = 2) // List(1, 3, 5) +List.fill(3)("foo") // List(foo, foo, foo) +List.tabulate(3)(n => n * n) // List(0, 1, 4) +List.tabulate(4)(n => n * n) // List(0, 1, 4, 9) + +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.filter(_ > 100) // List() +a.find(_ > 20) // Some(30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.map(_ * 2) // List(20, 40, 60, 80, 20) +a.slice(2, 4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +a.takeWhile(_ < 30) // List(10, 20) +a.filter(_ < 30).map(_ * 10) // List(100, 200, 100) + +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(10, 5, 8, 1, 7) +nums.sorted // List(1, 5, 7, 8, 10) +nums.sortWith(_ < _) // List(1, 5, 7, 8, 10) +nums.sortWith(_ > _) // List(10, 8, 7, 5, 1) +``` +{% endtab %} +{% endtabs %} + +## 8) Встроенные "best practices" + +Идиомы Scala поощряют "best practices" во многих ситуациях. +Для неизменяемости рекомендуется создавать неизменяемые val переменные: + +{% tabs val %} +{% tab 'Scala 2 и 3' for=val %} +```scala +val a = 1 // неизменяемая переменная +``` +{% endtab %} +{% endtabs %} + +Вам также рекомендуется использовать неизменяемые классы коллекций, такие как `List` и `Map`: + +{% tabs list-map %} +{% tab 'Scala 2 и 3' for=list-map %} +```scala +val b = List(1,2,3) // List неизменяем +val c = Map(1 -> "one") // Map неизменяема +``` +{% endtab %} +{% endtabs %} + +Case классы в первую очередь предназначены для использования в [моделировании предметной области]({% link _overviews/scala3-book/domain-modeling-intro.md %}), и их параметры также неизменяемы: + +{% tabs case-class %} +{% tab 'Scala 2 и 3' for=case-class %} +```scala +case class Person(name: String) +val p = Person("Michael Scott") +p.name // Michael Scott +p.name = "Joe" // compiler error (переназначение val name) +``` +{% endtab %} +{% endtabs %} + +Как показано в предыдущем разделе, классы коллекций Scala поддерживают функции высшего порядка, +и вы можете передавать в них методы (не показаны) и анонимные функции: + +{% tabs higher-order %} +{% tab 'Scala 2 и 3' for=higher-order %} +```scala +a.dropWhile(_ < 25) +a.filter(_ < 25) +a.takeWhile(_ < 30) +a.filter(_ < 30).map(_ * 10) +nums.sortWith(_ < _) +nums.sortWith(_ > _) +``` +{% endtab %} +{% endtabs %} + +Выражения `match` позволяют использовать сопоставление с образцом, и они действительно являются _выражениями_, которые возвращают значения: + +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' for=match %} +```scala +val numAsString = i match { + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +} +``` +{% endtab %} + +{% tab 'Scala 3' for=match %} +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` +{% endtab %} +{% endtabs %} + +Поскольку они могут возвращать значения, их часто используют в качестве тела метода: + +{% tabs match-body class=tabs-scala-version %} +{% tab 'Scala 2' for=match-body %} +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" => false + case _ => true +} +``` +{% endtab %} + +{% tab 'Scala 3' for=match-body %} +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true +``` +{% endtab %} +{% endtabs %} + +## 9) Библиотеки экосистемы + +Библиотеки Scala для функционального программирования, такие как [Cats](https://typelevel.org/cats) и [Zio](https://zio.dev), +являются передовыми библиотеками в сообществе ФП. +Об этих библиотеках можно сказать все модные словечки, такие как высокопроизводительная, типобезопасная, параллельная, асинхронная, ресурсобезопасная, тестируемая, функциональная, модульная, бинарно-совместимая, эффективная, эффектная и т.д. + +Мы могли бы перечислить здесь сотни библиотек, но, к счастью, все они перечислены в другом месте: подробности см. в списке [“Awesome Scala”](https://github.com/lauris/awesome-scala). + +## 10) Сильная система типов + +В Scala есть сильная система типов, и она была еще больше улучшена в Scala 3. +Цели Scala 3 были определены на раннем этапе, и к ним относятся: + +- Упрощение +- Устранение несоответствий +- Безопасность +- Эргономика +- Производительность + +_Упрощение_ достигается за счет десятков измененных и удаленных функций. +Например, изменения перегруженного ключевого слова `implicit` в Scala 2 на термины `given` и `using` в Scala 3 делает язык более понятным, особенно для начинающих разработчиков. + +_Устранение несоответствий_ связано с десятками [удаленных функций][dropped], [измененных функций][changed], и [добавленных функций][added] в Scala 3. +Некоторые из наиболее важных функций в этой категории: + +- Типы пересечения +- Типы объединения +- Неявные функциональные типы +- Зависимые функциональные типы +- Параметры трейтов +- Generic кортежи + +_Безопасность_ связана с несколькими новыми и измененными функциями: + +- Мультиверсальное равенство +- Ограничение неявных преобразований +- Null безопасность +- Безопасная инициализация + +Хорошими примерами _эргономики_ являются перечисления и методы расширения, +добавленные в Scala 3 довольно удобочитаемым образом: + +{% tabs extension %} +{% tab 'Только в Scala 3' for=extension %} +```scala +// перечисления +enum Color: + case Red, Green, Blue + +// методы расширения +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` +{% endtab %} +{% endtabs %} + +_Производительность_ относится к нескольким областям. +Одним из них являются [непрозрачные типы][opaque-types]. +В Scala 2 было несколько попыток создать решения, соответствующие практике проектирования, управляемого предметной областью (DDD), +когда значениям присваивались более осмысленные типы. +Эти попытки включали: + +- Псевдонимы типов +- Классы значений +- Case классы + +К сожалению, у всех этих подходов были недостатки, как описано в [SIP непрозрачных типов](https://docs.scala-lang.org/sips/opaque-types.html). +И наоборот, цель непрозрачных типов, как описано в этом SIP, заключается в том, что “операции с этими типами-оболочками не должны создавать дополнительных накладных расходов во время выполнения, но при этом обеспечивать безопасное использование типов во время компиляции”. + +Дополнительные сведения о системе типов см. в [справочной документации][reference]. + +## Другие замечательные функции + +Scala обладает множеством замечательных функций, и выбор Топ-10 может быть субъективным. +Несколько опросов показали, что разные группы разработчиков ценят разные функции. +Надеемся, вы откроете для себя больше замечательных возможностей Scala по мере использования языка. + +[java]: {% link _overviews/scala3-book/interacting-with-java.md %} +[given]: {% link _overviews/scala3-book/ca-context-parameters.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }} +[dropped]: {{ site.scala3ref }}/dropped-features +[changed]: {{ site.scala3ref }}/changed-features +[added]:{{ site.scala3ref }}/other-new-features + +[union-types]: {% link _overviews/scala3-book/types-union.md %} +[opaque-types]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_ru/scala3/contribute-to-docs.md b/_ru/scala3/contribute-to-docs.md new file mode 100644 index 0000000000..d01570ac6e --- /dev/null +++ b/_ru/scala3/contribute-to-docs.md @@ -0,0 +1,68 @@ +--- +layout: singlepage-overview +title: Вклад в документацию +partof: scala3-scaladoc +scala3: true +language: ru +--- + +## Обзор +В настоящее время предпринимается множество усилий по созданию высококачественной документации для Scala 3. +В частности, это следующие документы: + +- Книга Scala 3 +- Учебник по макросам +- Руководство по миграции +- Справочник по языку Scala 3 + +Мы приветствуем вклад сообщества в каждый аспект документации. + + +### Как я могу внести свой вклад? +В целом, есть много способов, которыми вы можете нам помочь: + +- **Запутались в чем-то в любом из документов?** Откройте issue. +- **Нашли что-то неактуальное?** Откройте issue или создайте PR. +- **Опечатки и другие мелкие улучшения текста?** Создайте PR. +- **Хотите добавить что-то новое или внести большие изменения?** Отлично! Пожалуйста, откройте issue и давайте обсудим это. + +Как правило, каждый из различных проектов документации содержит ссылки +(как и этот документ на панели оглавления — пока видимые только в desktop view) для их редактирования и улучшения. +Кроме того, ниже мы предоставим вам всю необходимую информацию для начала работы. + + +## Книга Scala 3 +[Книга Scala 3][scala3-book] написана Alvin Alexander и содержит обзор всех важных функций Scala 3. +Она предназначена для читателей, которые только знакомятся со Scala. + +- [Исходники](https://github.com/scala/docs.scala-lang/tree/main/_overviews/scala3-book) +- [Вопросы](https://github.com/scala/docs.scala-lang/issues) + +## Учебник по макросам +[Учебник по макросам](/scala3/guides/macros) написан Nicolas Stucki и содержит подробную информацию о макросах в Scala 3 и best-practices. + +- [Исходники](https://github.com/scala/docs.scala-lang/tree/main/_overviews/scala3-macros) +- [Вопросы](https://github.com/scala/docs.scala-lang/issues) + +## Руководство по миграции +[Руководство по миграции на Scala 3](/scala3/guides/migration/compatibility-intro.html) содержит исчерпывающий обзор +совместимости между Scala 2 и Scala 3, презентацию по инструментам миграции и подробные руководства по миграции. + +- [Исходники](https://github.com/scala/docs.scala-lang/tree/main/_overviews/scala3-migration) +- [Вопросы](https://github.com/scala/docs.scala-lang/issues) + +## Руководство по содействию в разработке Scala 3 +[Руководство по содействию в разработке Scala 3](/scala3/guides/contribution/contribution-intro.html) +содержит исчерпывающий обзор вклада в разработку и внутреннего устройства компилятора и библиотек Scala 3. + +- [Исходники](https://github.com/scala/docs.scala-lang/tree/main/_overviews/scala3-contribution) +- [Вопросы](https://github.com/scala/docs.scala-lang/issues) + +## Справочник по языку Scala 3 +[Справочник по Scala 3]({{ site.scala3ref }}) содержит формальное представление и подробную техническую информацию о различных возможностях языка. + +- [Исходники](https://github.com/scala/scala3/tree/main/docs/_docs) +- [Вопросы](https://github.com/scala/scala3/issues) + + +[scala3-book]: {% link _overviews/scala3-book/introduction.md %} diff --git a/_ru/scala3/guides/scaladoc/blog.md b/_ru/scala3/guides/scaladoc/blog.md new file mode 100644 index 0000000000..540a05bd03 --- /dev/null +++ b/_ru/scala3/guides/scaladoc/blog.md @@ -0,0 +1,87 @@ +--- +layout: multipage-overview +title: Встроенный блог +partof: scala3-scaladoc +language: ru +num: 5 +previous-page: static-site +next-page: site-versioning +--- + +Scaladoc позволяет включить в документацию простой блог. +На данный момент предоставляются только основные функции. +В будущем мы планируем включить более продвинутые функции, такие как теги или авторские страницы. + +К блогу относятся немного иначе, чем к обычным статическим сайтам. +Эта статья поможет вам создать свой собственный блог. + +## Правильная настройка каталога + +Статьи в блоге должны быть помещены в каталог `_blog/_posts`. + +``` +├── _blog +│ ├── _posts +│ │ └── 2016-12-05-implicit-function-types.md +│ └── index.html +``` + +Scaladoc загружает блог, если существует каталог `_blog`. + +## Соглашение об именовании + +Все имена файлов сообщений блога должны начинаться с даты в числовом формате, соответствующем `YYYY-MM-DD`. +Пример имени - `2022-06-17-dotty-compiler-bootstraps.md`. + +## Метаданные страницы + +Страницы блога в scaladoc поддерживают [Yaml Frontmatter](https://assemble.io/docs/YAML-front-matter.html), +что позволяет указывать различные значения, которые будут использоваться для метаданных на вашей странице. +Вот возможные поля: + +``` +--- +layout: <Ссылка на макет страницы для страницы блога> +author: <Автор страницы> +title: <Заголовок страницы> +subTitle: <Подзаголовок страницы> +date: <Дата создания страницы>, например, 2016-12-05 +authorImg: <Ссылка на картинку автора> +--- +<Содержимое страницы> +``` + +Вы также можете найти более подробную информацию о метаданных [на сайте документации Jekyll](https://jekyllrb.com/docs/front-matter/). + +## Синтаксис содержимого + +Имейте в виду, что для записи вашего блога необходимо использовать формат Markdown. +Более детальная информация о синтаксисе доступна в [Руководстве по Markdown](https://www.markdownguide.org/basic-syntax/). + +## Конфигурация блога + +Scaladoc позволяет настраивать блог, при его создании. + +Чтобы изменить настройки документации блога по умолчанию, +пользователям необходимо создать файл с именем `blog.yml` в **корневом каталоге блога**. +Этот файл должен содержать параметры, которые пользователь хочет изменить. +Например, если пользователь хочет изменить исходный каталог на "my_posts", +исходящий каталог на "my_docs" и временно скрыть блог, +то можно создать файл со следующим содержимым: + +``` +input: my_posts +output: my_docs +hidden: true +``` + +### Параметры: + +`input`: указывает каталог, содержащий markdown-файлы для постов блога (по умолчанию: "\_posts" в "docs"). + +`output`: указывает папку, в которой будут созданы HTML-страницы (по умолчанию: "blog" в "target/docs"). + +`hidden`: позволяет пользователям временно скрывать блог (по умолчанию: "false"). + +Чтобы изменить эти настройки, создайте файл с параметрами и сохраните его в корневом каталоге блога. +При следующей сборке блога будут использоваться новые параметры. diff --git a/_ru/scala3/guides/scaladoc/docstrings.md b/_ru/scala3/guides/scaladoc/docstrings.md new file mode 100644 index 0000000000..6ba32d082e --- /dev/null +++ b/_ru/scala3/guides/scaladoc/docstrings.md @@ -0,0 +1,222 @@ +--- +layout: multipage-overview +title: Docstrings - специфичные теги и особенности +partof: scala3-scaladoc +language: ru +num: 2 +previous-page: index +next-page: linking +--- + +В этой главе описывается, как правильно писать строки документации и как использовать все доступные функции scaladoc. +Так как многое осталось таким же, как и в старом scaladoc, некоторые детали взяты из этой +[статьи](https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html). + +Scaladoc расширяет возможности Markdown дополнительными функциями, такими как ссылки на определения API. +Это можно использовать в статической документации и постах в блогах для создания смешанного контента. + +## Куда поместить строки документации + +Комментарии Scaladoc идут перед элементами, к которым они относятся, в специальном блоке комментариев, +который начинается с `/**` и заканчивается `*/`, например: + +```scala +/** Комментарий начинается здесь. + * Левая "звезда", за которой следует пробел в каждой строке, + * позволяет продолжить комментарий. + * + * Даже на пустых строках разрыва абзаца. + * + * Обратите внимание, что '*' в каждой строке выровнена + * со вторым '*' в '/**' так, чтобы + * левое поле находилось в том же столбце, где + * первая строка и последующие. + * + * Комментарий закрывается с помощью '*' и обратного слэша. + * + * Если используются теги Scaladoc (@param, @group и т.д.), + * не забудьте поместить их в отдельные строки без предшествующих им строк. + * + * Например: + * + * Рассчитать квадрат заданного числа + * + * @param d the Double to square + * @return the result of squaring d + */ + def square(d: Double): Double = d * d +``` + +В приведенном выше примере этот комментарий Scaladoc связан с методом square, +поскольку он находится прямо перед ним в исходном коде. + +Комментарии Scaladoc могут идти перед полями, методами, классами, трейтами, объектами. +На данный момент scaladoc не поддерживает прямое решение для документирования пакетов. +На гитхабе есть специальный [issue](https://github.com/scala/scala3/issues/11284), где вы можете проверить текущий статус проблемы. + +Для первичных конструкторов класса, которые в Scala совпадают с определением самого класса, +тег @constructor используется для указания комментария, помещаемого в документацию первичных конструкторов, а не в обзор класса. + +## Теги + +Scaladoc использует теги `@`для предоставления определенных подробностей полей в комментариях. +Теги включают: + +### Теги, специфичные для класса + +- `@constructor` помещенный в комментарий класса, будет описывать первичный конструктор. + +### Теги, специфичные для метода + +- `@return` для детализации возвращаемого значения из метода (по одному на метод). + +### Теги метода, конструктора и/или класса + +- `@throws` какие исключения (если есть) может генерировать метод или конструктор. +- `@param` детализация параметра метода или конструктора, предоставляется по одному `@param` для каждого параметра метода/конструктора. +- `@tparam` детализация параметра типа для метода, конструктора или класса. Указывается по одному для каждого параметра типа. + +### Теги использования + +- `@see` ссылки на другие источники информации, такие как ссылки на внешние документы или связанные объекты в документации. +- `@note` добавление примечания о предварительных или последующих условиях или любых других заметных ограничениях или ожиданиях. +- `@example` предоставление примера кода или соответствующей документации. + + +### Теги группировки + +Эти теги хорошо подходят для больших типов или пакетов со многими элементами. +Они позволяют организовать страницу Scaladoc в отдельные разделы, каждый из которых отображается отдельно в выбранном порядке. + +Эти теги не включены по умолчанию! Необходимо передать флаг `-groups` в Scaladoc, чтобы включить их. +В sbt это обычно выглядит примерно так: + +```scala +Compile / doc / scalacOptions ++= Seq( + "-groups" +) +``` + +Каждый раздел должен иметь идентификатор из одного слова, который используется во всех этих тегах, как показано ниже в `group`. +По умолчанию этот идентификатор отображается как заголовок раздела документации, +но можно использовать `@groupname`, чтобы указать более длинный заголовок. + +Как правило, необходимо поместить `@groupprio` (и, возможно, `@groupname` и `@groupdesc`) в Scaladoc для самого пакета/трейта/класса/объекта, +описывая все группы и их порядок. Затем поместить `@group` в Scaladoc для каждого члена, указав, в какой группе он находится. + +Члены, у которых нет тега `@group`, будут перечислены в результирующей документации как “Ungrouped”. + +- `@group ` - пометить сущность как члена `` группы +- `@groupname ` - указание необязательного имени для группы. `` отображается как заголовок группы перед её описанием. +- `@groupdesc ` - добавление необязательного описания для отображения под именем группы. Поддерживает многострочный форматированный текст. +- `@groupprio ` - управление порядком группы на странице. По умолчанию 0. Несгруппированные элементы имеют неявный приоритет 1000. + Используются значения от 0 до 999, чтобы задать положение относительно других групп. Малые значения появятся перед большими значениями. + +### Другие теги + +- `@author` предоставление информации об авторе для следующего объекта. +- `@version` версия системы или API, частью которой является этот объект. +- `@since` похож на `@version`, но определяет систему или API, в котором эта сущность была впервые определена. +- `@deprecated` помечает объект как устаревший, предоставляя как замену реализации, которую следует использовать, + так и версию/дату, когда этот объект устарел. +- `@syntax ` позволяет изменить парсер для docstring. Синтаксис по умолчанию — markdown, + однако можно изменить его с помощью этой директивы. В настоящее время доступны синтаксисы `markdown` или `wiki`. + Пример использования: `@syntax wiki`. + +### Макросы + +- `@define ` позволяет использовать $name в других комментариях Scaladoc в том же исходном файле, который будет заменен на ``. + +Если комментарий не предоставляется для объекта на текущем уровне наследования, +но предоставляется для переопределенного объекта на более высоком уровне иерархии наследования, +будет использоваться комментарий из суперкласса. + +Аналогично, если `@param`, `@tparam`, `@return` и другие теги сущностей опущены, но доступны из суперкласса, будут использоваться из суперкласса. + +### Явный + +Для явного наследования комментариев используется тег `@inheritdoc`. + +### Разметка + +Scaladoc предоставляет два анализатора синтаксиса: `markdown` (по умолчанию) или `wikidoc`. +В Scaladoc по-прежнему можно встраивать теги HTML (как и в Javadoc), но в большинстве случаев это не обязательно, +поскольку вместо этого может использоваться разметка. + +#### Markdown + +Markdown использует [вариант commonmark](https://spec.commonmark.org/current/) с двумя пользовательскими расширениями: +- `wikidoc` ссылки для удобства +- `wikidoc`кодовые блоки с синтаксисом фигурных скобок + +#### Wikidoc + +Wikidoc — это синтаксис, используемый для scala2 scaladoc. +Он поддерживается из-за многих существующих исходников, однако **не** рекомендуется его использование в новых проектах. +Синтаксис вики можно включить с помощью глобального флага `-comment-syntax wiki` или с помощью `@syntax wiki` директивы в строке документации. + +Некоторые из стандартных доступных разметок: + +``` +`monospace` +''italic text'' +'''bold text''' +__underline__ +^superscript^ +,,subscript,, +[[entity link]], e.g. [[scala.collection.Seq]] +[[https://external.link External Link]], e.g. [[https://scala-lang.org Scala Language Site]] +``` + +Для получения дополнительной информации о вики-ссылках см. [эту главу](#связывание-с-api). + +Другие примечания по форматированию + +- Абзацы начинаются с одной (или нескольких) пустых строк. `*` на полях для комментария допустимо (и должно быть включено), + в противном случае строка должна оставаться пустой. +- Заголовки определяются окружающими символами `=` с большим количеством `=` для обозначения подзаголовков. + Например `=Heading=`, `==Sub-Heading==` и т.д. +- Блоки списка представляют собой последовательность элементов списка с одинаковым стилем и уровнем, + без прерываний от других стилей блоков. Неупорядоченные списки можно маркировать с помощью `-`, + нумерованные списки можно обозначать с помощью `1.`, `i.`, `I.` или `a.` для различных стилей нумерации. + В обоих случаях должно быть дополнительное пространство впереди, а большее пространство создает подуровень. + +Разметка для блоков списка выглядит так: + +``` +/** Вот неупорядоченный список: + * + * - Первый элемент + * - Второй элемент + * - Подпункт ко второму + * - Еще один подпункт + * - Третий пункт + * + * Вот упорядоченный список: + * + * 1. Первый пронумерованный элемент + * 1. Второй номер позиции + * i. Подпункт ко второму + * i. Еще один подпункт + * 1. Третий пункт + */ +``` + +### Общие примечания по написанию комментариев к Scaladoc + +Краткость - это хорошо! Быстро переходите к сути, у людей ограничено время, которое они могут потратить на вашу документацию, +используйте его с умом. Опустите ненужные слова. "Prefer возвращает X", а не "этот метод возвращает X", +и "X, Y и Z", а не "этот метод возвращает X, Y и Z". +Принцип DRY (_Don’t repeat yourself_) - не повторяйтесь. +Не дублируйте описание метода в теге `@return` и других формах повторяющихся комментариев. + +### Подробнее о написании Scaladoc + +Дополнительную информацию о рекомендациях по форматированию и стилю можно найти +в [руководстве по стилю Scala-lang scaladoc](https://docs.scala-lang.org/style/scaladoc.html). + +## Связывание с API + +Scaladoc позволяет ссылаться на документацию по API с помощью ссылок в стиле Wiki. +Связать с `scala.collection.immutable.List` так же просто, как указать `[[scala.collection.immutable.List]]`. +Для получения дополнительной информации о точном синтаксисе см. [ссылки в документации](/ru/scala3/guides/scaladoc/linking.html#определение-ссылок). diff --git a/_ru/scala3/guides/scaladoc/index.md b/_ru/scala3/guides/scaladoc/index.md new file mode 100644 index 0000000000..048272f371 --- /dev/null +++ b/_ru/scala3/guides/scaladoc/index.md @@ -0,0 +1,13 @@ +--- +layout: multipage-overview +title: Scaladoc +partof: scala3-scaladoc +language: ru +num: 1 +next-page: docstrings +--- + +![scaladoc logo]({{ site.baseurl }}/resources/images/scala3/scaladoc/logo.svg) + +Scaladoc — это инструмент для создания API документации ваших проектов Scala 3. +Он предоставляет функциональность, аналогичную `javadoc`, а также `jekyll` или `docusaurus`. diff --git a/_ru/scala3/guides/scaladoc/linking.md b/_ru/scala3/guides/scaladoc/linking.md new file mode 100644 index 0000000000..fd0670ba65 --- /dev/null +++ b/_ru/scala3/guides/scaladoc/linking.md @@ -0,0 +1,94 @@ +--- +layout: multipage-overview +title: Ссылки в документации +partof: scala3-scaladoc +language: ru +num: 3 +previous-page: docstrings +next-page: static-site +--- + +Основная функция Scaladoc — создание API документации из комментариев к коду. + +По умолчанию комментарии к коду понимаются как Markdown, хотя мы также поддерживаем старый Scaladoc синтаксис +[Wiki](https://docs.scala-lang.org/style/scaladoc.html). + +## Синтаксис + +### Определение ссылок + +Наш синтаксис определения ссылки очень близок к синтаксису Scaladoc, хотя мы внесли некоторые улучшения. + +#### Основной синтаксис + +Определение ссылки выглядит следующим образом: `[[scala.collection.immutable.List]]`. + +Другими словами, определение ссылки представляет собой последовательность идентификаторов, разделенных знаком `.`. +Идентификаторы также могут быть разделены с помощью `#`. + +По умолчанию идентификатор `id` ссылается на первую (в исходном порядке) сущность с именем `id`. +Идентификатор может заканчиваться на `$`, что заставляет его ссылаться на значение (объект, значение, given); +идентификатор также может заканчиваться на `!`, что заставляет его ссылаться на тип (класс, псевдоним типа, член типа). + +Ссылки рассчитываются относительно текущего местоположения в источнике. +То есть при документировании класса ссылки относятся к сущности, включающей класс (пакет, класс, объект); +то же самое относится к документированию определений. + +Специальные символы в ссылках могут быть экранированы обратной косой чертой, что вместо этого делает их частью идентификаторов. +Например, `` [[scala.collection.immutable\.List]] `` ссылается на класс `` `immutable.List` ``, указанный в package `scala.collection`. + +#### Новый синтаксис + +Определение ссылок Scaladoc было расширено, чтобы сделать их более удобными для записи и чтения в исходном коде. +Также целью было сблизить связь и синтаксис Scala. Новые функции: + +1. `package` может использоваться в качестве префикса для ссылки на прилагаемый пакет. + Пример: + ``` + package utils + class C { + def foo = "foo". + } + /** See also [[package.C]]. */ + class D { + def bar = "bar". + } + ``` + Ключевое слово `package` помогает сделать ссылки на прилагаемый пакет короче + и немного более устойчивым к рефакторингу имен. +1. `this` может использоваться в качестве префикса для ссылки на прилагаемый классоподобный пример: + ``` + class C { + def foo = "foo" + /** This is not [[this.foo]], this is bar. */ + def bar = "bar" + } + ``` + Использование здесь ключевого слова помогает сделать ссылки более привычными, + а также помогает ссылкам "пережить" изменения имени класса. +1. Обратные кавычки могут использоваться для экранирования идентификаторов. + Пример: + ``` + def `([.abusive.])` = ??? + /** TODO: Figure out what [[`([.abusive.])`]] is. */ + def foo = `([.abusive.])` + ``` + Ранее (в версиях 2.x) для ссылки на такие идентификаторы в Scaladoc требовалось экранирование обратной косой чертой. + Теперь (в версиях 3.x) Scaladoc позволяет использовать знакомую обратную кавычку Scala. + +#### Зачем сохранять синтаксис Wiki для ссылок? + +Есть несколько причин, по которым синтаксис Wiki сохранен для ссылок на документацию +вместо повторного использования синтаксиса Markdown. Это: + +1. Безымянные ссылки в Markdown уродливы: `[](definition)` против `[[definition]]` + Безусловно, большинство ссылок в документации безымянные. Должно быть очевидно, как их писать. +2. Поиск локального члена конфликтует с фрагментами URL: `[](#field)` против `[[#field]]` +3. Разрешение перегрузки противоречит синтаксису MD: `[](meth(Int))` против `[[meth(Int)]]` +4. Теперь, когда есть парсер для синтаксиса ссылок, можно разрешить пробелы внутри + (в Scaladoc нужно было экранировать их косой чертой), но это не распознается как ссылка в Markdown: + `[](meth(Int, Float))` против `[[meth(Int, Float)]]` + +Ни одна из этих причин не делает полностью невозможным использование стандартного синтаксиса ссылок Markdown, +но они делают его гораздо более неуклюжим и уродливым, чем нужно. +Кроме того, синтаксис ссылок Markdown даже не сохраняет никаких символов. diff --git a/_ru/scala3/guides/scaladoc/search-engine.md b/_ru/scala3/guides/scaladoc/search-engine.md new file mode 100644 index 0000000000..9ef88e0630 --- /dev/null +++ b/_ru/scala3/guides/scaladoc/search-engine.md @@ -0,0 +1,102 @@ +--- +layout: multipage-overview +title: Поиск по типу +partof: scala3-scaladoc +language: ru +num: 7 +previous-page: site-versioning +next-page: snippet-compiler +--- + +Поиск функций по их символическим именам может занять много времени. +Именно поэтому новый scaladoc позволяет искать методы и поля по их типам. + +Рассмотрим следующее определение метода расширения: +``` +extension [T](arr: IArray[T]) def span(p: T => Boolean): (IArray[T], IArray[T]) = ... +``` +Вместо поиска `span` также можно искать по `IArray[A] => (A => Boolean) => (IArray[A], IArray[A])`. + +Чтобы использовать эту функцию, введите сигнатуру искомого элемента в строке поиска scaladoc. +Вот как это работает: + +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/inkuire-1.0.0-M2_js_flatMap.gif) + +Эта функция предоставляется поисковой системой [Inkuire](https://github.com/VirtusLab/Inkuire), которая работает для Scala 3 и Kotlin. +Чтобы быть в курсе развития этой функции, следите за репозиторием [Inkuire](https://github.com/VirtusLab/Inkuire). + +## Примеры запросов + +Некоторые примеры запросов с предполагаемыми результатами: +- `List[Int] => (Int => Long) => List[Long]` -> `map` +- `Seq[A] => (A => B) => Seq[B]` -> `map` +- `(A, B) => A` -> `_1` +- `Set[Long] => Long => Boolean` -> `contains` +- `Int => Long => Int` -> `const` +- `String => Int => Char` -> `apply` +- `(Int & Float) => (String | Double)` -> `toDouble`, `toString` +- `F[A] => Int` -> `length` + +## Синтаксис запроса + +Для того чтобы запрос панели поиска scaladoc выполнялся с использованием Inkuire вместо поисковой системы по умолчанию, +запрос должен содержать последовательность символов `=>`. + +Принятый ввод аналогичен сигнатуре каррированной функции в Scala 3. С некоторыми отличиями: +- AndTypes, OrTypes и Functions должны быть заключены в круглые скобки, например, `(Int & Any) => String` +- поля и методы без параметров можно найти, указав перед их типом `=>`, например, `=> Int` +- Можно использовать подстановочный знак `_`, чтобы указать, что необходимо сопоставить любой тип в данном месте, + например, `Long => Double => _` +- Типы в виде одной буквы, например `A`, или буквы с цифрой `X1`, автоматически считаются переменными типа. +- Другие переменные типа могут быть объявлены так же, как и в полиморфных функциях, + например `[AVariable, AlsoAVariable] => AVariable => AlsoAVariable => AVariable` + +### Работа с псевдонимами типов и приемниками методов + +Когда дело доходит до того, как код сопоставляется с записями InkuireDb, есть некоторые преобразования, +чтобы сделать движок более самостоятельным (хотя и открытым для предложений и изменений). +Во-первых, получатель (не владелец модуля) функции может рассматриваться как первый аргумент. +Также применяется автоматическое каррирование, чтобы результаты не зависели от списков аргументов. +При поиске совпадений `val` и `def` не различаются. + +Итак, по запросу `Num => Int => Int => Int` должны быть найдены следующие объявления: +``` +class Num(): + def a(i: Int, j: Int): Int + def b(i: Int)(j: Int): Int + def c(i: Int): (Int => Int) + val d: Int => Int => Int + val e: Int => Int => Int + val f: (Int, Int) => Int +end Num + +def g(i: Num, j: Int, k: Int): Int +extension (i: Num) def h(j: Int, k: Int): Int +def i(i: Num, j: Int)(k: Int): Int +extension (i: Num) def j(j: Int)(k: Int): Int +... +``` + +Когда дело доходит до псевдонимов типов, они дешугаризуются как в объявлении, так и в подписи запроса. +Это означает, что для объявлений: +``` +type Name = String + +def fromName(name: Name): String +def fromString(str: String): Name +``` +оба метода `fromName` и `fromString`, должны быть найдены по запросам `Name => Name`, `String => String`, `Name => String` и `String => Name`. + +## Как это работает + +Inkuire работает как рабочий JavaScript в браузере благодаря мощи [ScalaJS](https://www.scala-js.org/). + +Чтобы включить Inkuire при запуске scaladoc, добавьте флаг `-Ygenerate-inkuire`. +При добавлении этого флага создаются два файла: +- `inkuire-db.json` - это файл, содержащий все доступные для поиска объявления из текущего документированного проекта в формате, + читаемом поисковой системой Inkuire. +- `inkuire-config.json` - этот файл содержит расположение файлов базы данных, + которые должны быть доступны для поиска в документации текущего проекта. + По умолчанию он будет сгенерирован с расположением локального файла базы данных, + а также с подразумеваемыми по умолчанию расположениями файлов базы данных во внешних сопоставлениях + [`-external-mappings`](/ru/scala3/guides/scaladoc/settings.html#-external-mappings). diff --git a/_ru/scala3/guides/scaladoc/settings.md b/_ru/scala3/guides/scaladoc/settings.md new file mode 100644 index 0000000000..942e88342b --- /dev/null +++ b/_ru/scala3/guides/scaladoc/settings.md @@ -0,0 +1,218 @@ +--- +layout: multipage-overview +title: Настройки +partof: scala3-scaladoc +language: ru +num: 9 +previous-page: snippet-compiler +--- + +В этой главе перечислены параметры конфигурации, которые можно использовать при вызове scaladoc. +Некоторую информацию, показанную здесь, можно получить, вызвав scaladoc с флагом `-help`. + +## Изменения scaladoc по сравнению со Scala 2 + +Scaladoc был переписан с нуля, и некоторые функции оказались бесполезными в новом контексте. +Текущее состояние совместимости со старыми флагами scaladoc можно увидеть [здесь](https://github.com/scala/scala3/issues/11907). + +## Указание настроек + +Настройки scaladoc можно указывать в качестве аргументов командной строки, +например, `scaladoc -d output -project my-project target/scala-3.0.0-RC2/classes`. +При вызове из sbt, обновите значение `Compile / doc / scalacOptions` и `Compile / doc / target` соответственно, например + +``` +Compile / doc / target := file("output"), +Compile / doc / scalacOptions ++= Seq("-project", "my-project"), +``` + +## Обзор всех доступных настроек + +##### -project + +Название проекта. Чтобы обеспечить совместимость с псевдонимами Scala2 с `-doc-title` + +##### -project-version + +Текущая версия проекта, которая отображается в верхнем левом углу. +Чтобы обеспечить совместимость с псевдонимами Scala2 с `-doc-version` + +##### -project-logo + +Логотип проекта, который появляется в верхнем левом углу. +Для темной темы можно выделить отдельный логотип с суффиксом `_dark`. +Например, если есть логотип `mylogo.png`, то для темной темы предполагается `mylogo_dark.png`. +Чтобы обеспечить совместимость с псевдонимами Scala2 с `-doc-logo` + +##### -project-footer + +Строковое сообщение, которое отображается в разделе нижнего колонтитула. +Чтобы обеспечить совместимость с псевдонимами Scala2 с `-doc-footer` + +##### -comment-syntax + +Язык стилей, используемый для разбора комментариев. +В настоящее время поддерживается два синтаксиса: `markdown` или `wiki`. +Если настройка отсутствует, по умолчанию - `markdown`. + +##### -revision + +Редакция (ветвь или ссылка), используемая для создания проекта. +Полезно с исходными ссылками, чтобы они не всегда указывали на последний `main`, который может быть изменен. + +##### -source-links + +Ссылки на источники обеспечивают сопоставление между файлом в документации и репозиторием кода. + +Примеры исходных ссылок: +`-source-links:docs=github://scala/scala3/main#docs` + +Принимаемые форматы: + +`=` + +где `` является одним из следующих: + +- `github:///[/revision][#subpath]` + будет соответствовать https://github.com/$organization/$repository/[blob|edit]/$revision[/$subpath]/$filePath[$lineNumber], + если редакция не указана, тогда требуется указать редакцию в качестве аргумента для Scaladoc +- `gitlab:///` + будет соответствовать https://gitlab.com/$organization/$repository/-/[blob|edit]/$revision[/$subpath]/$filePath[$lineNumber], + если редакция не указана, тогда требуется, чтобы редакция была указана как аргумент в Scaladoc +- `` + +`` — это формат параметра `doc-source-url` из старого scaladoc. +ПРИМЕЧАНИЕ. Поддерживаются только шаблоны `€{FILE_PATH_EXT}`, `€{TPL_NAME}`, `€{FILE_EXT}`, `€{FILE_PATH}` и `€{FILE_LINE}`. + +Шаблон может быть определен только подмножеством источников, определенных префиксом пути, представленным ``. +В этом случае пути, используемые в шаблонах, будут относительны к ``. + +##### -external-mappings + +Сопоставление регулярных выражений, соответствующих записям пути к классам, и внешней документации. + +Пример внешнего сопоставления: +`-external-mappings:.*scala.*::scaladoc3::https://scala-lang.org/api/3.x/,.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/` + +Отображение имеет вид `::[scaladoc3|scaladoc|javadoc]::`. +Можно указать несколько сопоставлений, разделенных запятыми, как показано в примере. + +##### -social-links + +Ссылки на социальные сети. Например: + +`-social-links:github::https://github.com/scala/scala3,discord::https://discord.com/invite/scala,twitter::https://x.com/scala_lang` + +Допустимые значения имеют вид: `[github|twitter|gitter|discord]::link`. +Scaladoc также поддерживает `custom::link::white_icon_name::black_icon_name`. +В этом случае иконки должны находиться в каталоге `images/`. + +##### -skip-by-id + +Идентификаторы пакетов или классов верхнего уровня, которые следует пропускать при создании документации. + +##### -skip-by-regex + +Регулярные выражения, соответствующие полным именам пакетов или классов верхнего уровня, +которые следует пропускать при создании документации. + +##### -doc-root-content + +Файл, из которого следует импортировать документацию корневого пакета. + +##### -author + +Добавление авторов в строку документации `@author Name Surname` по умолчанию не будет включено в сгенерированную html-документацию. +Если необходимо явно пометить классы авторами, scaladoc запускается с данным флагом. + +##### -groups + +Группировка похожих функций вместе (на основе аннотации `@group`) + +##### -private + +Показать все типы и члены. Если параметр не указан, показывать только `public` и `protected` типы и члены. + +##### -doc-canonical-base-url + +Базовый URL-адрес для использования в качестве префикса и добавления `canonical` URL-адресов на все страницы. +Канонический URL-адрес может использоваться поисковыми системами для выбора URL-адреса, +который вы хотите, чтобы люди видели в результатах поиска. +Если не установлено, канонические URL-адреса не генерируются. + +##### -siteroot + +Каталог, содержащий статические файлы, из которых создается документация. Каталог по умолчанию - `./docs` + +##### -no-link-warnings + +Подавить предупреждения для двусмысленных или невалидных ссылок. +Не влияет на предупреждения о некорректных ссылках ресурсов и т. д. + +##### -versions-dictionary-url + +URL-адрес, указывающий на документ JSON, содержащий словарь: `version label -> documentation location`. +Файл JSON имеет единственное поле `versions`, которое содержит словарь, +связывающий метки определенных версий документации с URL-адресами, указывающими на их `index.html`. +Полезно для библиотек, которые поддерживают разные версии документации. + +Пример JSON-файла: + +``` +{ + "versions": { + "3.0.x": "https://dotty.epfl.ch/3.0.x/docs/index.html", + "Nightly": "https://dotty.epfl.ch/docs/index.html" + } +} +``` + +##### -snippet-compiler + +Аргументы компилятора фрагментов, позволяющие настроить проверку типа сниппета. + +Этот параметр принимает список аргументов в формате: +`args := arg{,args} arg := [path=]flag` +, где `path` - префикс пути к исходным файлам, в которых находятся сниппеты, +и `flag` - режим проверки типов. + +Если `path` отсутствует, аргумент будет использоваться по умолчанию для всех несопоставленных путей. + +Доступные флаги: + +- `compile` — включает проверку фрагментов. +- `nocompile` — отключает проверку сниппетов. +- `fail` — включает проверку фрагмента, утверждает, что сниппет не компилируется. + +Флаг `fail` удобен для фрагментов, которые показывают, +что какое-то действие в конечном итоге завершится ошибкой во время компиляции. + +Пример использования: + +`-snippet-compiler:my/path/nc=nocompile,my/path/f=fail,compile` + +Что значит: + +- все фрагменты в файлах в каталоге `my/path/nc` вообще не должны рассматриваться снипеттом +- все фрагменты в файлах в каталоге `my/path/f` не должны компилироваться во время компиляции +- все остальные фрагменты должны компилироваться успешно + +#### -scastie-configuration + +Определите дополнительную sbt конфигурацию для Scastie фрагментов кода. +Например, когда вы импортируете внешние библиотеки в свои фрагменты, +необходимо добавить соответствующие зависимости. + +``` +"-scastie-configuration", """libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.12.0"""" +``` + +##### -Ysnippet-compiler-debug + +Установка этого параметра заставляет компилятор фрагментов печатать сниппет по мере его компиляции (после упаковки). + +##### -Ydocument-synthetic-types + +Включение в документацию страниц с документацией по встроенным типам (например, `Any`, `Nothing`). +Этот параметр полезен только для stdlib, поскольку scaladoc для Scala 3 использует файлы TASTy. +Все остальные пользователи не должны касаться этой настройки. diff --git a/_ru/scala3/guides/scaladoc/site-versioning.md b/_ru/scala3/guides/scaladoc/site-versioning.md new file mode 100644 index 0000000000..fac5266a7c --- /dev/null +++ b/_ru/scala3/guides/scaladoc/site-versioning.md @@ -0,0 +1,51 @@ +--- +layout: multipage-overview +title: Версионирование сайта +partof: scala3-scaladoc +language: ru +num: 6 +previous-page: blog +next-page: search-engine +--- + +Scaladoc предоставляет удобный способ переключения между различными версиями документации. +Эта функция полезна, когда желательно оставить старые версии документации пользователям, +которые ещё не перешли на новую версию библиотеки. + +### Как это настроить + +Эта функция была разработана для легкой масштабируемости без необходимости повторного создания всех scaladocs +после добавления новой версии. Для этого вводится новая настройка: `-versions-dictionary-url`. +Его аргумент должен быть URL-адресом документа JSON, содержащего информацию о расположении конкретных версий. +Файл JSON должен содержать свойство `versions` со словарём, +связывающий метки определенных версий документации с URL-адресами, указывающими на их `index.html`. + +Пример JSON-файла: +``` +{ + "versions": { + "3.0.x": "https://dotty.epfl.ch/3.0.x/docs/index.html", + "Nightly": "https://dotty.epfl.ch/docs/index.html" + } +} +``` + +Такие документы необходимо указывать для каждой из версий, однако позже это дает больше гибкости. +Если необходимо добавить версию документов API рядом с предыдущими 5 версиями, которые уже опубликованы, +нужно только загрузить новые документы на веб-сервер и добавить новую запись в файл JSON. +Все версии сайта теперь узнают о новой версии. + +Важно отметить, что существует только один файл JSON, чтобы избежать избыточности, +и каждый scaladoc должен заранее настроить свой URL-адрес, например, в sbt: + +``` +doc / scalacOptions ++= Seq("-versions-dictionary-url", "https://dotty.epfl.ch/versions.json") +``` + + +### Как это выглядит с точки зрения пользователя + +Предоставление файла JSON через `-versions-dictionary-url` позволяет scaladoc связывать версии. +Также удобно иметь возможность изменить метку ревизии в выпадающем меню. Все изменится автоматически. + +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/nightly.gif) diff --git a/_ru/scala3/guides/scaladoc/snippet-compiler.md b/_ru/scala3/guides/scaladoc/snippet-compiler.md new file mode 100644 index 0000000000..adb2c0932c --- /dev/null +++ b/_ru/scala3/guides/scaladoc/snippet-compiler.md @@ -0,0 +1,261 @@ +--- +layout: multipage-overview +title: Проверка фрагмента +partof: scala3-scaladoc +language: ru +num: 8 +previous-page: search-engine +next-page: settings +--- + +Основная функциональность документации — помочь пользователям понять и правильно использовать проект. +Иногда часть проекта нуждается в нескольких словах, чтобы показать ее использование, +но бывают моменты, когда описания недостаточно, и нет ничего лучше, чем подробный пример. + +Удобный способ предоставления примеров в документации — создание фрагментов кода, +представляющих использование заданной функциональности. Проблема фрагментов кода в том, +что одновременно с разработкой проекта их нужно обновлять. +Иногда изменения в одной части проекта могут нарушить работу примеров в других частях. +Количество фрагментов и количество времени, прошедшего с момента их написания, не позволяет запомнить каждое место, +где нужно их исправить. Через какое-то время наступает понимание, что документация — полный бардак +и нужно пройтись по всем примерам и переписать их. + +Многие проекты Scala 2 используют markdown документацию с проверкой типов с помощью [tut](https://tpolecat.github.io/tut/) +или [mdoc](https://scalameta.org/mdoc/). Почти все хотя бы слышали об этих инструментах. +Поскольку они оказались очень полезными и сообщество Scala их успешно приняло, +планируется включить функции tut и mdoc в компилятор, чтобы он был готов к включению в Scaladoc. + +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/snippet-compiler3.png) + +## Начало работы + +По умолчанию проверка фрагментов отключена. +Её можно включить, добавив в Scaladoc следующий аргумент: + +`-snippet-compiler:compile` + +Например, в sbt конфигурация выглядит так: + +```scala +Compile / doc / scalacOptions ++= Seq("-snippet-compiler:compile") +``` + +Эта опция включает компилятор фрагментов для всех scala фрагментов в проектной документации и распознает все фрагменты внутри ``` блоков scala. +В настоящее время проверка фрагментов работает как в строках документации, написанных в Markdown, так и на статических сайтах. +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/snippet-compiler4.png) + +Для нового проекта этой конфигурации должно хватить. +Однако, если вы переносите существующий проект, можно отключить компиляцию для некоторых фрагментов, +которые в настоящее время не могут быть обновлены. + +Для этого добавьте `nocompile` флаг прямо в scala фрагмент: + +```` +```scala sc:nocompile +// under the hood `map` is transformed into +List(1).map( _ + 1)() +``` +```` + +Однако иногда сбой компиляции является преднамеренным поведением, например, для демонстрации ошибки. +В этом случае выставляется флаг `fail`, который представляет одну из функций: [Assert compilation errors](#assert-compilation-errors). + +```` +```scala sc:fail +List(1,2,3).toMap +``` +```` + +Более подробное объяснение и более сложные настройки, такие как настройки флагов на основе пути, +см. в разделе ["Расширенная конфигурация"](#расширенная-конфигурация). + +## Обзор функций + +### Assert compilation errors + +Scala — это язык программирования со статической типизацией. +Иногда в документации должны упоминаться случаи, когда код не должен компилироваться, +или авторы хотят предоставить способы восстановления после определенных ошибок компиляции. + +Например, этот код: + +```scala +List(1,2,3).toMap +``` + +приводит к результату: + +```nohighlight + +At 18:21: + List(1,2,3).toMap +Error: Cannot prove that Int <:< (K, V) + +where: K is a type variable with constraint + V is a type variable with constraint +. +``` + +Примеры, представляющие код, который дает сбой во время компиляции, могут быть очень важными. +Например, можно показать, как библиотека защищена от неправильного кода. +Другой вариант использования — представить распространенные ошибки и способы их решения. +Принимая во внимание эти варианты использования, предоставляется функция проверки того, компилируются ли отмеченные фрагменты кода. + +Для фрагментов кода, которые намеренно не компилируются, например следующего, добавьте флаг `fail` во фрагмент кода: + +```` +```scala sc:fail +List(1,2,3).toMap +``` +```` +Проверка фрагмента проходит успешно и показывает ожидаемые ошибки компиляции в документации. +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/assert-compilation-errors.gif) + +Для фрагмента, который компилируется без ошибок: +```` +```scala sc:fail +List((1,2), (2,3)).toMap +``` +```` +результирующий вывод выглядит следующим образом: +```nohighlight + +In static site (./docs/docs/index.md): +Error: Snippet should not compile but compiled succesfully +``` + + +### Контекст + +В Scaladoc внедрён механизм переноса, предоставляющий контекст для каждого фрагмента. +Эта предварительная обработка выполняется автоматически для всех фрагментов в строках документации. + +Например, предположим, что необходимо задокументировать метод `slice` в файле `collection.List` для того, +чтобы объяснить, как он работает, сравнив его с комбинацией методов `drop` и `take`, используя такой фрагмент кода: +```scala +slice(2, 5) == drop(2).take(3) +``` +Показ этого примера — одна из первых вещей, которые приходят на ум, но он не скомпилируется без функции контекста. + +Помимо основной цели, это уменьшает шаблон фрагмента, потому что не нужно импортировать элементы одного и того же пакета +и создавать экземпляры документированного класса. + +Фрагмент кода после предварительной обработки выглядит так: +```scala +package scala.collection +trait Snippet[A] { self: List[A] => + slice(2,5) == drop(2).take(3) +} +``` + +### Скрытие кода + +Несмотря на наличие контекстной функции, описанной выше, иногда автору необходимо предоставить больше элементов +для области действия. Однако, с одной стороны, большой блок импортов и инициализаций необходимых классов +может привести к потере читабельности. Но с другой стороны, хотелось бы иметь возможность видеть весь код. +Для второго случая введен специальный синтаксис для фрагментов, +который скрывает определенные фрагменты `import` кода — операторы, например, — но также позволяет +расширить этот код в документации одним щелчком мыши. + +Пример: + +```scala +//{ +import scala.collection.immutable.List +//} +val intList: List[Int] = List(1, 2, 3) +``` + +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/hiding-code.gif) + +### Включенные фрагменты + +При написании фрагментов кода часто требуется механизм повторного использования кода из одного фрагмента в другом. +Например, взгляните на следующий фрагмент документации: +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/documentation-snippet.png) + +Чтобы успешно скомпилировать последний фрагмент, нужно иметь ранее объявленные определения в области видимости. +Для этого сценария — и, возможно, для многих других — добавлена новая функция: включение фрагмента. +Она позволяет повторно использовать код из одного фрагмента в другом, что снижает избыточность и повышает удобство сопровождения. + +Чтобы настроить это, добавьте аргумент `sc-name` к фрагменту, который необходимо включить в более поздний блок кода: +```` ```scala sc-name: ```` + +, где `snippet-name` должен быть уникальным в пределах файла и не может содержать пробелы и запятые. + +Затем в более позднем блоке кода в документации используйте аргумент `sc-compile-with` в scala фрагменте, +который должен “включать” предыдущий блок кода: +```` ```scala sc-compile-with:(,)+ ```` + +, где `snippet-name` - имя фрагмента, который должен быть включен. + +После настройки этой функции в примере код выглядит так: +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/documentation-snippet2.png) + +и вывод выглядит так: +![]({{ site.baseurl }}/resources/images/scala3/scaladoc/snippet-includes.png) + +Можно указать более одного включения. Обратите внимание, что порядок, в котором они указаны, определяет порядок включения. + +**Замечание**: можно включать только фрагменты, определенные над целевым фрагментом. + +## Расширенная конфигурация + +Часто включение проверки фрагментов для _всех_ фрагментов не является желаемым уровнем контроля, +поскольку варианты использования могут быть более сложными. Для таких ситуаций подготовлен инструмент, +чтобы пользователи могли настроить его под свои нужды. + +### Доступные флаги + +Чтобы обеспечить больший контроль, компилятор фрагмента предоставляет три флага, которые позволяют изменить его поведение: +- `compile` - включает проверку фрагментов +- `nocompile` - отключает проверку фрагментов +- `fail` - включает проверку фрагментов с подтверждением ошибки компиляции + +### Настройки на основе пути + +Для большей гибкости вместо установки одного флага для управления всеми фрагментами кода в проекте +его можно установить только для определенного пути, добавив префикс `=` перед флагом. Например: + +`-snippet-compiler:docs=compile` - устанавливает флаг `compile` для фрагментов в `docs`. + +Если `docs` - это каталог, флаг устанавливается для всех файлов внутри `docs`. + +Кроме того, `-snippet-compiler` может управляться более чем одним параметром, при этом параметры разделяются запятыми. +Например: +``` +-snippet-compiler:docs=compile,library/src=compile,library/src/scala/quoted=nocompile,library/src/scala/compiletime=fail +``` +Флаги выбираются по самому длинному совпадению префикса, поэтому можно определить общую настройку, +а затем изменить это поведение по умолчанию для более конкретных путей. +``` +-snippet-compiler:compile,library/src/scala/quoted=nocompile,library/src/scala/compiletime=fail +``` +Флаг без префикса пути, такой как флаг `compile` в этом примере, считается значением по умолчанию. + +### Переопределение прямо во фрагменте + +Аргументы CLI — хороший механизм для установки флагов для определенных файлов. +Однако этот подход нельзя использовать для настройки определенных фрагментов. +Допустим, необходимо написать один фрагмент кода, который должен потерпеть неудачу, +и другие фрагменты, которые должны скомпилироваться. +Эти аргументы находятся в информационной части блока кода: + +```` +```scala +// snippet +``` +```` + +Например, чтобы настроить проверку для определенного фрагмента, добавьте следующий аргумент в его информационную часть фрагмента, +где `flag` - один из доступных флагов, перечисленных выше (например, `compile`, `nocompile` или `fail`): + +`sc:` + +В качестве конкретного примера этот код показывает, как использовать флаг `fail` в отдельном фрагменте: + +```` +```scala sc:fail +val itShouldFail: Int = List(1.1, 2, 3).head +``` +```` diff --git a/_ru/scala3/guides/scaladoc/static-site.md b/_ru/scala3/guides/scaladoc/static-site.md new file mode 100644 index 0000000000..2249f88ffe --- /dev/null +++ b/_ru/scala3/guides/scaladoc/static-site.md @@ -0,0 +1,296 @@ +--- +layout: multipage-overview +title: Статическая документация +partof: scala3-scaladoc +language: ru +num: 4 +previous-page: linking +next-page: blog +--- + +Scaladoc умеет генерировать статические сайты, известные по [Jekyll](http://jekyllrb.com/) или [Docusaurus](https://docusaurus.io/). +Наличие комбинированного инструмента позволяет обеспечить взаимодействие между статической документацией +и API, что позволяет им естественным образом сочетаться. + +Создать сайт так же просто, как и в Jekyll. Корень сайта содержит макет сайта, +и все файлы, размещенные там, будут либо считаться статическими, либо обрабатываться для расширения шаблона. + +Файлы, которые рассматриваются для расширения шаблона, должны заканчиваться на `*.{html,md}` +и в дальнейшем будут называться "файлами шаблонов" или "шаблонами". + +Простой сайт "Hello World" может выглядеть примерно так: + +``` +. +└── / + └── _docs/ + ├── index.html + └── getting-started.html +``` + +Что даст сайт со следующими файлами в сгенерированной документации: + +``` +index.html +getting-started.html +``` + +Scaladoc может преобразовывать как файлы, так и каталоги (чтобы организовать документацию в древовидную структуру). +По умолчанию каталоги имеют заголовок, основанный на имени файла, с пустым содержимым. +Можно предоставить индексные страницы для каждого раздела, создав `index.html` или `index.md` (но не одновременно) в выделенном каталоге. + +Имейте в виду, что для локального просмотра вашего сайта со всеми его функциями, такими как поиск или фрагменты, +требуется локальный сервер. Например, если ваш выходной каталог - `output`, +вы можете использовать python сервер для просмотра всего, выполнив следующие действия и открыв `localhost:8080`: + +```sh +cd output +python3 -m http.server 8080 +``` + +## Характеристики + +Scaladoc использует механизм шаблонов [Liquid](https://shopify.github.io/liquid/) +и предоставляет несколько настраиваемых фильтров и тегов, характерных для документации Scala. + +В Scaladoc все шаблоны могут содержать вступительную часть YAML. +Передняя часть анализируется и помещается в переменную `page`, доступную в шаблонах через Liquid. + +Пример вступительной статьи: + +``` +--- +title: My custom title +--- +``` + +Scaladoc использует некоторые предопределенные свойства для управления аспектами страницы. + +Предустановленные свойства: + +- **title** - обеспечивает заголовок страницы, который будет использоваться в навигации и метаданных HTML. +- **extraCss** - дополнительные файлы `.css`, которые будут включены в эту страницу. + Пути должны указываться относительно корня документации. **Этот параметр не экспортируется в механизм шаблонов.** +- **extraJs** - дополнительные файлы `.js`, которые будут включены в эту страницу. + Пути должны указываться относительно корня документации. **Этот параметр не экспортируется в механизм шаблонов.** +- **hasFrame** - если установлено значение `false`, страница не будет включать макет по умолчанию (навигацию, breadcrumbs и т.д.), + а только токен-оболочку HTML для предоставления метаданных и ресурсов (файлы js и css). **Этот параметр не экспортируется в механизм шаблонов.** +- **layout** - предопределенный макет для использования, см. ниже. **Этот параметр не экспортируется в механизм шаблонов.** + +Свойства перенаправления: + +В дополнение к предустановленным свойствам, Scaladoc также поддерживает свойства перенаправления, +которые позволяют вам перенаправлять с одной страницы на другую. +Это может быть полезно, когда вы перемещаете страницу на новое место, +но хотите, чтобы старый URL-адрес работал. + +- **redirectFrom** - указывает на URL-адрес, с которого вы хотите выполнить перенаправление. + Используя свойство `redirectFrom`, Scaladoc создает пустую страницу по этому URL-адресу, + который включает браузерное перенаправление на текущую страницу. + +Пример: + +``` +--- +redirectFrom: /absolute/path/to/old/url.html +--- +``` + +В приведенном выше примере, +если вы перемещаете страницу из `/absolute/path/to/old/url.html` на новое место, +то вы можете использовать `redirectFrom`, чтобы убедиться, +что старый URL-адрес по-прежнему перенаправляет на новое место. + +Обратите внимание, что свойство `redirectFrom` было вдохновлено плагином Jekyll под названием +[`jekyll-redirect-from`](https://github.com/jekyll/jekyll-redirect-from). + +- **redirectTo** — указывает URL-адрес, на который вы хотите перенаправить с текущей страницы. + Это свойство полезно, когда вы хотите перенаправить на внешнюю страницу + или когда нельзя использовать `redirectFrom`. + +Пример: + +``` +--- +redirectTo: https://docs.scala-lang.org/ +--- +``` + +В приведенном выше примере страница будет перенаправлена на https://docs.scala-lang.org/. + +## Использование существующих шаблонов и макетов + +Чтобы выполнить расширение шаблона, Dottydoc просматривает поле `layout` во вступительной части. +Вот простой пример системы шаблонов в действии `index.html`: + +```html +--- +layout: main +--- + +

Hello world!

+``` + +С таким простым основным шаблоном, как этот: + +{% raw %} + +```html + + + Hello, world! + + + {{ content }} + + +``` + +`{{ content }}` будет заменен на `

Hello world!

` в файле `index.html`. +{% endraw %} + +Макеты должны быть размещены в каталоге `_layouts` в корне сайта: + +``` +├── _layouts +│ └── main.html +└── _docs + ├── getting-started.md + └── index.html +``` + +## Ресурсы + +Чтобы рендерить ассеты вместе со статическим сайтом, их нужно поместить в директорию `_assets` в корне сайта: + +``` +├── _assets +│ └── images +│ └── myimage.png +└── _docs + └── getting-started.md +``` + +Чтобы сослаться на ресурс на странице, необходимо создать ссылку относительно каталога `_assets`. + +``` +Take a look at the following image: [My image](images/myimage.png) +``` + +## Боковая панель + +По умолчанию Scaladoc отображает структуру каталогов из каталога `_docs` на визуализируемом сайте. +Существует также возможность переопределить его, предоставив файл `sidebar.yml` в корневом каталоге сайта. +Конфигурационный файл YAML описывает структуру отображаемого статического сайта и оглавление: + +```yaml +index: index.html +subsection: + - title: Usage + index: usage/index.html + directory: usage + subsection: + - title: Dottydoc + page: usage/dottydoc.html + hidden: false + - title: sbt-projects + page: usage/sbt-projects.html + hidden: false +``` + +Корневой элемент должен быть `subsection`. +Вложенные подразделы приведут к древовидной структуре навигации. + +Свойства `subsection`: + +- `title` - Необязательная строка - заголовок подраздела по умолчанию. + Вступительные заголовки имеют более высокий приоритет. +- `index` - Необязательная строка - Путь к индексной странице подраздела. Путь указан относительно каталога `_docs`. +- `directory` - Необязательная строка - Имя каталога, в котором будет находиться подраздел сгенерированного сайта. + По умолчанию именем каталога является имя подраздела, преобразованное в kebab case. +- `subsection` - Массив `subsection` или `page`. + +Либо `index`, либо `subsection` должны быть определены. Подраздел, определенный с `index` и без `subsection`, +будет содержать страницы и каталоги, загружаемые рекурсивно из каталога индексной страницы. + +Свойства `page`: + +- `title` - Необязательная строка - заголовок страницы по умолчанию. Вступительные заголовки имеют более высокий приоритет. +- `page` - Строка - Путь к странице относительно каталога `_docs`. +- `hidden` - Необязательное логическое значение. Флаг, указывающий, должна ли страница отображаться на боковой панели навигации. + По умолчанию установлено значение `false`. + +**Заметка**: Все пути в файле конфигурации YAML относятся к `/_docs`. + +## Иерархия title + +Если заголовок указан несколько раз, приоритет будет следующим (от высшего к низшему приоритету): + +#### Страница + +1. `title` из `front-matter` файла markdown/html +2. `title` свойство из `sidebar.yml` +3. имя файла + +#### Подраздел + +1. `title` из `front-matter` индексного файла markdown/html +2. `title` свойство из `sidebar.yml` +3. имя файла + +Обратите внимание, что если пропустить `index` файл в древовидной структуре или не указать `title` во вступительной части, +ему будет присвоено общее имя `index`. То же самое относится к использованию `sidebar.yml`, +но не указанию ни `title`, ни `index`, а только подраздела. Снова появится общее имя `index`. + +## Блог + +Функция блога описана в [отдельном документе](/ru/scala3/guides/scaladoc/blog.html). + +## Расширенная конфигурация + +### Полная структура корня сайта + +``` +. +└── / + ├── _layouts/ + │ └── ... + ├── _docs/ + │ └── ... + ├── _blog/ + │ ├── index.md + │ └── _posts/ + │ └── ... + └── _assets/ + ├── js/ + │ └── ... + ├── img/ + │ └── ... + └── ... +``` + +В результате получается статический сайт, содержащий документы, а также блог. +Он также содержит пользовательские макеты и ассеты. +Структура визуализируемой документации может быть основана на файловой системе, но также может быть переопределена конфигурацией YAML. + +### Структура каталогов сопоставления + +Используя файл конфигурации YAML, можно определить, как структура исходного каталога должна быть преобразована в структуру выходного каталога. + +Взглянем на следующее определение подраздела: + +```yaml +- title: Some other subsection + index: abc/index.html + directory: custom-directory + subsection: + - page: abc2/page1.md + - page: foo/page2.md +``` + +В этом подразделе показана возможность конфигурации YAML отображать структуру каталогов. +Несмотря на то, что индексная страница и все определенные дочерние элементы находятся в разных каталогах, +они будут рендериться в `custom-directory`. +Исходная страница `abc/index.html` будет генерировать страницу `custom-directory/index.html`, +исходная страница `abc2/page1.md` - `custom-directory/page1.html`, +а исходная страница `foo/page2.md` - `custom-directory/page2.html`. diff --git a/_ru/scala3/new-in-scala3.md b/_ru/scala3/new-in-scala3.md new file mode 100644 index 0000000000..d4d4ce0773 --- /dev/null +++ b/_ru/scala3/new-in-scala3.md @@ -0,0 +1,184 @@ +--- +layout: singlepage-overview +title: Новое в Scala 3 +partof: scala3-scaladoc +scala3: true +language: ru +--- + +Захватывающая новая версия Scala 3 содержит множество новых функций и улучшений. +Здесь мы представляем вам краткий обзор наиболее важных изменений. +Если вы хотите копнуть глубже, то в вашем распоряжении несколько ссылок: + +- [Книга Scala 3]({% link _overviews/scala3-book/introduction.md %}) предназначена для разработчиков, только знакомящихся с языком Scala. +- [Обзор синтаксиса][syntax-summary] содержит формальное описание нового синтаксиса. +- [Справочник по языку][reference] дает подробное описание изменений Scala 3 по сравнению со Scala 2. +- [Руководство по миграции][migration] содержит всю информацию, необходимую для перехода со Scala 2 на Scala 3. +- [Scala 3 Contributing Guide][contribution] более подробно рассказывает о компиляторе, включая руководство по исправлению проблем. + +## Что нового Scala 3 +Scala 3 — это полная переработка языка Scala. +По сути, многие аспекты системы типов были изменены, чтобы сделать их более последовательными. +Хотя эта версия также приносит интересные новые функции (например, типы объединения), +в первую очередь это означает, что система типов становится (даже) малозаметнее на вашем пути, +и, например, [вывод типов][type-inference] с перегрузкой значительно улучшаются. + +### Новое и яркое: синтаксис +Помимо многих (незначительных) чисток, синтаксис Scala 3 предлагает следующие улучшения: + +- Новый "тихий" синтаксис для структур управления, таких как `if`, `while` и `for` ([новый синтаксис управления][syntax-control]) +- Ключевое слово `new` теперь является необязательным (_например_ [для создания экземпляров][creator]) +- [Опциональные фигурные скобки][syntax-indentation] поддерживающие стиль программирования, не отвлекающий внимание и чувствительный к отступам +- Изменение [подстановочных знаков типа][syntax-wildcard] с `_` на `?`. +- Имплициты (и их синтаксис) были [значительно переработаны][implicits]. + +### Последовательное: контекстуальные абстракции +Одной из основных концепций Scala было (и до некоторой степени до сих пор является) +предоставление пользователям небольшого набора мощных функций, +которые можно комбинировать для достижения большей (а иногда даже непредусмотренной) выразительности. +Например, функциональность _имплицитов_ использовалась для моделирования контекстной абстракции, +для выражения вычислений на уровне типов, моделирования классов типов, выполнения неявных преобразований, +кодирования методов расширения и многого другого. +Извлекая уроки из этих вариантов использования, Scala 3 использует несколько иной подход +и фокусируется на **намерении**, а не на **механизме**. +Вместо того чтобы предлагать одну очень мощную функцию, +Scala 3 предлагает несколько специализированных языковых функций, +позволяющих программистам напрямую выражать свои намерения: + +- **Абстрагирование контекстной информации**. [Using предложения][contextual-using] позволяют программистам абстрагироваться от информации, + которая доступна в контексте вызова и должна передаваться неявно. + В качестве улучшения по сравнению с имплицитами в Scala 2 предложения _using_ могут указываться по типу, + освобождая сигнатуры функций от имен переменных, если на них не ссылаются явно. + +- **Предоставление экземпляров классов типов**. [Экземпляры given][contextual-givens] позволяют программистам определять + каноническое значение определенного типа. Это делает программирование с классами типов более простым без распространения деталей реализации. + +- **Расширение классов задним числом**. В Scala 2 методы расширения должны были быть закодированы с использованием + неявных преобразований или неявных классов. Напротив, в Scala 3 [методы расширения][contextual-extension] + теперь встроены непосредственно в язык, что приводит к более качественным сообщениям об ошибках и улучшенному выводу типов. + +- **Просмотр одного типа как другого**. Неявные преобразования между типами были [переработаны][contextual-conversions] с нуля как экземпляры класса типов `Conversion`. + +- **Высокоуровневые контекстные абстракции**. _Совершенно новая_ особенность [контекстных функций][contextual-functions] делает контекстные абстракции функциями первого класса. + Они являются важным инструментом для авторов библиотек и позволяют кратко выражать предметно-ориентированные языки. + +- **Полезная обратная связь от компилятора**. Если компилятор не может разрешить неявный параметр, + теперь он предоставляет [предложения по импорту](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html), которые могут решить проблему. + +### Говори, что имеешь в виду: улучшения системы типов +Помимо значительно улучшенного вывода типов, система типов Scala 3 также предлагает множество новых функций, +предоставляя вам мощные инструменты для статического выражения инвариантов в типах: + +- **Перечисления**. [Enums][enums] были переработаны, чтобы хорошо сочетаться с case-классами + и формировать новый стандарт для выражения [алгебраических типов данных][enums-adts]. + +- **Непрозрачные типы**. Скройте детали реализации за [непрозрачными псевдонимами типов][types-opaque], не платя за это производительностью! + Непрозрачные типы заменяют классы значений и позволяют установить барьер абстракции, не вызывая дополнительных накладных расходов на упаковку. + +- **Типы пересечения и объединения**. Основание системы типов на новом фундаменте привело к введению новых функций системы типов: + экземпляры [типов-пересечений][types-intersection], например `A & B`, являются экземплярами обоих типов `A` и `B`. + Экземпляры [типов объединения][types-union], например `A | B`, являются экземплярами либо `A`, либо `B`. + Обе конструкции позволяют программистам гибко выражать ограничения типов вне иерархии наследования. + +- **Зависимые типы функций**. Scala 2 уже позволяла возвращаемым типам зависеть от (значения) аргументов. + В Scala 3 теперь можно абстрагироваться от этого шаблона и выразить [зависимые типы функций][types-dependent]. + В типе `type F = (e: Entry) => e.Key` тип результата зависит от аргумента! + +- **Полиморфные типы функций**. Как и в случае с зависимыми типами функций, Scala 2 поддерживала методы, + допускающие параметры типа, но не позволяла программистам абстрагироваться от этих методов. + В Scala 3 [полиморфные типы функций][types-polymorphic], например, `[A] => List[A] => List[A]` + могут абстрагироваться от функций, которые принимают аргументы типа в дополнение к своим аргументам значения. + +- **Лямбда-типы**. То, что нужно было выразить с помощью [плагина компилятора](https://github.com/typelevel/kind-projector) в Scala 2, + теперь является функцией первого класса в Scala 3: лямбда-выражения типов — это функции уровня типа, + которые можно передавать как аргументы (более высокого типа), не требуя определения вспомогательного типа. + +- **Сопоставление типов**. Вместо кодирования вычислений на уровне типов с использованием неявного разрешения, + Scala 3 предлагает прямую поддержку [сопоставления типов][types-match]. + Интеграция вычислений на уровне типов в средство проверки типов позволяет улучшить сообщения об ошибках + и устраняет необходимость в сложных кодировках. + + +### Переосмысление: объектно-ориентированное программирование +Scala всегда была на границе между функциональным программированием и объектно-ориентированным программированием, +а Scala 3 расширяет границы в обоих направлениях! +Вышеупомянутые изменения системы типов и редизайн контекстных абстракций делают _функциональное программирование_ проще, чем раньше. +В то же время следующие новые функции позволяют создавать хорошо структурированные _объектно-ориентированные проекты_ +и поддерживают best practices. + +- **Передача параметров**. Трейты становятся ближе к классам и теперь также могут принимать [параметры][oo-trait-parameters], + что делает их еще более мощным средством модульной декомпозиции программного обеспечения. +- **Планирование расширения**. Наследование классов, которые не предназначены для расширения, + является давней проблемой объектно-ориентированного проектирования. + Чтобы решить эту проблему, [открытые классы][oo-open] требуют, чтобы разработчики библиотек _явно_ помечали классы как открытые. +- **Скрытие деталей реализации**. Вспомогательные трейты, которые реализуют поведение, иногда не должны быть частью вывода типов. + В Scala 3 эти трейты могут быть помечены как [прозрачные][oo-transparent], скрывающие наследование от пользователя (в выводимых типах). +- **Композиция над наследованием**. Эта фраза часто цитируется, но утомительна для реализации. + Не так обстоит дело с [export предложениями][oo-export] в Scala 3 : симметричные по отношению к импорту, + предложения export позволяют пользователю определять псевдонимы для выбранных членов объекта. +- **Больше никаких NullPointerException (экспериментально)**. Scala 3 безопаснее, чем когда-либо: + [явное значение null][oo-explicit-null] выводит `null` из иерархии типов, помогая статически отлавливать ошибки; + дополнительные проверки для [безопасной инициализации][oo-safe-init] обнаруживают попытки доступа к неинициализированным объектам. + +### Зарядка в комплекте: метапрограммирование +В то время как макросы в Scala 2 были только экспериментальной функцией, +Scala 3 поставляется с мощным арсеналом инструментов для метапрограммирования. +[Учебник по макросам]({% link _overviews/scala3-macros/tutorial/index.md %}) содержит подробную информацию о различных возможностях. +В частности, Scala 3 предлагает следующие функциональности для метапрограммирования: + +- **Inline**. В качестве базовой отправной точки [функция inline][meta-inline] позволяет редуцировать значения и методы во время компиляции. + Эта простая функция уже охватывает множество вариантов использования + и в то же время обеспечивает отправную точку для более продвинутых функций. +- **Операции времени компиляции**. Пакет [`scala.compiletime`][meta-compiletime] содержит дополнительные функции, + которые можно использовать для реализации inline методов. +- **Блоки кода Quoted**. В Scala 3 добавлена новая функция [квазицитирования кода][meta-quotes], + обеспечивающая удобный высокоуровневый интерфейс для создания и анализа кода. + Создать код для добавления единицы к единице так же просто, как `'{ 1 + 1 }`. +- **Reflection API**. Для более продвинутых вариантов использования [quotes.reflect][meta-reflection] + предоставляет более детализированный контроль для проверки и создания деревьев программ. + +Если вы хотите узнать больше о метапрограммировании в Scala 3, приглашаем вас пройти наш [tutorial][meta-tutorial]. + + +[enums]: {{ site.scala3ref }}/enums/enums.html +[enums-adts]: {{ site.scala3ref }}/enums/adts.html + +[types-intersection]: {{ site.scala3ref }}/new-types/intersection-types.html +[types-union]: {{ site.scala3ref }}/new-types/union-types.html +[types-dependent]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[types-lambdas]: {{ site.scala3ref }}/new-types/type-lambdas.html +[types-polymorphic]: {{ site.scala3ref }}/new-types/polymorphic-function-types.html +[types-match]: {{ site.scala3ref }}/new-types/match-types.html +[types-opaque]: {{ site.scala3ref }}/other-new-features/opaques.html + +[type-inference]: {{ site.scala3ref }}/changed-features/type-inference.html +[overload-resolution]: {{ site.scala3ref }}/changed-features/overload-resolution.html +[reference]: {{ site.scala3ref }}/overview.html +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[migration]: {% link _overviews/scala3-migration/compatibility-intro.md %} +[contribution]: {% link _overviews/scala3-contribution/contribution-intro.md %} + +[implicits]: {{ site.scala3ref }}/contextual +[contextual-using]: {{ site.scala3ref }}/contextual/using-clauses.html +[contextual-givens]: {{ site.scala3ref }}/contextual/givens.html +[contextual-extension]: {{ site.scala3ref }}/contextual/extension-methods.html +[contextual-conversions]: {{ site.scala3ref }}/contextual/conversions.html +[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html + +[syntax-summary]: {{ site.scala3ref }}/syntax.html +[syntax-control]: {{ site.scala3ref }}/other-new-features/control-syntax.html +[syntax-indentation]: {{ site.scala3ref }}/other-new-features/indentation.html +[syntax-wildcard]: {{ site.scala3ref }}/changed-features/wildcards.html + +[meta-tutorial]: {% link _overviews/scala3-macros/tutorial/index.md %} +[meta-inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[meta-compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[meta-quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[meta-reflection]: {% link _overviews/scala3-macros/tutorial/reflection.md %} + +[oo-explicit-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html +[oo-safe-init]: {{ site.scala3ref }}/other-new-features/safe-initialization.html +[oo-trait-parameters]: {{ site.scala3ref }}/other-new-features/trait-parameters.html +[oo-open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[oo-transparent]: {{ site.scala3ref }}/other-new-features/transparent-traits.html +[oo-export]: {{ site.scala3ref }}/other-new-features/export.html diff --git a/_ru/scala3/scaladoc.md b/_ru/scala3/scaladoc.md new file mode 100644 index 0000000000..9c3df2e7fe --- /dev/null +++ b/_ru/scala3/scaladoc.md @@ -0,0 +1,108 @@ +--- +layout: singlepage-overview +title: Новые возможности Scaladoc +partof: scala3-scaladoc +scala3: true +language: ru +--- + +Новая версия Scala 3 поставляется со совершенно новой реализацией генератора документации _Scaladoc_, переписанной с нуля. +В этой статье вы можете найти основные сведения о новых функциях, которые уже есть или будут представлены в Scaladoc. +Для общей справки см. руководство по [Scaladoc][scaladoc]. + +## Новая функциональность + +### Markdown синтаксис + +Самым большим изменением, представленным в новой версии Scaladoc, является изменение языка по умолчанию для строк документации. +До сих пор Scaladoc поддерживал только синтаксис Wikidoc. +Новый Scaladoc по-прежнему может анализировать устаревший синтаксис `Wikidoc`, +однако Markdown был выбран в качестве основного языка для форматирования комментариев. +Чтобы переключиться обратно на `Wikidoc`, можно передать глобальный параметр перед запуском задачи `doc` +или определить его для конкретных комментариев с помощью директивы `@syntax wiki`. + +Для получения дополнительной информации о том, как использовать все возможности документации, +ознакомьтесь с [документацией Scaladoc][scaladoc-docstrings]. + +### Статический сайт + +Scaladoc также предоставляет простой способ создания **статических сайтов** как для документации, +так и для сообщений в блогах, подобно тому, как это делает Jekyll. +Благодаря этой функциональности вы можете очень удобно хранить свою документацию вместе со сгенерированным Scaladoc API. + +Для получения дополнительной информации о том, как настроить создание статических сайтов, +ознакомьтесь с главой [Статическая документация][static-documentation]. + +![](../../resources/images/scala3/scaladoc/static-site.png) + +### Посты в блоге + +Посты в блогах — это особый тип статических сайтов. В руководстве по работе со Scaladoc +вы можете найти дополнительную информацию о том, как работать с [сообщениями в блогах][built-in-blog]. + +![](../../resources/images/scala3/scaladoc/blog-post.png) + +### Ссылки на соцсети + +Кроме того, Scaladoc предоставляет простой способ настроить [ссылки на социальные сети][social-links], например Twitter или Discord. + +![](../../resources/images/scala3/scaladoc/social-links.png){: style="width: 180px"} + +## Экспериментальные функции + +Следующие функции в настоящее время (май 2021 г.) не являются стабильными для выпуска в scaladoc, +однако мы рады услышать ваши отзывы. +У каждой функции есть отдельная ветка на сайте авторов scala-lang, где вы можете поделиться своим мнением. + +### Компиляция фрагментов + +Одной из экспериментальных возможностей Scaladoc является компилятор фрагментов кода. +Этот инструмент позволит вам компилировать фрагменты, которые вы прикрепляете к своей строке документации, +чтобы проверить, действительно ли они ведут себя так, как предполагалось, например, правильно ли компилируются. +Эта функция очень похожа на инструменты `tut` или `mdoc`, но будет поставляться вместе со Scaladoc, +что упрощает настройку и интеграцию в ваш проект. +Создание интерактивных фрагментов — например, предоставление пользователям возможности редактировать +и компилировать их в браузере — находится на рассмотрении. Но в настоящее время эта функция пока не рассматривается. + +Демонстрация: +* Скрытие кода ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/hiding-code.gif) +* Проверка ошибок компиляции ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/assert-compilation-errors.gif) +* Включение фрагментов ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/snippet-includes.png) + +Для получения дополнительной информации см. [Руководства][snippet-compiler] +или следите за этой веткой [Scala Contributors](https://contributors.scala-lang.org/t/snippet-validation-in-scaladoc-for-scala-3/4976). + +### Поиск по типу + +Поиск функций по их символическим именам может занять много времени. +Вот почему новый scaladoc позволяет искать методы и поля по их типам. + +Итак, для объявления: + +``` +extension [T](arr: IArray[T]) def span(p: T => Boolean): (IArray[T], IArray[T]) = ... +``` + +Вместо поиска по `span` мы также можем искать по `IArray[A] => (A => Boolean) => (IArray[A], IArray[A])`. + +Чтобы использовать эту функциональность, просто введите сигнатуру функции, которую ищете, в строке поиска scaladoc. +Вот как это работает: + +![](../../resources/images/scala3/scaladoc/inkuire-1.0.0-M2_js_flatMap.gif) + +Эта функция предоставляется поисковой системой [Inkuire](https://github.com/VirtusLab/Inkuire), которая работает для Scala 3 и Kotlin. +Чтобы быть в курсе развития этой функции, следите за репозиторием [Inkuire](https://github.com/VirtusLab/Inkuire). + +Для получения дополнительной информации см. [соответствующую главу][search-engine] + +Обратите внимание, что эта функция все еще находится в разработке, поэтому в нее могут быть внесены значительные изменения. +Если вы столкнулись с ошибкой или у вас есть идея для улучшения, не стесняйтесь создавать issue на +[Inkuire](https://github.com/VirtusLab/Inkuire/issues/new) или [dotty](https://github.com/scala/scala3/issues/new). + +[scaladoc]: /ru/scala3/guides/scaladoc/index.html +[scaladoc-docstrings]: /ru/scala3/guides/scaladoc/docstrings.html +[static-documentation]: /ru/scala3/guides/scaladoc/static-site.html +[built-in-blog]: /ru/scala3/guides/scaladoc/blog.html +[social-links]: /ru/scala3/guides/scaladoc/settings.html#-social-links +[search-engine]: /ru/scala3/guides/scaladoc/search-engine.html +[snippet-compiler]: /ru/scala3/guides/scaladoc/snippet-compiler.html diff --git a/_ru/scala3/talks.md b/_ru/scala3/talks.md new file mode 100644 index 0000000000..25fce82809 --- /dev/null +++ b/_ru/scala3/talks.md @@ -0,0 +1,73 @@ +--- +layout: singlepage-overview +title: Обсуждения +partof: scala3-scaladoc +scala3: true +language: ru +versionSpecific: true +--- + +Серия "Давайте поговорим о Scala 3" +------------------------------- + +[Давайте поговорим о Scala 3](https://www.youtube.com/playlist?list=PLTx-VKTe8yLxYQfX_eGHCxaTuWvvG28Ml) — это серия +коротких (около 15 минут) докладов о Scala 3. Они охватывают различные темы, например, как начать работу, +как воспользоваться преимуществами новых языковых функций или как перейти со Scala 2. + +Обсуждения Scala 3 +---------------- +- (ScalaDays 2019, Lausanne) [Тур по Scala 3](https://www.youtube.com/watch?v=_Rnrx2lo9cw) от [Martin Odersky](http://x.com/odersky) + +- (ScalaDays 2016, Berlin) [Развитие Scala](https://www.youtube.com/watch?v=GHzWqJKFCk4) от [Martin Odersky](http://x.com/odersky) [\[слайды\]](http://www.slideshare.net/Odersky/scala-days-nyc-2016) + +- (JVMLS 2015) [Компиляторы — это базы данных](https://www.youtube.com/watch?v=WxyyJyB_Ssc) от [Martin Odersky](http://x.com/odersky) [\[слайды\]](http://www.slideshare.net/Odersky/compilers-are-databases) + +- (Scala World 2015) [Dotty: изучение будущего Scala](https://www.youtube.com/watch?v=aftdOFuVU1o) от [Dmitry Petrashko](http://x.com/darkdimius) [\[слайды\]](https://d-d.me/scalaworld2015/#/). + Дмитрий рассказывает о многих новых функциях, которые предлагает Dotty, таких как типы пересечения и объединения, + улучшенная инициализация lazy val и многое другое. Дмитрий также рассказывает о внутреннем устройстве Dotty + и, в частности, о высоком уровне контекстных абстракций Dotty. + Вы познакомитесь со многими основными понятиями, такими как `Denotations`, их эволюция во времени (компиляции), + их преобразования и многое другое. + +Глубокое погружение в Scala 3 +---------------------- +- (ScalaDays 2019, Lausanne) [Метапрограммирование в Dotty](https://www.youtube.com/watch?v=ZfDS_gJyPTc) от [Nicolas Stucki](https://github.com/nicolasstucki). + +- (ScalaDays 2019, Lausanne) [Scala, ориентированная на будущее: промежуточное представление TASTY](https://www.youtube.com/watch?v=zQFjC3zLYwo) от [Guillaume Martres](http://guillaume.martres.me/). + +- (Mar 21, 2017) [Dotty Internals 1: Trees & Symbols](https://www.youtube.com/watch?v=yYd-zuDd3S8) от [Dmitry Petrashko](http://x.com/darkdimius) [\[заметки\]](https://dotty.epfl.ch/docs/internals/dotty-internals-1-notes.html). + Это записанная встреча между EPFL и Waterloo, на которой мы представляем первые понятия внутри Dotty: деревья и символы. + +- (Mar 21, 2017) [Dotty Internals 2: Types](https://www.youtube.com/watch?v=3gmLIYlGbKc) от [Martin Odersky](http://x.com/odersky) и [Dmitry Petrashko](http://x.com/darkdimius). + Это записанная встреча между EPFL и Waterloo, на которой мы рассказываем, как типы представлены внутри Dotty. + +- (Jun 15, 2017) [Dotty Internals 3: Denotations](https://youtu.be/9iPA7zMRGKY) от [Martin Odersky](http://x.com/odersky) и [Dmitry Petrashko](http://x.com/darkdimius). + Это записанная встреча между EPFL и Waterloo, где мы вводим обозначения в Dotty. + +- (JVM Language Summit) [How do we make the Dotty compiler fast](https://www.youtube.com/watch?v=9xYoSwnSPz0) от [Dmitry Petrashko](http://x.com/darkdimius). + [Dmitry Petrashko](http://x.com/darkdimius) в общих чертах рассказывает о том, что было сделано для создания Dotty. + +- (Typelevel Summit Oslo, May 2016) [Dotty and types: the story so far](https://www.youtube.com/watch?v=YIQjfCKDR5A) от + Guillaume Martres [\[слайды\]](http://guillaume.martres.me/talks/typelevel-summit-oslo/). + Guillaume сосредоточился на некоторых практических улучшениях системы типов, реализованных в Dotty, + таких как новый алгоритм вывода параметров типа, + который может принимать решения о безопасности типов в большем количестве ситуаций, чем scalac. + +- (flatMap(Oslo) 2016) [AutoSpecialization in Dotty](https://vimeo.com/165928176) от [Dmitry Petrashko](http://x.com/darkdimius) [\[слайды\]](https://d-d.me/talks/flatmap2016/#/). + Dotty Linker анализирует вашу программу и ее зависимости, чтобы применить новую схему специализации. + Он основан на нашем опыте Specialization, Miniboxing и проекта Valhalla и значительно уменьшает размер создаваемого байт-кода. + И, что лучше всего, он всегда включен, выполняется за кулисами без аннотаций и приводит к ускорению более чем в 20 раз. + Кроме того, он "просто работает" с коллекциями Scala. + +- (ScalaSphere 2016) [Hacking on Dotty: A live demo](https://www.youtube.com/watch?v=0OOYGeZLHs4) от Guillaume Martres [\[слайды\]](http://guillaume.martres.me/talks/dotty-live-demo/). + Guillaume взламывает Dotty: живая демонстрация, во время которой он создает простую фазу компиляции + для трассировки вызовов методов во время выполнения. + +- (Scala By the Bay 2016) [Dotty: what is it and how it works](https://www.youtube.com/watch?v=wCFbYu7xEJA) от Guillaume + Martres [\[слайды\]](http://guillaume.martres.me/talks/dotty-tutorial/#/). + Guillaume предоставляет высокоуровневое представление о конвейере компиляции Dotty. + +- (ScalaDays 2015, Amsterdam) [Making your Scala applications smaller and faster with the Dotty linker](https://www.youtube.com/watch?v=xCeI1ArdXM4) от Dmitry Petrashko [\[слайды\]](https://d-d.me/scaladays2015/#/). + Дмитрий представляет алгоритм анализа графа вызовов, который реализует Dotty, и преимущества производительности, + которые мы можем получить с точки зрения количества методов, размера байт-кода, размера кода JVM + и количества объектов, выделенных в конце. diff --git a/_ru/scalacenter-courses.md b/_ru/scalacenter-courses.md new file mode 100644 index 0000000000..efa09f8254 --- /dev/null +++ b/_ru/scalacenter-courses.md @@ -0,0 +1,166 @@ +--- +title: Онлайн курсы (MOOCs) от Scala Center +layout: singlepage-overview +language: ru +testimonials: +- /resources/images/scalacenter-courses/testimonial000.jpg +- /resources/images/scalacenter-courses/testimonial001.jpg +- /resources/images/scalacenter-courses/testimonial002.jpg +- /resources/images/scalacenter-courses/testimonial003.jpg +- /resources/images/scalacenter-courses/testimonial004.jpg +- /resources/images/scalacenter-courses/testimonial005.jpg +- /resources/images/scalacenter-courses/testimonial006.jpg +- /resources/images/scalacenter-courses/testimonial007.jpg +- /resources/images/scalacenter-courses/testimonial008.jpg +- /resources/images/scalacenter-courses/testimonial009.jpg +- /resources/images/scalacenter-courses/testimonial010.jpg +- /resources/images/scalacenter-courses/testimonial011.jpg +- /resources/images/scalacenter-courses/testimonial012.jpg +- /resources/images/scalacenter-courses/testimonial013.jpg +- /resources/images/scalacenter-courses/testimonial014.jpg +--- + +[Scala Center] создает онлайн-курсы (также известные как МООК) различного уровня: от начального до продвинутого. + +**Если вы программист и хотите изучить Scala**, рекомендуется использовать два подхода. +Быстрый путь состоит в прохождении курса ["Эффективное программирование на Scala"](#effective-programming-in-scala). +В противном случае вы можете пройти полную [специализацию Scala][Scala Specialization], +состоящую из четырех курсов (охватывающих сложные темы, такие как анализ больших данных и параллельное программирование) +и завершающего проекта. + +Подробнее о курсах вы можете узнать из следующего видео: + +
+ +
+ +## Путь обучения Scala + +На диаграмме ниже показаны возможные пути обучения на наших курсах: + +![](/resources/images/learning-path.png) + +"Базовые" курсы предназначены для программистов без предварительного опыта работы со Scala, +тогда как "углубленные" курсы направлены на укрепление навыков программирования на Scala в конкретной области +(например, параллельном программировании). + +Мы рекомендуем начать с "Эффективного программирования на Scala" (Effective Programming in Scala) +или "Принципов функционального программирования на Scala" (Functional Programming Principles in Scala), +а затем с "Проектирования функциональных программ" (Functional Program Design). +Затем вы можете дополнить свои навыки Scala, +пройдя любой из курсов "Программирование реактивных систем" (Programming Reactive Systems), +"Параллельное программирование" (Parallel Programming) +или "Анализ больших данных с помощью Scala и Spark" (Big Data Analysis with Scala and Spark). +Если вы выберете специализацию Scala, то последним проектом будет Scala Capstone. + +## Учебные платформы + +В настоящее время все наши МООК доступны на платформе [Coursera](https://coursera.org), +а некоторые из них доступны на [edX](https://edx.org) или [Extension School](https://extensionschool.ch). +В этом разделе объясняются различия между этими учебными платформами. + +На всех платформах полный материал всегда доступен онлайн. +Он включает в себя видеолекции, текстовые статьи, опросники и домашние задания с автоматической оценкой. +Все платформы также предоставляют дискуссионные форумы, где вы можете общаться с другими учащимися. + +Отличие Extension School от других платформ заключается в том, +что она проводит живые встречи с инструкторами и обзоры кода экспертами Scala. + +С другой стороны, на Coursera или edX наши курсы можно пройти бесплатно (режим "audit"). +При желании подписка дает вам доступ к сертификату об окончании, подтверждающему ваши результаты. + +Узнайте больше о [сертификатах Coursera](https://learners.coursera.help/hc/en-us/articles/209819053-Get-a-Course-Certificate), +[сертификатах edX](https://support.edx.org/hc/en-us/categories/115002269627-Certificates) +или [сертификатах Extension School](https://www.extensionschool.ch/faqs#certifying-coursework). +Обратите внимание, что ваши подписки также поддерживают работу [Scala Center], +миссией которого является создание качественных учебных материалов. + +Если вы предпочитаете самостоятельное обучение, мы рекомендуем вам выбрать платформу Coursera или edX, +но если вам нужна дополнительная поддержка, рекомендуем вам выбрать Extension School. +Ниже приведена таблица, в которой сравниваются платформы обучения: + +| | Coursera / edX (аудит) | Coursera / edX (подписка) | Extension School | +| ------------------------------------------------ | ---------------------- | ------------------------- | ---------------- | +| Видео-лекции, тесты | Да | Да | Да | +| Домашние задания с автоматической оценкой | Да | Да | Да | +| Дискуссионные форумы | Да | Да | Да | +| Самостоятельный темп | Да | Да | Да | +| Стоимость | $0 | от $50 до $100 за курс | $420 в месяц | +| Сертификат об окончании | Нет | Да | Да | +| Поддерживает Scala Center | Нет | Да | Да | +| 30 минут живого занятия с инструкторами в неделю | Нет | Нет | Да | +| Code reviews экспертами Scala | Нет | Нет | Да | + +## Effective Programming in Scala + +Этот курс доступен на [Coursera](https://coursera.org/learn/effective-scala) и [Extension School](https://extensionschool.ch/learn/effective-programming-in-scala). +Пожалуйста, обратитесь к [этому разделу](#учебные-платформы), чтобы узнать о различиях между обеими учебными платформами. + +["Эффективное программирование на Scala"][Effective Programming in Scala] обучает программистов, не владеющих Scala, +всему, что им нужно для подготовки к работе в Scala. +В конце этого практического курса вы узнаете, как решать общие задачи программирования на Scala +(например, моделирование бизнес-областей, реализацию бизнес-логики, +проектирование больших систем, состоящих из компонентов, +обработку ошибок, обработка данных, параллельное выполнение задач, тестирование вашего кода). +Подробнее об этом курсе вы можете узнать из следующего видео: + +
+ +
+ +Этот курс также является хорошим способом улучшить свои знания Scala 2 до Scala 3. + +После прохождения этого курса вам может быть интересно улучшить свои навыки в конкретных областях, +пройдя курсы ["Параллельное программирование"][Parallel Programming], +["Анализ больших данных с помощью Scala и Spark"][Big Data Analysis with Scala and Spark] +или ["Программирование реактивных систем"][Programming Reactive Systems]. + +## Специализация Scala + +[Специализация Scala][Scala Specialization] обеспечивает практическое введение в функциональное программирование с использованием Scala. +Вы можете получить доступ к материалам и упражнениям курса, зарегистрировавшись на специализацию или прослушав курсы индивидуально. +Специализация состоит из следующих курсов: + +- [Принципы функционального программирования на Scala][Functional Programming Principles in Scala], +- [Функциональный дизайн программ на Scala][Functional Program Design in Scala], +- [Параллельное программирование][Parallel programming], +- [Анализ больших данных с помощью Scala и Spark][Big Data Analysis with Scala and Spark], +- [Функциональное программирование в Scala Capstone][Functional Programming in Scala Capstone]. + +Эти курсы обеспечивают глубокое понимание самого языка Scala, а также погружаются в более конкретные темы, +такие как параллельное программирование и Spark. + +## Программирование реактивных систем + +[Программирование реактивных систем][Programming Reactive Systems] +(также доступно на [edX](https://www.edx.org/course/scala-akka-reactive)) +обучает писать адаптивные, масштабируемые и отказоустойчивые системы с помощью библиотеки Akka. + +## Курсы по Скала 2 + +Все вышеперечисленные курсы используют Scala 3. +При необходимости вы можете найти (устаревшую) версию наших курсов Scala 2 здесь: + +- [Принципы функционального программирования на Scala (версия Scala 2)](https://www.coursera.org/learn/scala2-functional-programming) +- [Функциональный дизайн программ на Scala (версия Scala 2)](https://www.coursera.org/learn/scala2-functional-program-design) +- [Параллельное программирование (версия Scala 2)](https://www.coursera.org/learn/scala2-parallel-programming) +- [Анализ больших данных с помощью Scala и Spark (версия Scala 2)](https://www.coursera.org/learn/scala2-spark-big-data) +- [Программирование реактивных систем (версия Scala 2)](https://www.coursera.org/learn/scala2-akka-reactive) + +## Отзывы + +{% include carousel.html images=page.testimonials number=0 height="50" unit="%" duration="10" %} + +## Другие онлайн-ресурсы + +[На этой странице]({% link online-courses.md %}) вы можете найти другие онлайн-ресурсы, предоставленные сообществом. + +[Scala Center]: https://scala.epfl.ch +[Scala Specialization]: https://www.coursera.org/specializations/scala +[Effective Programming in Scala]: https://www.coursera.org/learn/effective-scala +[Functional Programming Principles in Scala]: https://www.coursera.org/learn/scala-functional-programming +[Functional Program Design in Scala]: https://www.coursera.org/learn/scala-functional-program-design +[Parallel programming]: https://www.coursera.org/learn/scala-parallel-programming +[Big Data Analysis with Scala and Spark]: https://www.coursera.org/learn/scala-spark-big-data +[Functional Programming in Scala Capstone]: https://www.coursera.org/learn/scala-capstone +[Programming Reactive Systems]: https://www.coursera.org/learn/scala-akka-reactive diff --git a/_ru/toolkit/OrderedListOfMdFiles b/_ru/toolkit/OrderedListOfMdFiles new file mode 100644 index 0000000000..b2790bd58a --- /dev/null +++ b/_ru/toolkit/OrderedListOfMdFiles @@ -0,0 +1,36 @@ +introduction.md +testing-intro.md +testing-suite.md +testing-run.md +testing-run-only.md +testing-exceptions.md +testing-asynchronous.md +testing-resources.md +testing-what-else.md +os-intro.md +os-read-directory.md +os-read-file.md +os-write-file.md +os-run-process.md +os-what-else.md +json-intro.md +json-parse.md +json-modify.md +json-deserialize.md +json-serialize.md +json-files.md +json-what-else.md +http-client-intro.md +http-client-request.md +http-client-uris.md +http-client-request-body.md +http-client-json.md +http-client-upload-file.md +http-client-what-else.md +web-server-intro.md +web-server-static.md +web-server-dynamic.md +web-server-query-parameters.md +web-server-input.md +web-server-websockets.md +web-server-cookies-and-decorators.md diff --git a/_ru/toolkit/http-client-intro.md b/_ru/toolkit/http-client-intro.md new file mode 100644 index 0000000000..6e552acecd --- /dev/null +++ b/_ru/toolkit/http-client-intro.md @@ -0,0 +1,24 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Отправка HTTP-запросов с помощью sttp +type: chapter +description: Введение в библиотеку sttp +language: ru +num: 23 +previous-page: +next-page: +--- + +sttp — популярная и многофункциональная библиотека для выполнения HTTP-запросов к веб-серверам. + +Она предоставляет как синхронный API, так и асинхронный API, основанный на `Future`. Она также поддерживает WebSockets. + +Доступны расширения, добавляющие такие возможности, как потоковая передача, логирование, телеметрия и сериализация. + +sttp предлагает одинаковые API на всех платформах (JVM, Scala.js и Scala Native). + +sttp — хороший выбор для небольших синхронных скриптов, а также для крупномасштабных, высококонкурентных, асинхронных приложений. + +{% include markdown.html path="_markdown/_ru/install-sttp.md" %} diff --git a/_ru/toolkit/introduction.md b/_ru/toolkit/introduction.md new file mode 100644 index 0000000000..f6a216e298 --- /dev/null +++ b/_ru/toolkit/introduction.md @@ -0,0 +1,88 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Введение +type: chapter +description: Знакомство с учебными пособиями по Scala инструментариям +language: ru +num: 1 +previous-page: +next-page: testing-intro +toolkit-index: + - title: Тесты + description: Тестирование кода с помощью MUnit. + icon: "fa fa-vial-circle-check" + link: /ru/toolkit/testing-intro.html + - title: Файлы и процессы + description: Запись файлов и запуск процессов с помощью OS-Lib. + icon: "fa fa-folder-open" + link: /ru/toolkit/os-intro.html + - title: JSON + description: Парсинг JSON и сериализация объектов в JSON с помощью uPickle. + icon: "fa fa-file-code" + link: /ru/toolkit/json-intro.html + - title: HTTP-запросы + description: Отправка HTTP-запросов и загрузка файлов с помощью sttp. + icon: "fa fa-globe" + link: /ru/toolkit/http-client-intro.html + - title: Веб-серверы + description: Создание веб-серверов с помощью Cask. + icon: "fa fa-server" + link: /ru/toolkit/web-server-intro.html +--- + +## Что такое набор инструментов Scala? + +Scala Toolkit — это набор библиотек, предназначенных для эффективного выполнения типичных задач программирования. +Он включает в себя инструменты для работы с файлами и процессами, парсинга JSON, отправки HTTP-запросов и модульного тестирования. + +Инструментарий поддерживает: + +- Scala 3 и Scala 2 +- JVM, Scala.js и Scala Native + +Варианты использования набора инструментов включают в себя: + +- кратковременные программы, работающие на JVM, для сканирования веб-сайта, сбора и преобразования данных + или для извлечения и обработки некоторых файлов, +- скрипты интерфейса, которые запускаются в браузере и обеспечивают работу ваших веб-сайтов, +- инструменты командной строки, упакованные в виде собственных двоичных файлов для мгновенного запуска + +{% include inner-documentation-sections.html links=page.toolkit-index %} + +## Что это за руководства? + +В этой серии руководств основное внимание уделяется кратким примерам кода, которые помогут вам быстро приступить к работе. + +Если вам нужна более подробная информация, в руководствах содержатся ссылки на дополнительную документацию +по всем библиотекам в наборе инструментов. + +## Как запустить код? + +Вы можете следовать руководствам независимо от того, как решите запустить свой Scala код. +Руководства фокусируются на самом коде, а не на процессе его запуска. + +Способы запуска кода на Scala включают: + +- В вашем **браузере** с помощью [Scastie](https://scastie.scala-lang.org) + - Плюсы: не требует установки, возможность делиться кодом онлайн + - Минусы: только один файл, доступно только онлайн +- Интерактивно в **REPL** Scala (Read/Eval/Print/Loop) + - Плюсы: интерактивное исследование в терминале + - Минусы: не сохраняет ваш код +- Интерактивно в **worksheet** в вашей IDE, например [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) или [Metals](http://scalameta.org/metals/) + - Плюсы: интерактивное исследование в графическом интерфейсе + - Минусы: требует для запуска среды worksheet +- В **скриптах** с использованием [Scala CLI](https://scala-cli.virtuslab.com) + - Плюсы: рабочий процесс в терминале с минимальной настройкой + - Минусы: может не подходить для крупных проектов +- С использованием **инструмента сборки** (например, [sbt](https://www.scala-sbt.org) или [mill](https://com-lihaoyi.github.io/mill/)) + - Плюсы: рабочий процесс в терминале для проектов любого размера + - Минусы: требует дополнительной настройки и обучения +- С использованием **IDE**, например [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) или [Metals](http://scalameta.org/metals/) + - Плюсы: рабочий процесс в графическом интерфейсе для проектов любого размера + - Минусы: требует дополнительной настройки и обучения + +Эти варианты, с их плюсами и минусами, характерны для большинства языков программирования. +Вы можете использовать любой из них, в зависимости от того варианта, который вам удобен. diff --git a/_ru/toolkit/json-intro.md b/_ru/toolkit/json-intro.md new file mode 100644 index 0000000000..c91f04b075 --- /dev/null +++ b/_ru/toolkit/json-intro.md @@ -0,0 +1,23 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Обработка JSON с помощью uPickle +type: chapter +description: Описание библиотеки uPickle. +language: ru +num: 16 +previous-page: +next-page: +--- + +uPickle — это облегченная библиотека сериализации для Scala. + +В его состав входит uJson — библиотека для работы с JSON, которая может анализировать строки JSON, +получать доступ к их значениям в памяти или изменять их, а также записывать их обратно. + +uPickle может сериализовать и десериализовать объекты Scala напрямую в JSON и из него. +Он знает, как обрабатывать коллекции Scala, такие как `Map` и `Seq`, +а также ваши собственные типы данных, такие как case class-ы и перечисления Scala 3. + +{% include markdown.html path="_markdown/_ru/install-upickle.md" %} diff --git a/_ru/toolkit/os-intro.md b/_ru/toolkit/os-intro.md new file mode 100644 index 0000000000..4e5cb4fd2a --- /dev/null +++ b/_ru/toolkit/os-intro.md @@ -0,0 +1,25 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Работа с файлами и процессами с помощью OS-Lib +type: chapter +description: Введение в библиотеку OS-lib +language: ru +num: 10 +previous-page: +next-page: +--- + +OS-Lib — это библиотека для работы с файлами и процессами. Она является частью Scala Toolkit. + +OS-Lib стремится заменить API `java.nio.file` и `java.lang.ProcessBuilder`. +Скорее всего, вам не понадобиться напрямую использовать какие-либо низкоуровневые Java API. + +OS-Lib также нацелена на то, чтобы вытеснить устаревшие API `scala.io` и `scala.sys` из стандартной библиотеки Scala. + +OS-Lib не имеет зависимостей. + +Весь функционал OS-Lib находится в пространстве имён `os.*`. + +{% include markdown.html path="_markdown/_ru/install-os-lib.md" %} diff --git a/_ru/toolkit/testing-intro.md b/_ru/toolkit/testing-intro.md new file mode 100644 index 0000000000..3fa905ddb3 --- /dev/null +++ b/_ru/toolkit/testing-intro.md @@ -0,0 +1,28 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Тестирование с помощью MUnit +type: chapter +description: Введение в библиотеку MUnit +language: ru +num: 2 +previous-page: introduction +next-page: +--- + +MUnit — легковесная библиотека для тестирования. Она предоставляет единый стиль написания тестов, который можно быстро освоить. + +Несмотря на свою простоту, MUnit обладает такими полезными функциями, как: + +- **утверждения (assertions)** для проверки поведения программы, +- **фикстуры (fixtures)**, чтобы гарантировать, что тесты имеют доступ ко всем необходимым ресурсам, +- **поддержка асинхронности** для тестирования параллельных (concurrent) и распределённых приложений. + +MUnit создаёт полезные отчёты об ошибках с указанием различий и местоположения в исходном коде, что помогает быстро понять причины сбоев. + +Тестирование является важной частью любого процесса разработки программного обеспечения, +так как оно помогает находить ошибки на ранних этапах, +улучшает качество кода и облегчает совместную работу. + +{% include markdown.html path="_markdown/_ru/install-munit.md" %} diff --git a/_ru/toolkit/web-server-intro.md b/_ru/toolkit/web-server-intro.md new file mode 100644 index 0000000000..6afdebab4f --- /dev/null +++ b/_ru/toolkit/web-server-intro.md @@ -0,0 +1,30 @@ +--- +layout: multipage-overview +partof: toolkit +overview-name: "Scala инструментарий" +title: Создание веб-серверов с помощью Cask +type: chapter +description: Введение в библиотеку Cask +language: ru +num: 30 +previous-page: +next-page: +--- + +Cask — это микрофреймворк HTTP, предоставляющий простой и гибкий способ создания веб-приложений. + +Основное внимание уделяется простоте использования, что делает его идеальным для новичков, +но при этом приходится отказываться от некоторых функций, предоставляемых другими фреймворками, например, асинхронности. + +Чтобы определить endpoint, достаточно аннотировать функцию аннотацией, указывающей путь запроса. +Cask позволяет вручную строить ответ с помощью инструментов, предоставляемых библиотекой, +указывая содержимое, заголовки, код статуса и т.д. +Функция endpoint также может возвращать строку, JSON тип [uPickle](https://com-lihaoyi.github.io/upickle/) +или шаблон [Scalatags](https://com-lihaoyi.github.io/scalatags/). +В этом случае Cask автоматически создаст ответ с соответствующими заголовками. + +Cask поставляется в комплекте с библиотекой uPickle для обработки JSON, поддерживает WebSockets +и позволяет расширять конечные точки с помощью декораторов, +которые можно использовать для обработки аутентификации или ограничения скорости. + +{% include markdown.html path="_markdown/_ru/install-cask.md" %} diff --git a/_ru/tour/abstract-type-members.md b/_ru/tour/abstract-type-members.md index 849286419f..537ebf80f4 100644 --- a/_ru/tour/abstract-type-members.md +++ b/_ru/tour/abstract-type-members.md @@ -1,9 +1,6 @@ --- layout: tour title: Члены Абстрактного Типа - -discourse: true - partof: scala-tour num: 23 language: ru @@ -11,20 +8,38 @@ next-page: compound-types previous-page: inner-classes topics: abstract type members prerequisite-knowledge: variance, upper-type-bound - --- -Абстрактные типы, такие как трейты и абстрактные классы, могут содержать членов абстрактного типа. +Абстрактные типы, такие как трейты и абстрактные классы, могут содержать члены абстрактного типа. Абстрактный означает, что только конкретный экземпляр определяет, каким именно будет тип. Вот пример: +{% tabs abstract-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_1 %} + ```scala mdoc trait Buffer { type T val element: T } ``` -Здесь мы определили абстрактный тип `T`, который используется для описания типа члена `element`. Мы можем расширить его в абстрактном классе, добавив верхнюю границу нового типа `U` связанного с `T`, делая описание типа более конкретным. + +{% endtab %} +{% tab 'Scala 3' for=abstract-types_1 %} + +```scala +trait Buffer: + type T + val element: T +``` + +{% endtab %} +{% endtabs %} + +Здесь мы определили абстрактный тип `T`, который используется для описания типа члена `element`. Мы можем расширить его в абстрактном классе, добавив верхнюю границу нового типа `U`, связанного с `T`, делая описание типа более конкретным. + +{% tabs abstract-types_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_2 %} ```scala mdoc abstract class SeqBuffer extends Buffer { @@ -33,28 +48,68 @@ abstract class SeqBuffer extends Buffer { def length = element.length } ``` + +{% endtab %} +{% tab 'Scala 3' for=abstract-types_2 %} + +```scala +abstract class SeqBuffer extends Buffer: + type U + type T <: Seq[U] + def length = element.length +``` + +{% endtab %} +{% endtabs %} + Обратите внимание, как мы можем использовать новый абстрактный тип `U` в качестве верхней границы типа. Класс `SeqBuffer` позволяет хранить в буфере только последовательности, указывая, что тип `T` должен быть подтипом `Seq[U]` для нового абстрактного типа `U`. -[Трейты](traits.html) или [классы](classes.html) с членами абстрактного типа часто используются в сочетании с анонимными экземплярами классов. Чтобы проиллюстрировать это рассмотрим программу, которая имеет дело с буфером, который ссылается на список целых чисел: +[Трейты](traits.html) или [классы](classes.html) с членами абстрактного типа часто используются в сочетании с анонимными экземплярами классов. Чтобы проиллюстрировать это рассмотрим программу, имеющую дело с буфером, который ссылается на список целых чисел: + +{% tabs abstract-types_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_3 %} ```scala mdoc 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) - } + type T = List[U] + val element = List(elem1, elem2) + } +val buf = newIntSeqBuf(7, 8) +println("length = " + buf.length) +println("content = " + buf.element) +``` + +{% endtab %} +{% tab 'Scala 3' for=abstract-types_3 %} + +```scala +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) ``` -Здесь класс `newIntSeqBuf` создает экземпляры `IntSeqBuffer`, используя анонимную реализацию класса `IntSeqBuffer` (т.е. `new IntSeqBuffer`), устанавливая тип `T` как `List[Int]`. -Мы можем вывести тип класса из типа его членов и наоборот. Приведем версию кода, в которой выводится тип класса из типа его члена: +{% endtab %} +{% endtabs %} + +Здесь метод `newIntSeqBuf` создает экземпляры `IntSeqBuffer`, используя анонимную реализацию класса `IntSeqBuffer` (т.е. `new IntSeqBuffer`), устанавливая тип `T` как `List[Int]`. + +Мы можем вывести тип класса из типа его членов и наоборот. Приведем версию кода, в которой выводится тип класса из типов его члена: + +{% tabs abstract-types_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_4 %} ```scala mdoc:nest abstract class Buffer[+T] { @@ -74,4 +129,26 @@ println("length = " + buf.length) println("content = " + buf.element) ``` -Обратите внимание, что здесь необходимо использовать [вариантность в описании типа](variances.html) (`+T <: Seq[U]`) для того, чтобы скрыть конкретный тип реализации списка, возвращаемого из метода `newIntSeqBuf`. +{% endtab %} +{% tab 'Scala 3' for=abstract-types_4 %} + +```scala +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) +``` + +{% endtab %} +{% endtabs %} + +Обратите внимание, что здесь необходимо использовать [вариантность в описании типа](variances.html) (`+T <: Seq[U]`) для того, чтобы скрыть конкретный тип реализации списка, возвращаемого из метода `newIntSeqBuf`. diff --git a/_ru/tour/annotations.md b/_ru/tour/annotations.md index 99a668acb0..0af144139d 100644 --- a/_ru/tour/annotations.md +++ b/_ru/tour/annotations.md @@ -1,34 +1,52 @@ --- layout: tour title: Аннотации - -discourse: true - partof: scala-tour - num: 32 language: ru next-page: packages-and-imports previous-page: by-name-parameters - --- Аннотации используются для передачи метаданных при объявлении. Например, аннотация `@deprecated` перед объявлением метода, заставит компилятор вывести предупреждение, если этот метод будет использован. -``` + +{% tabs annotations_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_1 %} + +```scala mdoc:fail object DeprecationDemo extends App { @deprecated("deprecation message", "release # which deprecates method") def hello = "hola" - hello + hello } ``` + +{% endtab %} +{% tab 'Scala 3' for=annotations_1 %} + +```scala +object DeprecationDemo extends App: + @deprecated("deprecation message", "release # which deprecates method") + def hello = "hola" + + hello +``` + +{% endtab %} +{% endtabs %} + Такой код скомпилируется, но компилятор выдаст предупреждение: "there was one deprecation warning". Аннотация применяется к первому идущему после нее объявлению или определению. Допускается использование сразу нескольких аннотаций следующих друг за другом. Порядок, в котором приводятся аннотации, не имеет значения. ## Аннотации, обеспечивающие корректность работы кода + Некоторые аннотации приводят к невозможности компиляции, если условие (условия) не выполняется. Например, аннотация `@tailrec` гарантирует, что метод является [хвостовой рекурсией](https://ru.wikipedia.org/wiki/%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F). Хвостовая рекурсия помогает держать потребление памяти на постоянном уровне. Вот как она используется в методе, который вычисляет факториал: +{% tabs annotations_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_2 %} + ```scala mdoc import scala.annotation.tailrec @@ -41,8 +59,30 @@ def factorial(x: Int): Int = { factorialHelper(x, 1) } ``` -Метод `factorialHelper` имеет аннотацию `@tailrec`, которая гарантирует, что метод действительно является хвостовой рекурсией. Если бы мы изменили реализацию `factorialHelper` так как указано далее, то компиляция бы провалилась: + +{% endtab %} +{% tab 'Scala 3' for=annotations_2 %} + +```scala +import scala.annotation.tailrec + +def factorial(x: Int): Int = + + @tailrec + def factorialHelper(x: Int, accumulator: Int): Int = + if x == 1 then accumulator else factorialHelper(x - 1, accumulator * x) + factorialHelper(x, 1) ``` + +{% endtab %} +{% endtabs %} + +Метод `factorialHelper` имеет аннотацию `@tailrec`, которая гарантирует, что метод действительно является хвостовой рекурсией. Если бы мы изменили реализацию `factorialHelper` так как указано далее, то компиляция бы провалилась: + +{% tabs annotations_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_3 %} + +```scala mdoc:fail import scala.annotation.tailrec def factorial(x: Int): Int = { @@ -53,77 +93,155 @@ def factorial(x: Int): Int = { factorialHelper(x) } ``` -Мы бы получили сообщение "Recursive call not in tail position"(Рекурсивный вызов не в хвостовой позиции). +{% endtab %} +{% tab 'Scala 3' for=annotations_3 %} + +```scala +import scala.annotation.tailrec + +def factorial(x: Int): Int = + @tailrec + def factorialHelper(x: Int): Int = + if x == 1 then 1 else x * factorialHelper(x - 1) + factorialHelper(x) +``` + +{% endtab %} +{% endtabs %} + +Мы бы получили сообщение "Recursive call not in tail position"(Рекурсивный вызов не в хвостовой позиции). ## Аннотации, влияющие на генерацию кода + +{% tabs annotations_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_4 %} + Некоторые аннотации типа `@inline` влияют на сгенерированный код (т.е. в результате сам код вашего jar-файл может отличаться). Такая аннотация означает вставку всего кода в тело метода вместо вызова. Полученный байт-код длиннее, но, надеюсь, работает быстрее. Использование аннотации `@inline` не гарантирует, что метод будет встроен, но заставит компилятор сделать это, если и только если будут соблюдены некоторые разумные требования к размеру сгенерированного кода. -### Java аннотации ### +{% endtab %} +{% tab 'Scala 3' for=annotations_4 %} + +Некоторые аннотации типа `@main` влияют на сгенерированный код (т.е. в результате сам код вашего jar-файл может отличаться). +Аннотация `@main` к методу создает исполняемую программу, которая вызывает метод как точку входа. + +{% endtab %} +{% endtabs %} + +### Java аннотации + Есть некоторые отличия синтаксиса аннотаций, если пишется Scala код, который взаимодействует с Java. **Примечание:**Убедитесь, что вы используете опцию `-target:jvm-1.8` с аннотациями Java. Java имеет определяемые пользователем метаданные в виде [аннотаций](https://docs.oracle.com/javase/tutorial/java/annotations/). Ключевой особенностью аннотаций является то, что они задаются в виде пар ключ-значение для инициализации своих элементов. Например, если нам нужна аннотация для отслеживания источника какого-то класса, мы можем определить её как -``` +{% tabs annotations_5 %} +{% tab 'Java' for=annotations_5 %} + +```java @interface Source { - public String URL(); + public String url(); public String mail(); } ``` +{% endtab %} +{% endtabs %} + А затем использовать следующим образом -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_6 %} +{% tab 'Java' for=annotations_6 %} + +```java +@Source(url = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + Использование аннотации в Scala похоже на вызов конструктора. Для создания экземпляра из Java аннотации необходимо использовать именованные аргументы: -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_7 %} +{% tab 'Scala 2 и 3' for=annotations_7 %} + +```scala +@Source(url = "https://coders.com/", mail = "support@coders.com") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} + Этот синтаксис достаточно перегруженный, если аннотация содержит только один элемент (без значения по умолчанию), поэтому, если имя указано как `value`, оно может быть применено в Java с помощью конструктора-подобного синтаксиса: -``` +{% tabs annotations_8 %} +{% tab 'Java' for=annotations_8 %} + +```java @interface SourceURL { public String value(); public String mail() default ""; } ``` +{% endtab %} +{% endtabs %} + А затем можно использовать следующим образом -``` +{% tabs annotations_9 %} +{% tab 'Java' for=annotations_9 %} + +```java @SourceURL("https://coders.com/") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + В этом случае Scala предоставляет такую же возможность -``` +{% tabs annotations_10 %} +{% tab 'Scala 2 и 3' for=annotations_10 %} + +```scala @SourceURL("https://coders.com/") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} + Элемент `mail` был указан со значением по умолчанию, поэтому нам не нужно явно указывать его значение. Мы не можем смешивать эти два стиля в Java: -``` +{% tabs annotations_11 %} +{% tab 'Java' for=annotations_11 %} + +```java @SourceURL(value = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + Scala обеспечивает большую гибкость в этом отношении -``` +{% tabs annotations_12 %} +{% tab 'Scala 2 и 3' for=annotations_12 %} + +```scala @SourceURL("https://coders.com/", mail = "support@coders.com") - class MyScalaClass ... +class MyScalaClass ... ``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/automatic-closures.md b/_ru/tour/automatic-closures.md deleted file mode 100644 index 7de83279d8..0000000000 --- a/_ru/tour/automatic-closures.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: tour -title: Конструкция Автоматического Замыкания Зависимого Типа - -discourse: true -language: ru -partof: scala-tour -num: 14 ---- - -Scala допускает использование в качестве параметров методов имена беспараметрических функций. При вызове такого метода фактические параметры для беспараметрических функций не вычисляются, а передается функция с нулем аргументов, которая захватывает вычисление соответствующего параметра (так называемый *вызов по имени*). - -Следующий код демонстрирует этот механизм: - - object TargetTest1 extends Application { - 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` реализует Java-подобный цикл while-loop со схемой рекурсивной реализации. - -Мы можем комбинировать использование [инфиксных/постфиксных операторов](operators.html) с этим механизмом для создания более сложных выражений (с хорошим синтаксисом). - -Вот реализация loop-unless выражения: - - object TargetTest2 extends Application { - 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 { < выражение > } unless ( < условие > )`. - - -Ниже приведен вывод выполнения `TargetTest2`: - - i = 10 - i = 9 - i = 8 - i = 7 - i = 6 - i = 5 - i = 4 - i = 3 - i = 2 - i = 1 diff --git a/_ru/tour/basics.md b/_ru/tour/basics.md index f6c3788f4c..3d8590bfe7 100644 --- a/_ru/tour/basics.md +++ b/_ru/tour/basics.md @@ -1,92 +1,125 @@ --- layout: tour title: Основы - -discourse: true - partof: scala-tour - num: 2 language: ru next-page: unified-types previous-page: tour-of-scala - --- На этой странице мы расскажем об основах Scala. ## Попробовать Scala в браузере. -Вы можете запустить Scala в браузере с помощью ScalaFiddle. +Вы можете запустить Scala в браузере с помощью Scastie. -1. Зайдите на [https://scalafiddle.io](https://scalafiddle.io). +1. Зайдите на [Scastie](https://scastie.scala-lang.org/). 2. Вставьте `println("Hello, world!")` в левую панель. 3. Нажмите кнопку "Run". Вывод отобразится в правой панели. Это простой способ поэкспериментировать со Scala кодом без всяких настроек. -Большинство примеров кода в этой документации также интегрированы с ScalaFiddle, -поэтому вы можете поэкспериментировать с ними, просто нажав кнопку Run. - ## Выражения Выражения — это вычислимые утверждения. + +{% tabs expression %} +{% tab 'Scala 2 и 3' for=expression %} + ```scala mdoc 1 + 1 ``` + +{% endtab %} +{% endtabs %} + Вы можете выводить результаты выражений, используя `println`. -{% scalafiddle %} +{% tabs println %} +{% tab 'Scala 2 и 3' for=println %} + ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} + +{% endtab %} +{% endtabs %} ### Значения Результаты выражений можно присваивать именам с помощью ключевого слова `val`. +{% tabs val %} +{% tab 'Scala 2 и 3' for=val %} + ```scala mdoc val x = 1 + 1 println(x) // 2 ``` -Названные результаты, такие как `x` в примере, называются значениями. +{% endtab %} +{% endtabs %} + +Названные результаты, такие как `x` в примере, называются значениями. Вызов значения не приводит к его повторному вычислению. Значения не изменяемы и не могут быть переназначены. +{% tabs val-error %} +{% tab 'Scala 2 и 3' for=val-error %} + ```scala mdoc:fail x = 3 // Не компилируется. ``` +{% endtab %} +{% endtabs %} + Типы значений могут быть выведены автоматически, но можно и явно указать тип, как показано ниже: +{% tabs type-inference %} +{% tab 'Scala 2 и 3' for=type-inference %} + ```scala mdoc:nest val x: Int = 1 + 1 ``` -Обратите внимание, что объявление типа `Int` происходит после идентификатора `x`, следующим за `:`. +{% endtab %} +{% endtabs %} + +Обратите внимание, что объявление типа `Int` происходит после идентификатора `x`, следующим за `:`. ### Переменные Переменные похожи на значения константы, за исключением того, что их можно присваивать заново. Вы можете объявить переменную с помощью ключевого слова `var`. +{% tabs var %} +{% tab 'Scala 2 и 3' for=var %} + ```scala mdoc:nest var x = 1 + 1 x = 3 // Компилируется потому что "x" объявлен с ключевым словом "var". println(x * x) // 9 ``` +{% endtab %} +{% endtabs %} + Как и в случае со значениями, вы можете явно указать тип, если захотите: +{% tabs type-inference-2 %} +{% tab 'Scala 2 и 3' for=type-inference-2 %} + ```scala mdoc:nest var x: Int = 1 + 1 ``` +{% endtab %} +{% endtabs %} ## Блоки @@ -94,6 +127,9 @@ var x: Int = 1 + 1 Результат последнего выражения в блоке будет результатом всего блока в целом. +{% tabs blocks %} +{% tab 'Scala 2 и 3' for=blocks %} + ```scala mdoc println({ val x = 1 + 1 @@ -101,79 +137,119 @@ println({ }) // 3 ``` +{% endtab %} +{% endtabs %} + ## Функции Функции — это выражения, которые принимают параметры. Вы можете определить анонимную функцию (т.е. без имени), которая возвращает переданное число, прибавив к нему единицу: +{% tabs anonymous-function %} +{% tab 'Scala 2 и 3' for=anonymous-function %} + ```scala mdoc (x: Int) => x + 1 ``` +{% endtab %} +{% endtabs %} + Слева от `=>` находится список параметров. Справа — выражение, связанное с параметрами. Вы также можете назвать функции. -{% scalafiddle %} +{% tabs named-function %} +{% tab 'Scala 2 и 3' for=named-function %} + ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} + +{% endtab %} +{% endtabs %} Функции могут принимать множество параметров. -{% scalafiddle %} +{% tabs multiple-parameters %} +{% tab 'Scala 2 и 3' for=multiple-parameters %} + ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} + +{% endtab %} +{% endtabs %} Или вообще не принимать никаких параметров. +{% tabs no-parameters %} +{% tab 'Scala 2 и 3' for=no-parameters %} + ```scala mdoc val getTheAnswer = () => 42 println(getTheAnswer()) // 42 ``` +{% endtab %} +{% endtabs %} + ## Методы Методы выглядят и ведут себя очень похоже на функции, но между ними есть несколько принципиальных различий. -Методы задаются ключевым словом `def`. За `def` следует имя, список параметров, возвращаемый тип и тело. +Методы задаются ключевым словом `def`. За `def` следует имя, список параметров, возвращаемый тип и тело. + +{% tabs method %} +{% tab 'Scala 2 и 3' for=method %} -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} + +{% endtab %} +{% endtabs %} Обратите внимание, как объявлен возвращаемый тип сразу _после_ списка параметров и двоеточия `: Int`. Методы могут принимать несколько списков параметров. -{% scalafiddle %} +{% tabs multiple-parameter-lists %} +{% tab 'Scala 2 и 3' for=multiple-parameter-lists %} + ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} + +{% endtab %} +{% endtabs %} Или вообще ни одного списка параметров. +{% tabs no-parameter-lists %} +{% tab 'Scala 2 и 3' for=no-parameter-lists %} + ```scala mdoc def name: String = System.getProperty("user.name") println("Hello, " + name + "!") ``` +{% endtab %} +{% endtabs %} + Есть некоторые отличия от функций, но пока что их можно рассматривать как нечто похожее. Методы также могут иметь многострочные выражения. -{% scalafiddle %} +{% tabs get-square-string class=tabs-scala-version %} + +{% tab 'Scala 2' for=get-square-string %} + ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -181,7 +257,22 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} + +{% endtab %} + +{% tab 'Scala 3' for=get-square-string %} + +```scala +def getSquareString(input: Double): String = + val square = input * input + square.toString + +println(getSquareString(2.5)) // 6.25 +``` + +{% endtab %} + +{% endtabs %} Последнее выражение в теле становится возвращаемым значением метода (у Scala есть ключевое слово `return`, но оно практически не используется). @@ -189,55 +280,129 @@ println(getSquareString(2.5)) // 6.25 Вы можете объявлять классы используя ключевое слово `class`, за которым следует его имя и параметры конструктора. +{% tabs greeter-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-definition %} + ```scala mdoc class Greeter(prefix: String, suffix: String) { def greet(name: String): Unit = println(prefix + name + suffix) } ``` + +{% endtab %} + +{% tab 'Scala 3' for=greeter-definition %} + +```scala +class Greeter(prefix: String, suffix: String): + def greet(name: String): Unit = + println(prefix + name + suffix) +``` + +{% endtab %} + +{% endtabs %} + Возвращаемый тип метода `greet` это `Unit`, используется тогда, когда не имеет смысла что-либо возвращать. Аналогично `void` в Java и C. Поскольку каждое выражение Scala должно иметь какое-то значение, то при отсутствии возвращающегося значения возвращается экземпляр типа Unit. Явным образом его можно задать как `()`, он не несет какой-либо информации. Вы можете создать экземпляр класса, используя ключевое слово `new`. -```scala mdoc +{% tabs greeter-usage class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-usage %} + +```scala mdoc:nest val greeter = new Greeter("Hello, ", "!") greeter.greet("Scala developer") // Hello, Scala developer! ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-usage %} + +```scala +val greeter = Greeter("Hello, ", "!") +greeter.greet("Scala developer") // Hello, Scala developer! +``` + +{% endtab %} + +{% endtabs %} + Позже мы рассмотрим классы [подробнее](classes.html). ## Классы-образцы (Case Class) В Scala есть специальный тип класса, который называется классом-образцом (case class). По умолчанию такие классы неизменны и сравниваются по значению из конструктора. Вы можете объявлять классы-образцы с помощью ключевых слов `case class`. +{% tabs case-class-definition %} +{% tab 'Scala 2 и 3' for=case-class-definition %} + ```scala mdoc case class Point(x: Int, y: Int) ``` +{% endtab %} +{% endtabs %} + Можно создавать экземпляры класса-образца без использования ключевого слова `new`. +{% tabs case-class-creation %} +{% tab 'Scala 2 и 3' for=case-class-creation %} + ```scala mdoc val point = Point(1, 2) val anotherPoint = Point(1, 2) val yetAnotherPoint = Point(2, 2) ``` +{% endtab %} +{% endtabs %} + Они сравниваются по значению. +{% tabs compare-case-class-equality class=tabs-scala-version %} + +{% tab 'Scala 2' for=compare-case-class-equality %} + ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$point and $anotherPoint are different.") } // Point(1,2) и Point(1,2) одни и те же. if (point == yetAnotherPoint) { - println(point + " and " + yetAnotherPoint + " are the same.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) и Point(2,2) разные. ``` +{% endtab %} + +{% tab 'Scala 3' for=compare-case-class-equality %} + +```scala +if point == anotherPoint then + println(s"$point and $anotherPoint are the same.") +else + println(s"$point and $anotherPoint are different.") +// Point(1,2) и Point(1,2) одни и те же. + +if point == yetAnotherPoint then + println(s"$point and $yetAnotherPoint are the same.") +else + println(s"$point and $yetAnotherPoint are different.") +// Point(1,2) и Point(2,2) разные. +``` + +{% endtab %} + +{% endtabs %} + Есть еще много деталей, которые мы бы хотели рассказать про классы-образцы; мы уверены, что вы влюбитесь в них! Обязательно рассмотрим их [позже](case-classes.html). ## Объекты @@ -246,6 +411,10 @@ if (point == yetAnotherPoint) { Вы можете задать объекты при помощи ключевого слова `object`. +{% tabs id-factory-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=id-factory-definition %} + ```scala mdoc object IdFactory { private var counter = 0 @@ -256,8 +425,27 @@ object IdFactory { } ``` +{% endtab %} + +{% tab 'Scala 3' for=id-factory-definition %} + +```scala +object IdFactory: + private var counter = 0 + def create(): Int = + counter += 1 + counter +``` + +{% endtab %} + +{% endtabs %} + Вы можете сразу получить доступ к объекту, ссылаясь на его имя. +{% tabs id-factory-usage %} +{% tab 'Scala 2 и 3' for=id-factory-usage %} + ```scala mdoc val newId: Int = IdFactory.create() println(newId) // 1 @@ -265,6 +453,9 @@ val newerId: Int = IdFactory.create() println(newerId) // 2 ``` +{% endtab %} +{% endtabs %} + Позже мы рассмотрим объекты [подробнее](singleton-objects.html). ## Трейты @@ -273,15 +464,35 @@ println(newerId) // 2 Объявить трейт можно с помощью ключевого слова `trait`. +{% tabs greeter-trait-def class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-trait-def %} + ```scala mdoc:nest trait Greeter { def greet(name: String): Unit } ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-trait-def %} + +```scala +trait Greeter: + def greet(name: String): Unit +``` + +{% endtab %} + +{% endtabs %} + Трейты также могут иметь реализации методов и полей, которые предполагается использовать умолчанию. -{% scalafiddle %} +{% tabs greeter-trait-def-impl class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-trait-def-impl %} + ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -289,8 +500,26 @@ trait Greeter { } ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-trait-def-impl %} + +```scala +trait Greeter: + def greet(name: String): Unit = + println("Hello, " + name + "!") +``` + +{% endtab %} + +{% endtabs %} + Вы можете наследовать свойства трейтов, используя ключевое слово `extends` и переопределять реализацию с помощью ключевого слова `override`. +{% tabs greeter-implementations class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-implementations %} + ```scala mdoc class DefaultGreeter extends Greeter @@ -306,7 +535,28 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} + +{% endtab %} + +{% tab 'Scala 3' for=greeter-implementations %} + +```scala +class DefaultGreeter extends Greeter + +class CustomizableGreeter(prefix: String, postfix: String) extends Greeter: + override def greet(name: String): Unit = + println(prefix + name + postfix) + +val greeter = DefaultGreeter() +greeter.greet("Scala developer") // Hello, Scala developer! + +val customGreeter = CustomizableGreeter("How are you, ", "?") +customGreeter.greet("Scala developer") // How are you, Scala developer? +``` + +{% endtab %} + +{% endtabs %} Здесь `DefaultGreeter` наследуется только от одного трейта, но можно наследоваться от нескольких. @@ -314,14 +564,38 @@ customGreeter.greet("Scala developer") // How are you, Scala developer? ## Главный метод -Главный метод является отправной точкой в программе. +Главный метод является отправной точкой в программе. Для Виртуальной Машины Java требуется, чтобы главный метод назывался `main` и принимал один аргумент, массив строк. Используя объект, можно задать главный метод следующим образом: +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-demo %} + +In Scala 2 you must define a main method manually. Using an object, you can define the main method as follows: + ```scala mdoc object Main { def main(args: Array[String]): Unit = println("Hello, Scala developer!") } ``` + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} + +In Scala 3, with the `@main` annotation, a main method is automatically generated from a method as follows: + +```scala +@main def hello() = println("Hello, Scala developer!") +``` + +{% endtab %} + +{% endtabs %} + +## Дополнительные ресурсы + +- Обзор [Scala book](/ru/scala3/book/taste-intro.html) diff --git a/_ru/tour/by-name-parameters.md b/_ru/tour/by-name-parameters.md index 4a1a4d4417..06c9a32063 100644 --- a/_ru/tour/by-name-parameters.md +++ b/_ru/tour/by-name-parameters.md @@ -1,26 +1,32 @@ --- layout: tour title: Вызов по имени - -discourse: true - partof: scala-tour - num: 31 language: ru next-page: annotations previous-page: operators - --- _Вызов параметров по имени_ - это когда значение параметра вычисляется только в момент вызова параметра. Этот способ противоположен _вызову по значению_. Чтоб вызов параметра был по имени, необходимо просто указать `=>` перед его типом. + +{% tabs by-name-parameters_1 %} +{% tab 'Scala 2 и 3' for=by-name-parameters_1 %} + ```scala mdoc def calculate(input: => Int) = input * 37 ``` + +{% endtab %} +{% endtabs %} + Преимущество вызова параметров по имени заключается в том, что они не вычисляются если не используются в теле функции. С другой стороны плюсы вызова параметров по значению в том, что они вычисляются только один раз. Вот пример того, как мы можем реализовать условный цикл: +{% tabs by-name-parameters_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=by-name-parameters_2 %} + ```scala mdoc def whileLoop(condition: => Boolean)(body: => Unit): Unit = if (condition) { @@ -35,8 +41,29 @@ whileLoop (i > 0) { i -= 1 } // выведет 2 1 ``` -Метод `whileLoop` использует несколько списков параметров - условие и тело цикла. Если `condition` является верным, выполняется `body`, а затем выполняется рекурсивный вызов whileLoop. Если `condition` является ложным, то тело никогда не вычисляется, тк у нас стоит `=>` перед типом `body`. + +{% endtab %} +{% tab 'Scala 3' for=by-name-parameters_2 %} + +```scala +def whileLoop(condition: => Boolean)(body: => Unit): Unit = + if condition then + body + whileLoop(condition)(body) + +var i = 2 + +whileLoop (i > 0) { + println(i) + i -= 1 +} // выведет 2 1 +``` + +{% endtab %} +{% endtabs %} + +Метод `whileLoop` использует несколько списков параметров - условие и тело цикла. Если `condition` является верным, выполняется `body`, а затем выполняется рекурсивный вызов whileLoop. Если `condition` является ложным, то тело никогда не вычисляется, тк у нас стоит `=>` перед типом `body`. Теперь, когда мы передаем `i > 0` как наше условие `condition` и `println(i); i-= 1` как тело `body`, код ведет себя также как обычный цикл в большинстве языков программирования. -Такая возможность откладывать вычисления параметра до его использования может помочь повысить производительность, отсекая не нужные вычисления при определенных условиях. +Такая возможность откладывать вычисления параметра до его использования может помочь повысить производительность, отсекая не нужные вычисления при определенных условиях. diff --git a/_ru/tour/case-classes.md b/_ru/tour/case-classes.md index 9a49c560dd..467fc0655e 100644 --- a/_ru/tour/case-classes.md +++ b/_ru/tour/case-classes.md @@ -1,53 +1,90 @@ --- layout: tour title: Классы Образцы - -discourse: true - partof: scala-tour - num: 11 language: ru next-page: pattern-matching previous-page: multiple-parameter-lists prerequisite-knowledge: classes, basics, mutability - --- -Классы образцы (Case classes) похожи на обычные классы с несколькими ключевыми отличиями, о которых мы поговорим ниже. Классы образцы хороши для моделирования неизменяемых данных. На следующей странице обзора вы увидите, насколько они полезны для участия в [сопоставлении с примером](pattern-matching.html). +Классы образцы (Case classes) похожи на обычные классы с несколькими ключевыми отличиями, о которых мы поговорим ниже. +Классы образцы хороши для моделирования неизменяемых данных. +На следующей странице обзора вы увидите, насколько они полезны для участия в [сопоставлении с примером](pattern-matching.html). ## Объявление класса образца + Минимальный вариант объявления класса образца: указание ключевого слова `case class`, название и список параметров (которые могут быть пустыми). Пример: + +{% tabs case-classe_Book %} + +{% tab 'Scala 2 и 3' for=case-classe_Book %} + ```scala mdoc case class Book(isbn: String) val frankenstein = Book("978-0486282114") ``` -Обратите внимание, что ключевое слово `new` не было использовано для создания экземпляра класса `Book`. Это связано с тем, что классы образцы по умолчанию имеют объект компаньон с методом `apply`, который берет на себя заботу о создании экземпляра класса. + +{% endtab %} + +{% endtabs %} + +Обратите внимание, что ключевое слово `new` не было использовано для создания экземпляра класса `Book`. +Это связано с тем, что классы образцы по умолчанию имеют объект компаньон с методом `apply`, +который берет на себя заботу о создании экземпляра класса. При создании класса образца с параметрами, эти параметры являются публичными и неизменяемыми. + +{% tabs case-classe_Message_define %} + +{% tab 'Scala 2 и 3' for=case-classe_Message_define %} + ``` 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 +println(message1.sender) // печатает guillaume@quebec.ca message1.sender = "travis@washington.us" // эта строка не компилируется ``` + +{% endtab %} + +{% endtabs %} + Вы не можете переназначить `message1.sender`, потому что это `val` (т.е. константа). Возможно использовать `var` в классах образцах, но это не рекомендуется. ## Сравнение + Классы образцы сравниваются по структуре, а не по ссылкам: -``` + +{% tabs case-classe_Message_compare %} + +{% tab 'Scala 2 и 3' for=case-classe_Message_compare %} + +```scala mdoc 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 ``` + +{% endtab %} + +{% endtabs %} + Даже если `message2` и `message3` ссылаются на разные объекты, значения каждого из них равны. ## Копирование + Вы можете создать копию экземпляра класса образца, просто воспользовавшись методом `copy`. При этом по желанию можно изменить аргументы конструктора. + +{% tabs case-classe_Message_copy %} + +{% tab 'Scala 2 и 3' for=case-classe_Message_copy %} + ``` 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") @@ -56,4 +93,13 @@ message5.sender // travis@washington.us message5.recipient // claire@bourgogne.fr message5.body // "Me zo o komz gant ma amezeg" ``` + +{% endtab %} + +{% endtabs %} + Получатель `message4` использует в качестве отправителя `message5`, кроме параметра `body` который был скопирован из `message4`. + +## Дополнительные ресурсы + +- Дополнительная информация о классах образцах доступна в [Scala Book](/ru/scala3/book/domain-modeling-tools.html#case-class-ы) diff --git a/_ru/tour/classes.md b/_ru/tour/classes.md index db66150841..75701bb107 100644 --- a/_ru/tour/classes.md +++ b/_ru/tour/classes.md @@ -1,30 +1,53 @@ --- layout: tour title: Классы - -discourse: true - partof: scala-tour - num: 4 language: ru next-page: traits previous-page: unified-types topics: classes prerequisite-knowledge: no-return-keyword, type-declaration-syntax, string-interpolation, procedures - --- Классы в Scala являются основами для создания объектов. Они могут содержать методы, константы, переменные, типы, объекты, трейты и классы, которые в совокупности называются _членами_. Типы, объекты и трейты будут рассмотрены позже в ходе нашего обзора. ## Объявление класса + Минимальное объявление класса - это просто ключевое слово `class` и его имя. Имена классов должны быть написаны с заглавной буквы. + +{% tabs class-minimal-user class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-minimal-user %} + ```scala mdoc class User val user1 = new User ``` -Ключевое слово `new` используется для создания экземпляра класса. `User` имеет конструктор по умолчанию, который не принимает аргументов, так как конструктор не был определен. Однако обычно используется и конструктор, и тело класса. Пример объявления класса Point приведен ниже: + +Ключевое слово `new` используется для создания экземпляра класса. +{% endtab %} + +{% tab 'Scala 3' for=class-minimal-user %} + +```scala +class User + +val user1 = User() +``` + +Чтобы создать экземпляр класса, мы вызываем его как функцию: `User()`. +Также можно явно использовать ключевое слово `new`: `new User()` - хотя обычно это опускается. +{% endtab %} + +{% endtabs %} + +`User` имеет конструктор по умолчанию, который не принимает аргументов, так как конструктор не был определен. Однако обычно используется и конструктор, и тело класса. Пример объявления класса Point приведен ниже: + +{% tabs class-point-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-example %} ```scala mdoc class Point(var x: Int, var y: Int) { @@ -39,10 +62,34 @@ class Point(var x: Int, var y: Int) { } val point1 = new Point(2, 3) -point1.x // 2 -println(point1) // prints (2, 3) +println(point1.x) // выводит 2 +println(point1) // выводит (2, 3) +``` + +{% endtab %} + +{% tab 'Scala 3' for=class-point-example %} + +```scala +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)" +end Point + +val point1 = Point(2, 3) +println(point1.x) // выводит 2 +println(point1) // выводит (2, 3) ``` +{% endtab %} + +{% endtabs %} + В этом классе у `Point` есть четыре члена: переменные `x` и `y` и методы `move` и `toString`. В отличие от многих других языков, основной конструктор находится в сигнатуре класса `(var x: Int, var y: Int)`. Метод `move` принимает два целочисленных аргумента и возвращает значение Unit `()` - это пустое множество, которое не содержит никакой информации. Примерно соответствует `void` в Java-подобных языках. С другой стороны, `toString` не принимает никаких аргументов, а возвращает значение `String`. Поскольку `toString` переопределяет `toString` из [`AnyRef`](unified-types.html), он помечается ключевым словом `override`. @@ -50,61 +97,193 @@ println(point1) // prints (2, 3) Конструкторы могут иметь необязательные параметры, если указать их значения по умолчанию как в примере: +{% tabs class-point-with-default-values class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-with-default-values %} + ```scala mdoc:nest class Point(var x: Int = 0, var y: Int = 0) -val origin = new Point // x и y оба равны 0 -val point1 = new Point(1) -println(point1.x) // выводит 1 +val origin = new Point // x и y оба равны 0 +val point1 = new Point(1) // x равен 1, а y равен 0 +println(point1) // выводит (1, 0) +``` + +{% endtab %} +{% tab 'Scala 3' for=class-point-with-default-values %} + +```scala +class Point(var x: Int = 0, var y: Int = 0) + +val origin = Point() // x и y оба равны 0 +val point1 = Point(1) // x равен 1, а y равен 0 +println(point1) // выводит (1, 0) ``` +{% endtab %} + +{% endtabs %} + В этой версии класса `Point`, `x` и `y` имеют значение по умолчанию `0`, поэтому аргументов не требуется. Однако, поскольку конструктор считывает аргументы слева направо, если вы просто хотите передать значение `y`, то вам нужно будет указать задаваемый параметр. + +{% tabs class-point-named-argument class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-named-argument %} + ```scala mdoc:nest class Point(var x: Int = 0, var y: Int = 0) -val point2 = new Point(y=2) -println(point2.y) // выводит 2 +val point2 = new Point(y = 2) +println(point2) // выводит (0, 2) +``` + +{% endtab %} + +{% tab 'Scala 3' for=class-point-named-argument %} + +```scala +class Point(var x: Int = 0, var y: Int = 0) +val point2 = Point(y = 2) +println(point2) // выводит (0, 2) ``` +{% endtab %} + +{% endtabs %} + Что также является хорошей практикой для повышения ясности кода. ## Скрытые члены и синтаксис Геттер/Сеттер (получатель/установщик значений) + По умолчанию члены класса являются открытыми для внешнего доступа (публичными). Используйте модификатор `private`, чтобы скрыть их от внешнего доступа. -```scala mdoc:nest + +{% tabs class-point-private-getter-setter class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-private-getter-setter %} + +```scala mdoc:reset 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 x: Int = _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 + def y: Int = _y + def y_=(newValue: Int): Unit = { + if (newValue < bound) + _y = newValue + else + printWarning() } - private def printWarning = println("WARNING: Out of bounds") + private def printWarning(): Unit = + println("WARNING: Out of bounds") } val point1 = new Point point1.x = 99 point1.y = 101 // выводит предупреждение (printWarning) ``` -В данной версии класса `Point` данные хранятся в скрытых переменных `_x` и `_y`. Существуют методы `def x` и `def y` для доступа к скрытым данным. Методы `def x_=` и `def y_=` (сеттеры) предназначены для проверки и установки значения `_x` и `_y`. Обратите внимание на специальный синтаксис для сеттеров: метод `_=` применяется к имени геттера. + +{% endtab %} + +{% tab 'Scala 3' for=class-point-private-getter-setter %} + +```scala +class Point: + private var _x = 0 + private var _y = 0 + private val bound = 100 + + def x: Int = _x + def x_=(newValue: Int): Unit = + if newValue < bound then + _x = newValue + else + printWarning() + + def y: Int = _y + def y_=(newValue: Int): Unit = + if newValue < bound then + _y = newValue + else + printWarning() + + private def printWarning(): Unit = + println("WARNING: Out of bounds") +end Point + +val point1 = Point() +point1.x = 99 +point1.y = 101 // выводит предупреждение (printWarning) +``` + +{% endtab %} + +{% endtabs %} + +В данной версии класса `Point` данные хранятся в скрытых переменных `_x` и `_y`. Существуют методы `def x` и `def y` для доступа к скрытым данным. Методы `def x_=` и `def y_=` (сеттеры) предназначены для проверки и установки значения `_x` и `_y`. Обратите внимание на специальный синтаксис для сеттеров: метод `_=` применяется к имени геттера. Первичные параметры конструктора с параметрами `val` и `var` являются общедоступными. Однако, поскольку `val` - это константа, то нельзя писать следующее. + +{% tabs class-point-cannot-set-val class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-cannot-set-val %} + ```scala mdoc:fail class Point(val x: Int, val y: Int) val point = new Point(1, 2) point.x = 3 // <-- не компилируется ``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-cannot-set-val %} + +```scala +class Point(val x: Int, val y: Int) +val point = Point(1, 2) +point.x = 3 // <-- не компилируется +``` + +{% endtab %} + +{% endtabs %} + Параметры без `val` или `var` являются скрытыми от внешнего доступа и видимы только внутри класса. + +{% tabs class-point-non-val-ctor-param class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-non-val-ctor-param %} + ```scala mdoc:fail class Point(x: Int, y: Int) val point = new Point(1, 2) point.x // <-- не компилируется ``` + +{% endtab %} + +{% tab 'Scala 3' for=class-point-non-val-ctor-param %} + +```scala +class Point(x: Int, y: Int) +val point = Point(1, 2) +point.x // <-- не компилируется +``` + +{% endtab %} + +{% endtabs %} + +## Дополнительные ресурсы + +- Узнайте больше о классах в [Scala Book](/ru/scala3/book/domain-modeling-tools.html#классы) +- Как использовать [вспомогательные конструкторы классов](/ru/scala3/book/domain-modeling-tools.html#вспомогательные-конструкторы) diff --git a/_ru/tour/compound-types.md b/_ru/tour/compound-types.md index eb861afbfd..876bbaf42a 100644 --- a/_ru/tour/compound-types.md +++ b/_ru/tour/compound-types.md @@ -1,25 +1,26 @@ --- layout: tour title: Составные Типы - -discourse: true - partof: scala-tour - num: 24 language: ru next-page: self-types previous-page: abstract-type-members - --- -Иногда необходимо выразить, то что тип объекта является подтипом нескольких других типов. В Scala это можно выразить с помощью *составных типов*, которые являются объединением нескольких типов объектов. +Иногда необходимо выразить, то что тип объекта является подтипом нескольких других типов. + +В Scala это можно выразить с помощью _типов пересечений_ (или _составных типов_ в Scala 2), +которые являются объединением нескольких типов объектов. Предположим, у нас есть два трейта: `Cloneable` и `Resetable`: +{% tabs compound-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_1 %} + ```scala mdoc trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { + override def clone(): Cloneable = { // создает публичный метод 'clone' super.clone().asInstanceOf[Cloneable] } } @@ -28,9 +29,26 @@ trait Resetable { } ``` -Теперь предположим, что мы хотим написать функцию `cloneAndReset`, которая берет объект, клонирует его и сбрасывает (Reset) состояние исходного объекта: +{% endtab %} +{% tab 'Scala 3' for=compound-types_1 %} +```scala +trait Cloneable extends java.lang.Cloneable: + override def clone(): Cloneable = // создает публичный метод 'clone' + super.clone().asInstanceOf[Cloneable] +trait Resetable: + def reset: Unit ``` + +{% endtab %} +{% endtabs %} + +Теперь предположим, что мы хотим написать функцию `cloneAndReset`, которая берет объект, клонирует его и сбрасывает (Reset) состояние исходного объекта: + +{% tabs compound-types_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_2 %} + +```scala mdoc:fail def cloneAndReset(obj: ?): Cloneable = { val cloned = obj.clone() obj.reset @@ -38,17 +56,49 @@ def cloneAndReset(obj: ?): Cloneable = { } ``` -Возникает вопрос, какой тип параметр `obj` должна принимать наша объединённая функция. Если это `Cloneable`, то объект может использовать метод `clone`, но не `reset`; если это `Resetable` мы можем использовать метод `reset`, но нет операции `clone`. Чтобы избежать приведения типа в такой ситуации, мы можем указать, что тип `obj` является и `Cloneable`, и `Resetable`. Этот совместный тип в Scala записывается как: `Cloneable with Resetable`. +{% endtab %} +{% tab 'Scala 3' for=compound-types_2 %} + +```scala +def cloneAndReset(obj: ?): Cloneable = + val cloned = obj.clone() + obj.reset + cloned +``` + +{% endtab %} +{% endtabs %} + +Возникает вопрос, какой тип параметра `obj` должна принимать наша объединённая функция. Если это `Cloneable`, то объект может использовать метод `clone`, но не `reset`; если это `Resetable` мы можем использовать метод `reset`, но нет операции `clone`. Чтобы избежать приведения типа в такой ситуации, мы можем указать, что тип `obj` является и `Cloneable`, и `Resetable`. + +{% tabs compound-types_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_3 %} +Этот совместный тип в Scala записывается как: `Cloneable with Resetable`. Вот обновленная функция: -``` +```scala mdoc:fail def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //... } ``` -Составные типы могут состоять из нескольких типов объектов, и они могут содержать единый доработанный объект, в котором будут доработаны характеристики существующих членов объекта. -Общая форма записи: `A with B with C ... { доработанный объект }` +Обратите внимание, что у вас может быть более двух типов: `A with B with C with ...`. +Это означает то же самое, что и: `(...(A with B) with C) with ... )` + +{% endtab %} +{% tab 'Scala 3' for=compound-types_3 %} +Этот совместный тип в Scala записывается как: `Cloneable & Resetable`. + +Вот обновленная функция: + +```scala +def cloneAndReset(obj: Cloneable & Resetable): Cloneable = { + //... +} +``` -Пример использования таких доработок приведен на странице об [объединении классов с примесями](mixin-class-composition.html). +Обратите внимание, что у вас может быть более двух типов: `A & B & C & ...`. +`&` является ассоциативным, поэтому скобки могут быть добавлены вокруг любой части без изменения значения. +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/default-parameter-values.md b/_ru/tour/default-parameter-values.md index 72ab55e786..c45b5dd419 100644 --- a/_ru/tour/default-parameter-values.md +++ b/_ru/tour/default-parameter-values.md @@ -1,20 +1,18 @@ --- layout: tour title: Значения Параметров По умолчанию - -discourse: true - partof: scala-tour - num: 33 language: ru next-page: named-arguments previous-page: classes prerequisite-knowledge: named-arguments, function syntax - --- -Scala предоставляет возможность задавать значения параметров по умолчанию, что позволяет лишний раз не указывать параметры. +Scala предоставляет возможность задавать значения параметров по умолчанию, что позволяет лишний раз не указывать параметры. + +{% tabs default-parameter-values-1 %} +{% tab 'Scala 2 и 3' for=default-parameter-values-1 %} ```scala mdoc def log(message: String, level: String = "INFO") = println(s"$level: $message") @@ -23,22 +21,41 @@ log("System starting") // выведет "INFO: System starting" log("User not found", "WARNING") // выведет "WARNING: User not found" ``` +{% endtab %} +{% endtabs %} + У параметра `level` есть значение по умолчанию, поэтому он необязателен. В последней строке аргумент `"WARNING"` переназначает аргумент по умолчанию `"INFO"`. Вместо того чтоб использовать перегруженные методы в Java, вы можете просто указать дополнительные параметры как параметры по умолчанию для достижения того же эффекта. Однако, если при вызове пропущен хотя бы один аргумент, все остальные аргументы должны вызываться с указанием конкретного имени аргумента. +{% tabs default-parameter-values-2 %} +{% tab 'Scala 2 и 3' for=default-parameter-values-2 %} + ```scala mdoc class Point(val x: Double = 0, val y: Double = 0) val point1 = new Point(y = 1) ``` + +{% endtab %} +{% endtabs %} + Так мы можем указать что `y = 1`. Обратите внимание, что параметры по умолчанию в Scala, при вызове из Java кода, являются обязательными: +{% tabs default-parameter-values-3 %} +{% tab 'Scala 2 и 3' for=default-parameter-values-3 %} + ```scala mdoc:reset // Point.scala class Point(val x: Double = 0, val y: Double = 0) ``` +{% endtab %} +{% endtabs %} + +{% tabs default-parameter-values-4 %} +{% tab 'Java' for=default-parameter-values-4 %} + ```java // Main.java public class Main { @@ -47,3 +64,37 @@ public class Main { } } ``` + +{% endtab %} +{% endtabs %} + +### Параметры по умолчанию для перегруженных методов + +Scala не позволяет определять два метода с параметрами по умолчанию и с одинаковым именем (перегруженные методы). +Важная причина этого - избежание двусмысленности, которая может быть вызвана наличием параметров по умолчанию. +Чтобы проиллюстрировать проблему, давайте рассмотрим определение методов, представленных ниже: + +{% tabs default-parameter-values-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala mdoc:fail +object A { + def func(x: Int = 34): Unit + def func(y: String = "abc"): Unit +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object A: + def func(x: Int = 34): Unit + def func(y: String = "abc"): Unit +``` + +{% endtab %} +{% endtabs %} + +Если мы вызываем `A.func()`, компилятор не может узнать, +намеревался ли программист вызвать `func(x: Int = 34)` или `func(y: String = "abc")`. diff --git a/_ru/tour/extractor-objects.md b/_ru/tour/extractor-objects.md index 0c968f36cd..f93981cd6c 100644 --- a/_ru/tour/extractor-objects.md +++ b/_ru/tour/extractor-objects.md @@ -1,26 +1,25 @@ --- layout: tour title: Объект Экстрактор - -discourse: true - partof: scala-tour - num: 16 language: ru next-page: for-comprehensions previous-page: regular-expression-patterns - --- Объект Экстрактор (объект распаковщик или extractor object) - это объект с методом `unapply`. В то время как метод `apply` обычно действует как конструктор, который принимает аргументы и создает объект, метод `unapply` действует обратным образом, он принимает объект и пытается извлечь и вернуть аргументы из которых он (возможно) был создан. Чаще всего этот метод используется в функциях сопоставления с примером и в частично определенных функциях. +{% tabs extractor-objects_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=extractor-objects_definition %} + ```scala mdoc import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") @@ -34,32 +33,82 @@ customer1ID match { case _ => println("Could not extract a CustomerID") } ``` + +{% endtab %} + +{% tab 'Scala 3' for=extractor-objects_definition %} + +```scala +import scala.util.Random + +object CustomerID: + + def apply(name: String) = s"$name--${Random.nextLong()}" + + def unapply(customerID: String): Option[String] = + val stringArray: Array[String] = customerID.split("--") + if stringArray.tail.nonEmpty then Some(stringArray.head) else None + +val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 +customer1ID match + case CustomerID(name) => println(name) // выведет Sukyoung + case _ => println("Could not extract a CustomerID") +``` + +{% endtab %} + +{% endtabs %} + Метод `apply` создает `CustomerID` из строки `name`. `unapply` делает обратное, чтобы вернуть `name` обратно. Когда мы вызываем `CustomerID("Sukyoung")`, это сокращенный синтаксис вызова `CustomerID.apply("Sukyoung")`. Когда мы вызываем `case CustomerID(name) => println(name)`, мы на самом деле вызываем метод `unapply`. При объявлении нового значения можно использовать пример, в котором значение для инициализации переменной получается через извлечение, используя метод `unapply`. +{% tabs extractor-objects_use-case-1 %} + +{% tab 'Scala 2 и 3' for=extractor-objects_use-case-1 %} + ```scala mdoc val customer2ID = CustomerID("Nico") val CustomerID(name) = customer2ID println(name) // выведет Nico ``` +{% endtab %} + +{% endtabs %} + Что эквивалентно `val name = CustomerID.unapply(customer2ID).get`. +{% tabs extractor-objects_use-case-2 %} + +{% tab 'Scala 2 и 3' for=extractor-objects_use-case-2 %} + ```scala mdoc val CustomerID(name2) = "--asdfasdfasdf" ``` +{% endtab %} + +{% endtabs %} + Если совпадений нет, то бросается `scala.MatchError`: +{% tabs extractor-objects_use-case-3 %} + +{% tab 'Scala 2 и 3' for=extractor-objects_use-case-3 %} + ```scala mdoc:crash val CustomerID(name3) = "-asdfasdfasdf" ``` +{% endtab %} + +{% endtabs %} + Возвращаемый тип `unapply` выбирается следующим образом: -* Если это всего лишь тест, возвращается `Boolean`. Например `case even()`. -* Если в результате найдено одно значение типа `T`, то возвращается `Option[T]`. -* Если вы хотите получить несколько значений `T1,..., Tn`, то ответ необходимо группировать в дополнительный кортеж `Option[(T1,..., Tn)]`. +- Если это всего лишь тест, возвращается `Boolean`. Например `case even()`. +- Если в результате найдено одно значение типа `T`, то возвращается `Option[T]`. +- Если вы хотите получить несколько значений `T1,..., Tn`, то ответ необходимо группировать в дополнительный кортеж `Option[(T1,..., Tn)]`. -Иногда количество извлекаемых значений не является фиксированным. Если в зависимости от входа мы хотим вернуть произвольное количество значений, то для этого случая мы можем определить экстрактор методом `unapplySeq`, который возвращает `Option[Seq[T]]`. Характерным примером такого подхода является разложение `List` с помощью `case List(x, y, z) =>` и разложение `String` с помощью регулярного выражения `Regex`, такого как `case r(name, remainingFields @ _*) =>`. +Иногда количество извлекаемых значений не является фиксированным. Если в зависимости от входа мы хотим вернуть произвольное количество значений, то для этого случая мы можем определить экстрактор методом `unapplySeq`, который возвращает `Option[Seq[T]]`. Характерным примером такого подхода является разложение `List` с помощью `case List(x, y, z) =>` и разложение `String` с помощью регулярного выражения `Regex`, такого как `case r(name, remainingFields @ _*) =>`. diff --git a/_ru/tour/for-comprehensions.md b/_ru/tour/for-comprehensions.md index 29aad7105c..8f6ae91bba 100644 --- a/_ru/tour/for-comprehensions.md +++ b/_ru/tour/for-comprehensions.md @@ -1,54 +1,97 @@ --- layout: tour title: Сложные for-выражения - -discourse: true - partof: scala-tour - num: 17 language: ru next-page: generic-classes previous-page: extractor-objects - --- -Scala предлагает простую запись для выражения *последовательных преобразований*. Эти преобразования можно упростить используя специальный синтаксис `for выражения` (for comprehension), который записывается как `for (enumerators) yield e`, где `enumerators` относятся к списку перечислителей, разделенных точкой с запятой. Где отдельный такой "перечислитель" (*enumerator*) является либо генератором, который вводит новые переменные, либо фильтром. For-выражение вычисляет тело `e` (которое связанно с тем что генерирует *enumerator*) и возвращает последовательность вычислений. +Scala предлагает простую запись для выражения _последовательных преобразований_. Эти преобразования можно упростить используя специальный синтаксис `for выражения` (for comprehension), который записывается как `for (enumerators) yield e`, где `enumerators` относятся к списку перечислителей, разделенных точкой с запятой. Где отдельный такой "перечислитель" (_enumerator_) является либо генератором, который вводит новые переменные, либо фильтром. For-выражение вычисляет тело `e` (которое связанно с тем что генерирует _enumerator_) и возвращает последовательность вычислений. Вот пример: +{% tabs for-comprehensions-01 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-01 %} + ```scala mdoc case class User(name: String, age: Int) -val userBase = List(User("Travis", 28), +val userBase = List( + User("Travis", 28), User("Kelly", 33), User("Jennifer", 44), User("Dennis", 23)) -val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30)) - yield user.name // т. е. добавить результат к списку +val twentySomethings = + for (user <- userBase if user.age >=20 && user.age < 30) + yield user.name // т. е. добавить результат к списку -twentySomethings.foreach(name => println(name)) // выводит "Travis Dennis" +twentySomethings.foreach(println) // выводит "Travis Dennis" ``` - `for`-выражение, используется с оператором `yield`, на самом деле создает `List`. Потому что мы указали `yield user.name` (то есть вывести имя пользователя), получаем `List[String]`. `user <- userBase` и есть наш генератор, а `if (user.age >=20 && user.age < 30)` - это фильтр который отфильтровывает пользователей, не достигших 30-летнего возраста. + +{% endtab %} +{% tab 'Scala 3' for=for-comprehensions-01 %} + +```scala +case class User(name: String, age: Int) + +val userBase = List( + User("Travis", 28), + User("Kelly", 33), + User("Jennifer", 44), + User("Dennis", 23)) + +val twentySomethings = + for user <- userBase if user.age >=20 && user.age < 30 + yield user.name // т. е. добавить результат к списку + +twentySomethings.foreach(println) // выводит "Travis Dennis" +``` + +{% endtab %} +{% endtabs %} + +`for`-выражение, используется с оператором `yield`, на самом деле создает `List`. Потому что мы указали `yield user.name` (то есть вывести имя пользователя), получаем `List[String]`. `user <- userBase` и есть наш генератор, а `if (user.age >=20 && user.age < 30)` - это фильтр который отфильтровывает пользователей, не достигших 30-летнего возраста. Ниже приведен более сложный пример использования двух генераторов. Он вычисляет все пары чисел между `0` и `n-1`, сумма которых равна заданному значению `v`: +{% tabs for-comprehensions-02 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-02 %} + ```scala mdoc def foo(n: Int, v: Int) = for (i <- 0 until n; - j <- i until n if i + j == v) + j <- 0 until n if i + j == v) yield (i, j) -foo(10, 10) foreach { +foo(10, 10).foreach { case (i, j) => - println(s"($i, $j) ") // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) + println(s"($i, $j) ") // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1) } +``` + +{% endtab %} +{% tab 'Scala 3' for=for-comprehensions-02 %} +```scala +def foo(n: Int, v: Int) = + for i <- 0 until n + j <- 0 until n if i + j == v + yield (i, j) + +foo(10, 10).foreach { + (i, j) => println(s"($i, $j) ") // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1) +} ``` + +{% endtab %} +{% endtabs %} + Здесь `n == 10` и `v == 10`. На первой итерации `i == 0` и `j == 0` так `i + j != v` и поэтому ничего не выдается. `j` увеличивается еще в 9 раз, прежде чем `i` увеличивается до `1`. Без фильтра `if` будет просто напечатано следующее: -```scala +```scala (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 0) ... ``` @@ -56,11 +99,34 @@ foo(10, 10) foreach { Вы можете обойтись без `yield` в for-выражении. В таком случае, результатом будет `Unit`. Это может быть полезным для выполнения кода основанного на побочных эффектах. Вот программа, эквивалентная предыдущей, но без использования `yield`: +{% tabs for-comprehensions-03 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-03 %} + ```scala mdoc:nest def foo(n: Int, v: Int) = for (i <- 0 until n; - j <- i until n if i + j == v) + j <- 0 until n if i + j == v) println(s"($i, $j)") foo(10, 10) ``` + +{% endtab %} + +{% tab 'Scala 3' for=for-comprehensions-03 %} + +```scala +def foo(n: Int, v: Int) = + for i <- 0 until n + j <- 0 until n if i + j == v + do println(s"($i, $j)") + +foo(10, 10) +``` + +{% endtab %} +{% endtabs %} + +## Дополнительные ресурсы + +- Другие примеры "For comprehension" доступны [в книге Scala](/ru/scala3/book/control-structures.html#выражение-for) diff --git a/_ru/tour/generic-classes.md b/_ru/tour/generic-classes.md index fafc2fd13e..591f810cfd 100644 --- a/_ru/tour/generic-classes.md +++ b/_ru/tour/generic-classes.md @@ -1,22 +1,23 @@ --- layout: tour title: Обобщенные Классы - -discourse: true - partof: scala-tour - num: 18 language: ru next-page: variances previous-page: for-comprehensions assumed-knowledge: classes unified-types - --- + Обобщенные классы (Generic classes) - это классы, обладающие параметрическим полиморфизмом (т. е. классы, которые изменяют свое поведение в зависимости от приписываемого им типа. Этот тип указывается в квадратных скобках `[]` сразу после имени класса). Они особенно полезны для создания коллекций. ## Объявление обобщенного класса + Для объявления обобщенного класса необходимо после имени добавить тип в квадратных скобках `[]` как еще один параметр класса. По соглашению обычно используют заглавные буквы `A`, хотя можно использовать любые имена. + +{% tabs generic-classes-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-1 %} + ```scala mdoc class Stack[A] { private var elements: List[A] = Nil @@ -30,20 +31,64 @@ class Stack[A] { } } ``` -Данная реализация класса `Stack` принимает в качестве параметра любой тип `A`. Это означает что список, `var elements: List[A] = Nil`, может хранить только элементы типа `A`. Процедура `def push` принимает только объекты типа `A` (примечание: `elements = x :: elements` переназначает `elements` в новый список, созданный путем добавления `x`а к текущим `elements`). + +{% endtab %} +{% tab 'Scala 3' for=generic-classes-1 %} + +```scala +class Stack[A]: + private var elements: List[A] = Nil + def push(x: A): Unit = + elements = x :: elements + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` + +{% endtab %} +{% endtabs %} + +Данная реализация класса `Stack` принимает в качестве параметра любой тип `A`. Это означает что список, `var elements: List[A] = Nil`, может хранить только элементы типа `A`. Процедура `def push` принимает только объекты типа `A` (примечание: `elements = x :: elements` переназначает `elements` в новый список, созданный путем добавления `x` к текущим `elements`). + +Здесь `Nil` — это пустой `List`, и его не следует путать с `null`. ## Использование Чтобы использовать обобщенный класс, поместите конкретный тип в квадратные скобки вместо `A`. -``` + +{% tabs generic-classes-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-2 %} + +```scala mdoc val stack = new Stack[Int] stack.push(1) stack.push(2) -println(stack.pop) // выведет 2 -println(stack.pop) // выведет 1 +println(stack.pop()) // выведет 2 +println(stack.pop()) // выведет 1 ``` -Экземпляр `stack` может принимать только Intы. Однако, если тип имеет подтипы, то они также могут быть приняты: + +{% endtab %} +{% tab 'Scala 3' for=generic-classes-2 %} + +```scala +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // выведет 2 +println(stack.pop()) // выведет 1 ``` + +{% endtab %} +{% endtabs %} + +Экземпляр `stack` может принимать элементы типа `Int`. Однако, если тип имеет подтипы, то они также могут быть приняты: + +{% tabs generic-classes-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-3 %} + +```scala mdoc:nest class Fruit class Apple extends Fruit class Banana extends Fruit @@ -55,6 +100,25 @@ val banana = new Banana stack.push(apple) stack.push(banana) ``` + +{% endtab %} +{% tab 'Scala 3' for=generic-classes-3 %} + +```scala +class Fruit +class Apple extends Fruit +class Banana extends Fruit + +val stack = Stack[Fruit] +val apple = Apple() +val banana = Banana() + +stack.push(apple) +stack.push(banana) +``` + +{% endtab %} +{% endtabs %} Классы `Apple` и `Banana` наследуются от `Fruit` так, что мы можем засунуть экземпляры `Apple` и `Banana` в пачку `Fruit`. _Примечание: подтипы обобщенных типов - *инвариантны*. Это означает, что если у нас есть стэк символов типа `Stack[Char]`, то он не может быть использован как стек интов типа `Stack[Int]`. Это нежелательное поведение, потому как позволило бы нам добавлять в стек символов целые числа. В заключение, `Stack[A]` является подтипом `Stack[B]` тогда и только тогда, когда `B = A`. Поскольку это может быть довольно строгим ограничением, Scala предлагает [механизм вариативного описания параметров типа](variances.html) для контроля за поведением подтипов._ diff --git a/_ru/tour/higher-order-functions.md b/_ru/tour/higher-order-functions.md index 36a8f0a2a7..b55b4d9b24 100644 --- a/_ru/tour/higher-order-functions.md +++ b/_ru/tour/higher-order-functions.md @@ -1,46 +1,80 @@ --- layout: tour title: Функции Высшего Порядка - -discourse: true - partof: scala-tour - num: 8 language: ru next-page: nested-functions previous-page: mixin-class-composition - --- -Функции высшего порядка могут принимать другие функции в качестве параметров или возвращать функцию в качестве результата. -Такое возможно поскольку функции являются объектами первого класса в Scala. -На текущем этапе терминология может казаться немного запутанной, мы используем следующую фразу "функция высшего порядка" как для методов, так и для функций, которые могут принимать другие функции в качестве параметров, или возвращать функции в качестве результата. +Функции высшего порядка могут принимать другие функции в качестве параметров или возвращать функцию в качестве результата. +Такое возможно поскольку функции являются объектами первого класса в Scala. +На текущем этапе терминология может казаться немного запутанной, мы используем следующую фразу "функция высшего порядка" как для методов, так и для функций, которые могут принимать другие функции в качестве параметров, или возвращать функции в качестве результата. + +В чисто объектно-ориентированном мире рекомендуется избегать раскрытия методов, +параметризованных функциями, которые могут привести к утечке внутреннего состояния объекта. +Утечка внутреннего состояния может нарушить инварианты самого объекта, тем самым нарушив инкапсуляцию. -Одним из наиболее распространенных примеров функции высшего порядка +Одним из наиболее распространенных примеров функции высшего порядка является функция `map`, которая доступна в коллекциях Scala. -```scala mdoc -val salaries = Seq(20000, 70000, 40000) + +{% tabs map_example_1 %} + +{% tab 'Scala 2 и 3' for=map_example_1 %} + +```scala mdoc:nest +val salaries = Seq(20_000, 70_000, 40_000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) ``` + +{% endtab %} + +{% endtabs %} + `doubleSalary` - это функция, которая принимает один Int `x` и возвращает `x * 2`. В общем случае, кортеж (список имен в скобках) слева от стрелки `=>` - это список параметров, а значение выражения следует справа. Это же значение возвращается в качестве результата. В строке 3 к каждому элементу списка зарплат (salaries) применяется функция `doubleSalary`. Чтобы сократить код, мы можем сделать функцию анонимной и передать ее напрямую в качестве аргумента в map: + +{% tabs map_example_2 %} + +{% tab 'Scala 2 и 3' for=map_example_2 %} + ```scala mdoc:nest -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` + +{% endtab %} + +{% endtabs %} + Обратите внимание, что в приведенном выше примере `x`не объявлен как `Int`. Это потому, что компилятор может вывести тип, основываясь на типе который ожидает функция map. Еще более элегантным способом написания этого же кода было бы таким: +{% tabs map_example_3 %} + +{% tab 'Scala 2 и 3' for=map_example_3 %} + ```scala mdoc:nest -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` + +{% endtab %} + +{% endtabs %} + Поскольку компилятор Scala уже знает тип параметров (Int), вам нужно только указать правую часть функции. Единственное условие заключается в том, что вместо имени параметра необходимо использовать `_` (в предыдущем примере это было `x`). ## Преобразование методов в функции + Также возможно передавать методы в качестве аргументов функциям более высокого порядка, поскольку компилятор Scala может преобразовать метод в функцию. + +{% tabs Coercing_methods_into_functions class=tabs-scala-version %} + +{% tab 'Scala 2' for=Coercing_methods_into_functions %} + ```scala mdoc case class WeeklyWeatherForecast(temperatures: Seq[Double]) { @@ -49,11 +83,33 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) { def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- передается метод convertCtoF } ``` + +{% endtab %} + +{% tab 'Scala 3' for=Coercing_methods_into_functions %} + +```scala +case class WeeklyWeatherForecast(temperatures: Seq[Double]): + + private def convertCtoF(temp: Double) = temp * 1.8 + 32 + + def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- передается метод convertCtoF +``` + +{% endtab %} + +{% endtabs %} + Здесь метод `convertCtoF` передается в `forecastInFahrenheit`. Это возможно, потому что компилятор преобразовывает `convertCtoF` в функцию `x => ConvertCtoF(x)` (примечание: `x` будет сгенерированным именем, которое гарантированно будет уникальным в рамках своей области видимости). ## Функции, которые принимают функции + Одной из причин использования функций высшего порядка является сокращение избыточного кода. Допустим, вам нужны какие-то методы, которые могли бы повышать чью-то зарплату по разным условиям. Без создания функции высшего порядка это могло бы выглядеть примерно так: +{% tabs Functions_that_accept_functions_1 class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_1 %} + ```scala mdoc object SalaryRaiser { @@ -68,8 +124,33 @@ object SalaryRaiser { } ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_accept_functions_1 %} + +```scala +object SalaryRaiser: + + def smallPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * 1.1) + + def greatPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * salary) +``` + +{% endtab %} + +{% endtabs %} + Обратите внимание, что каждый из этих трех методов отличается только коэффициентом умножения. Для упрощения можно перенести повторяющийся код в функцию высшего порядка: +{% tabs Functions_that_accept_functions_2 class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_2 %} + ```scala mdoc:nest object SalaryRaiser { @@ -79,7 +160,7 @@ object SalaryRaiser { def smallPromotion(salaries: List[Double]): List[Double] = promotion(salaries, salary => salary * 1.1) - def bigPromotion(salaries: List[Double]): List[Double] = + def greatPromotion(salaries: List[Double]): List[Double] = promotion(salaries, salary => salary * math.log(salary)) def hugePromotion(salaries: List[Double]): List[Double] = @@ -87,12 +168,44 @@ object SalaryRaiser { } ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_accept_functions_2 %} + +```scala +object SalaryRaiser: + + private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] = + salaries.map(promotionFunction) + + def smallPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * 1.1) + + def greatPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * salary) +``` + +{% endtab %} + +{% endtabs %} + Новый метод, `promotion`, берет зарплату и функцию типа `Double => Double` (т.е. функция, которая берет Double и возвращает Double) и возвращает их произведение. +Методы и функции обычно выражают поведение или преобразование данных, поэтому наличие функций, +которые компонуются на основе других функций, может помочь в создании общих механизмов. +Эти типовые функции откладывают блокировку всего поведения операции, +предоставляя клиентам возможность контролировать или дополнительно настраивать части самой операции. ## Функции, возвращающие функции Есть определенные случаи, когда вы хотите сгенерировать функцию. Вот пример метода, который возвращает функцию. +{% tabs Functions_that_return_functions class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_return_functions %} + ```scala mdoc def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = { val schema = if (ssl) "https://" else "http://" @@ -106,4 +219,24 @@ val query = "id=1" val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_return_functions %} + +```scala +def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = + val schema = if ssl then "https://" else "http://" + (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query" + +val domainName = "www.example.com" +def getURL = urlBuilder(ssl=true, domainName) +val endpoint = "users" +val query = "id=1" +val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String +``` + +{% endtab %} + +{% endtabs %} + Обратите внимание, что возвращаемый тип urlBuilder`(String, String) => String`. Это означает, что возвращаемая анонимная функция принимает две строки и возвращает строку. В нашем случае возвращаемая анонимная функция `(endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"`. diff --git a/_ru/tour/implicit-conversions.md b/_ru/tour/implicit-conversions.md index bd36a9e749..5e3001862d 100644 --- a/_ru/tour/implicit-conversions.md +++ b/_ru/tour/implicit-conversions.md @@ -1,63 +1,52 @@ --- layout: tour title: Неявные Преобразования - -discourse: true - partof: scala-tour - num: 27 language: ru next-page: polymorphic-methods previous-page: implicit-parameters - --- -Неявные преобразование типа `S` к типу `T` задается неявным значением функционального типа `S =>T`, или неявным методом, который способен преобразовывать к значению требуемого типа. +Неявные преобразования — это мощная функция Scala, применяемая в двух распространенных вариантах: -Неявное преобразование применяются в двух случаях: +- разрешить пользователям предоставлять аргумент одного типа так, как если бы это был другой тип, чтобы избежать шаблонного. +- в Scala 2 для предоставления дополнительных членов запечатанным классам (заменены [методами расширения][exts] в Scala 3). -* Если выражение `e` типа `S` не подходит под ожидаемый тип выражения `T`. -* Если мы выбирая член `e.m`, где `e` является представителем типа `S`, при этом выбранное имя `m` не найдено среди доступных селекторов принадлежащих типу `S`. - -В первом случае выполняется поиск приведения `c`, которое можно применить к `e` чтоб тип результата стал соответствовать ожидаемому `T`. -Во втором случае выполняется поиск преобразования `c`, которое применимо к `e` и результат которого бы содержал член с именем `m`. +### Детальный разбор -Если неявный метод `List[A] => Ordered[List[A]]` находится в области видимости, также как и неявный метод `Int => Ordered[Int]`, то следующая операция с двумя списками типа `List[Int]` является допустимой: +{% tabs implicit-conversion-defn class=tabs-scala-version %} +{% tab 'Scala 2' %} -``` -List(1, 2, 3) <= List(4, 5) -``` +В Scala 2 неявное преобразование из типа `S` в тип `T` определяется либо [неявным классом]({% link _overviews/core/implicit-classes.md %}) `T` +с одним параметром типа `S`, [неявным значением](implicit-parameters.html), которое имеет тип функции `S => T`, +либо неявным методом, преобразуемым в значение этого типа. -Неявный метод `Int => Ordered[Int]` предоставляется автоматически через `scala.Predef.intWrapper`. Ниже приведен пример объявления неявного метода `List[A] => Ordered[List[A]]`. +{% endtab %} +{% tab 'Scala 3' %} -```scala mdoc -import scala.language.implicitConversions +В Scala 3 неявное преобразование из типа `S` в тип `T` определяется [экземпляром `given`](implicit-parameters.html), который имеет тип `scala.Conversion[S, T]`. +Для совместимости со Scala 2 их также можно определить неявным методом (подробнее читайте во вкладке Scala 2). -implicit def list2ordered[A](x: List[A]) - (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] = - new Ordered[List[A]] { - //заменить на более полезную реализацию - def compare(that: List[A]): Int = 1 - } -``` +{% endtab %} +{% endtabs %} -Неявно импортируемый объект `scala.Predef` объявляет ряд псевдонимов для часто используемым типов (например, `scala.collection.immutable.Map` использует псевдоним `Map`) и методов (например, `assert`), а также делает доступным целую серию неявных преобразований. +Неявные преобразования применяются в двух случаях: -Например, при вызове Java метода, который ожидает `java.lang.Integer`, вместо него вы можете свободно использовать `scala.Int`. Потому что Predef включает в себя следующие неявные преобразования: +1. Если выражение `e` имеет тип `S` и `S` не соответствует ожидаемому типу выражения `T`. +2. При выборе `e.m`, где `e` типа `S`, если селектор `m` не указывает на элемент `S`. -```scala mdoc -import scala.language.implicitConversions +В первом случае ищется конверсия `c`, применимая к `e` и тип результата которой соответствует `T`. -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) -``` +Примером является передача `scala.Int`, например `x`, методу, который ожидает `scala.Long`. +В этом случае вставляется неявное преобразование `Int.int2long(x)`. -Компилятор предупреждает при компиляции об обнаружении неявных преобразований, тк неявные преобразования могут иметь разные подводные камни (особенно если использовать их без разбора). +Во втором случае ищется преобразование `c`, применимое к `e` и результат которого содержит элемент с именем `m`. -Чтоб отключить предупреждения выполните одно из следующих действий: +Примером является сравнение двух строк `"foo" < "bar"`. +В этом случае `String` не имеет члена `<`, поэтому вставляется неявное преобразование `Predef.augmentString("foo") < "bar"` +(`scala.Predef` автоматически импортируется во все программы Scala). -* Импортируйте `scala.language.implicitConversions` в области видимости, где объявлены неявные преобразования. -* Вызывайте компилятор с ключом `-language:implicitConversions`. +Дополнительная литература: [Неявные преобразования (в книге Scala)](/ru/scala3/book/ca-implicit-conversions.html). -В таком случае при преобразовании компилятором не будет выдаваться никаких предупреждений. +[exts]: /ru/scala3/book/ca-extension-methods.html diff --git a/_ru/tour/implicit-parameters.md b/_ru/tour/implicit-parameters.md index cf58ded10e..14f11c299c 100644 --- a/_ru/tour/implicit-parameters.md +++ b/_ru/tour/implicit-parameters.md @@ -1,73 +1,111 @@ --- layout: tour -title: Неявные Параметры - -discourse: true - +title: Контекстные параметры, также известные, как неявные параметры partof: scala-tour - num: 26 language: ru next-page: implicit-conversions previous-page: self-types - --- -Метод может иметь список _неявных_ параметров, помеченный ключевым словом _implicit_ в начале списка параметров. Если параметры в этом списке не передаются как обычно, то Scala будет искать, где можно получить неявное значение требуемого типа, и если найдет, то передаст его автоматически. - -Места, где Scala будет искать эти параметры, делятся на две категории: +Метод может иметь список _контекстных параметров_ (_contextual parameters_), +также называемых _неявными параметрами_ (_implicit parameters_) или, точнее, _имплицитами_ (_implicits_). +Списки параметров, начинающиеся с ключевого слова `using` (или `implicit` в Scala 2), задают контекстные параметры. +Если сторона вызова явно не предоставляет аргументы для таких параметров, +Scala будет искать неявно доступные `given` (или `implicit` в Scala 2) значения правильного типа. +Если можно найти подходящие значения, то они автоматически передаются. -* Скала сначала будет искать неявные параметры, доступ к которым можно получить напрямую (без префикса) в месте вызова метода в котором запрошены неявные параметры. -* Затем он ищет членов, помеченных как implicit во всех объектах компаньонах, связанных с типом неявного параметра. +Лучше всего вначале показать это на небольшом примере. +Мы определяем интерфейс `Comparator[A]`, который может сравнивать элементы типа `A`, +и предоставляем две реализации для `Int`-ов и `String`-ов. +Затем мы определяем метод `max[A](x: A, y: A)`, который возвращает больший из двух аргументов. +Так как `x` и `y` имеют абстрактный тип, в общем случае мы не знаем, как их сравнивать, но можем запросить соответствующий компаратор. +Поскольку обычно для любого заданного типа существует канонический компаратор `A`, +то мы можем объявить их как _заданные_ (_given_) или _неявно_ (_implicitly_) доступные. -Более подробное руководство, о том где scala ищет неявные значения можно найти в [FAQ](//docs.scala-lang.org/tutorials/FAQ/finding-implicits.html) +{% tabs implicits-comparator class=tabs-scala-version %} -В следующем примере мы определяем метод `sum`, который вычисляет сумму элементов списка, используя операции `add` и `unit` моноида. Обратите внимание, что неявные значения не могут находится выше уровнем. +{% tab 'Scala 2' for=implicits-comparator %} ```scala mdoc -abstract class Monoid[A] { - def add(x: A, y: A): A - def unit: A +trait Comparator[A] { + def compare(x: A, y: A): Int } -object ImplicitTest { - implicit val stringMonoid: Monoid[String] = new Monoid[String] { - def add(x: String, y: String): String = x concat y - def unit: String = "" - } - - implicit val intMonoid: Monoid[Int] = new Monoid[Int] { - def add(x: Int, y: Int): Int = x + y - def unit: Int = 0 +object Comparator { + implicit object IntComparator extends Comparator[Int] { + def compare(x: Int, y: Int): Int = Integer.compare(x, y) } - - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - def main(args: Array[String]): Unit = { - println(sum(List(1, 2, 3))) // использует intMonoid неявно - println(sum(List("a", "b", "c"))) // использует stringMonoid неявно + + implicit object StringComparator extends Comparator[String] { + def compare(x: String, y: String): Int = x.compareTo(y) } } + +def max[A](x: A, y: A)(implicit comparator: Comparator[A]): A = + if (comparator.compare(x, y) >= 0) x + else y + +println(max(10, 6)) // 10 +println(max("hello", "world")) // world ``` -`Monoid` определяет здесь операцию под названием `add`, которая сочетает два элемента типа `A` и возвращает сумму типа `A`, операция `unit` позволяет вернуть отдельный (специфичный) элемент типа `A`. +```scala mdoc:fail +// не компилируется: +println(max(false, true)) +// ^ +// error: could not find implicit value for parameter comparator: Comparator[Boolean] +``` + +Параметр `comparator` автоматически заполняется значением `Comparator.IntComparator` для `max(10, 6)` +и `Comparator.StringComparator` для `max("hello", "world")`. +Поскольку нельзя найти неявный `Comparator[Boolean]`, вызов `max(false, true)` не компилируется. -Чтобы показать, как работают неявные параметры, сначала определим моноиды `stringMonoid` и `intMonoid` для строк и целых чисел, соответственно. Ключевое слово `implicit` указывает на то, что этот объект может быть использован неявно. +{% endtab %} -Метод `sum` принимает `List[A]` и возвращает `A`, который берет начальное `A` из `unit` и объединяет каждое следующее `A` в списке используя `add` метод. Указание параметра `m` в качестве неявного параметра подразумевает, что `xs` параметр будет обеспечен тогда, когда при вызове параметра метода Scala сможет найти неявный `Monoid[A]` чтоб его передать в качестве параметра `m`. +{% tab 'Scala 3' for=implicits-comparator %} -В нашем `main` методе мы вызываем `sum` дважды и предоставляем только `xs` параметр. Теперь Scala будет искать неявное значение в указанных ранее областях видимости. Первый вызов `sum` проходит с использованием `List[Int]` в качестве `xs`, это означает, что элемент `A` имеет тип `Int`. Неявный список параметров с `m` опущен, поэтому Scala будет искать неявное значение типа `Monoid[Int]`. Первое правило поиска гласит +```scala +trait Comparator[A]: +def compare(x: A, y: A): Int -> Скала сначала будет искать неявные параметры, доступ к которым можно получить напрямую (без префикса) в месте вызова метода в котором запрошены неявные параметры. +object Comparator: +given Comparator[Int] with +def compare(x: Int, y: Int): Int = Integer.compare(x, y) -`intMonoid` - это задание неявного значения, доступ к которому можно получить непосредственно в `main`. Оно имеет подходящий тип, поэтому передается методу `sum` автоматически. +given Comparator[String] with +def compare(x: String, y: String): Int = x.compareTo(y) +end Comparator -Второй вызов `sum` проходит используя `List[String]`, что означает, что `A` - это `String`. Неявный поиск будет идти так же, как и в случае с `Int`, но на этот раз будет найден `stringMonoid`, и передан автоматически в качестве `m`. +def max[A](x: A, y: A)(using comparator: Comparator[A]): A = + if comparator.compare(x, y) >= 0 then x + else y -Программа выведет на экран +println(max(10, 6)) // 10 +println(max("hello", "world")) // world ``` -6 -abc + +```scala +// не компилируется: +println(max(false, true)) +-- Error: ---------------------------------------------------------------------- +1 |println(max(false, true)) + | ^ + |no given instance of type Comparator[Boolean] was found for parameter comparator of method max ``` + +Параметр `comparator` автоматически заполняется значением `given Comparator[Int]` для `max(10, 6)` +и `given Comparator[String]` для `max("hello", "world")`. +Поскольку нельзя найти `given Comparator[Boolean]`, вызов `max(false, true)` не компилируется. + +{% endtab %} + +{% endtabs %} + +Места, где Scala будет искать эти параметры, делятся на две категории: + +- Вначале Scala будет искать `given` параметры, доступ к которым можно получить напрямую (без префикса) в месте вызова `max`. +- Затем он ищет членов, помеченных как given/implicit во всех объектах компаньонах, + связанных с типом неявного параметра (например: `object Comparator` для типа-кандидата `Comparator[Int]`). + +Более подробное руководство, о том где scala ищет неявные значения можно найти в [FAQ](/tutorials/FAQ/finding-implicits.html) diff --git a/_ru/tour/inner-classes.md b/_ru/tour/inner-classes.md index 736677ceff..15a1a2882d 100644 --- a/_ru/tour/inner-classes.md +++ b/_ru/tour/inner-classes.md @@ -1,27 +1,25 @@ --- layout: tour title: Внутренние классы - -discourse: true - partof: scala-tour - num: 22 language: ru next-page: abstract-type-members previous-page: lower-type-bounds - --- -В Scala классам можно иметь в качестве членов другие классы. В отличие от Java-подобных языков, где такие внутренние классы являются членами окружающего класса, в Scala такие внутренние классы привязаны к содержащему его объекту. Предположим, мы хотим, чтобы компилятор не позволял нам на этапе компиляции смешивать узлы этого графа. Для решения этой задачи нам подойдут типы, зависящие от своего расположения. +В Scala классам можно иметь в качестве членов другие классы. В отличие от Java-подобных языков, где такие внутренние классы являются членами окружающего класса, в Scala такие внутренние классы привязаны к содержащему его объекту. Предположим, мы хотим, чтобы компилятор не позволял нам на этапе компиляции смешивать узлы этого графа. Для решения этой задачи нам подойдут типы, зависящие от своего расположения. Чтобы проиллюстрировать суть подхода, мы быстро набросаем реализацию такого графа: +{% tabs inner-classes_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_1 %} + ```scala mdoc class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -35,8 +33,33 @@ class Graph { } } ``` + +{% endtab %} +{% tab 'Scala 3' for=inner-classes_1 %} + +```scala +class Graph: + class Node: + var connectedNodes: List[Node] = Nil + def connectTo(node: Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` + +{% endtab %} +{% endtabs %} + Данная программа представляет собой граф в составленного из списка узлов (`List[Node]`). Каждый узел имеет список других узлов, с которым он связан (`connectedNodes`). Класс `Node` является _зависимым от месторасположения типом_, поскольку он вложен в `Class Graph`. Поэтому все узлы в `connectedNodes` должны быть созданы с использованием `newNode` из одного и того же экземпляра `Graph`. +{% tabs inner-classes_2 %} +{% tab 'Scala 2 и 3' for=inner-classes_2 %} + ```scala mdoc val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -45,12 +68,19 @@ val node3: graph1.Node = graph1.newNode node1.connectTo(node2) node3.connectTo(node1) ``` + +{% endtab %} +{% endtabs %} + Мы явно объявили тип `node1`, `node2` и `node3` как `graph1.Node` для ясности, хотя компилятор мог определить это самостоятельно. Это потому, что когда мы вызываем `graph1.newNode`, вызывающий `new Node`, метод использует экземпляр `Node`, специфичный экземпляру `graph1`. Если у нас есть два графа, то система типов Scala не позволит смешивать узлы, определенные в рамках одного графа, с узлами другого, так как узлы другого графа имеют другой тип. Вот некорректная программа: -```scala:nest +{% tabs inner-classes_3 %} +{% tab 'Scala 2 и 3' for=inner-classes_3 %} + +```scala mdoc:fail val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode val node2: graph1.Node = graph1.newNode @@ -59,13 +89,20 @@ val graph2: Graph = new Graph val node3: graph2.Node = graph2.newNode node1.connectTo(node3) // не работает! ``` + +{% endtab %} +{% endtabs %} + Тип `graph1.Node` отличается от типа `graph2.Node`. В Java последняя строка в предыдущем примере программы была бы правильной. Для узлов обоих графов Java будет присваивать один и тот же тип `Graph.Node`, т.е. `Node` имеет префикс класса `Graph`. В Скале такой тип также может быть выражен, он записывается `Graph#Node`. Если мы хотим иметь возможность соединять узлы разных графов, то вам нужно изменить описание первоначальной реализации графов следующим образом: +{% tabs inner-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_4 %} + ```scala mdoc:nest class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -79,3 +116,24 @@ class Graph { } } ``` + +{% endtab %} +{% tab 'Scala 3' for=inner-classes_4 %} + +```scala +class Graph: + class Node: + var connectedNodes: List[Graph#Node] = Nil + def connectTo(node: Graph#Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/lower-type-bounds.md b/_ru/tour/lower-type-bounds.md index 8351695a72..3d3b22edb9 100644 --- a/_ru/tour/lower-type-bounds.md +++ b/_ru/tour/lower-type-bounds.md @@ -1,70 +1,115 @@ --- layout: tour title: Нижнее Ограничение Типа - -discourse: true - partof: scala-tour - num: 21 language: ru next-page: inner-classes previous-page: upper-type-bounds prerequisite-knowledge: upper-type-bounds, generics, variance - --- -В то время как [верхнее ограничение типа](upper-type-bounds.html) ограничивает тип до подтипа стороннего типа, *нижнее ограничение типа* объявляют тип супертипом стороннего типа. Термин `B >: A` выражает, то что параметр типа `B` или абстрактный тип `B` относится к супертипу типа `A`. В большинстве случаев `A` будет задавать тип класса, а `B` задавать тип метода. +В то время как [верхнее ограничение типа](upper-type-bounds.html) ограничивает тип до подтипа стороннего типа, _нижнее ограничение типа_ объявляют тип супертипом стороннего типа. Термин `B >: A` выражает, то что параметр типа `B` или абстрактный тип `B` относится к супертипу типа `A`. В большинстве случаев `A` будет задавать тип класса, а `B` задавать тип метода. Вот пример, где это полезно: +{% tabs upper-type-bounds_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_1 %} + ```scala mdoc:fail -trait Node[+B] { - def prepend(elem: B): Node[B] +trait List[+A] { + def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) } -case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { - def prepend(elem: B): ListNode[B] = ListNode(elem, this) - def head: B = h - def tail: Node[B] = t -} +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] -case class Nil[+B]() extends Node[B] { - def prepend(elem: B): ListNode[B] = ListNode(elem, this) -} +object Nil extends List[Nothing] +``` + +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_1 %} + +```scala +trait List[+A]: + def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] ``` -В данной программе реализован связанный список. `Nil` представляет пустой список. Класс `ListNode` - это узел, который содержит элемент типа `B` (`head`) и ссылку на остальную часть списка (`tail`). Класс `Node` и его подтипы ковариантны, потому что у нас указанно `+B`. +{% endtab %} +{% endtabs %} -Однако эта программа _не компилируется_, потому что параметр `elem` в `prepend` имеет тип `B`, который мы объявили *ко*вариантным. Так это не работает, потому что функции *контр*вариантны в типах своих параметров и *ко*вариантны в типах своих результатов. +В данной программе реализован связанный список. `Nil` представляет пустой список. Класс `NonEmptyList` - это узел, который содержит элемент типа `A` (`head`) и ссылку на остальную часть списка (`tail`). `trait List` и его подтипы ковариантны, потому что у нас указанно `+A`. -Чтобы исправить это, необходимо перевернуть вариантность типа параметра `elem` в `prepend`. Для этого мы вводим новый тип для параметра `U`, у которого тип `B` указан в качестве нижней границы типа. +Однако эта программа _не компилируется_, потому что параметр `elem` в `prepend` имеет тип `A`, который мы объявили *ко*вариантным. Так это не работает, потому что функции *контр*вариантны в типах своих параметров и *ко*вариантны в типах своих результатов. + +Чтобы исправить это, необходимо перевернуть вариантность типа параметра `elem` в `prepend`. Для этого мы вводим новый тип для параметра `B`, у которого тип `A` указан в качестве нижней границы типа. + +{% tabs upper-type-bounds_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_2 %} ```scala mdoc -trait Node[+B] { - def prepend[U >: B](elem: U): Node[U] +trait List[+A] { + def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) } -case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { - def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) - def head: B = h - def tail: Node[B] = t -} +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] -case class Nil[+B]() extends Node[B] { - def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) -} +object Nil extends List[Nothing] ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_2 %} + +```scala +trait List[+A]: + def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] +``` + +{% endtab %} +{% endtabs %} + Теперь мы можем сделать следующее: + +{% tabs upper-type-bounds_3 %} +{% tab 'Scala 2 и 3' for=upper-type-bounds_3 %} + ```scala mdoc trait Bird case class AfricanSwallow() extends Bird case class EuropeanSwallow() extends Bird +val africanSwallows: List[AfricanSwallow] = Nil.prepend(AfricanSwallow()) +val swallowsFromAntarctica: List[Bird] = Nil +val someBird: Bird = EuropeanSwallow() -val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) -val birdList: Node[Bird] = africanSwallowList -birdList.prepend(EuropeanSwallow()) +// присвоить птицам (birds) африканских ласточек (swallows) +val birds: List[Bird] = africanSwallows + +// добавляем новую птицу к африканским ласточкам, `B` - это `Bird` +val someBirds = africanSwallows.prepend(someBird) + +// добавляем европейскую ласточку к птицам +val moreBirds = birds.prepend(EuropeanSwallow()) + +// соединяем вместе различных ласточек, `B` - это `Bird`, потому что это общий супертип для обоих типов ласточек +val allBirds = africanSwallows.prepend(EuropeanSwallow()) + +// но тут ошибка! добавление списка птиц слишком расширяет тип аргументов. -Xlint предупредит! +val error = moreBirds.prepend(swallowsFromAntarctica) // List[Object] ``` -`Node[Bird]` может быть присвоен `africanSwallowList` , но затем может добавлять и `EuropeanSwallow`. + +{% endtab %} +{% endtabs %} + +Параметр ковариантного типа позволяет `birds` получать значение `africanSwallows`. + +Тип, связанный с параметром типа `prepend`, позволяет добавлять различные разновидности ласточек и получать более абстрактный тип: вместо `List[AfricanSwallow]`, мы получаем `List[Bird]`. + +Используйте `-Xlint`, чтобы предупредить, если аргумент предполагаемого типа слишком абстрактен. diff --git a/_ru/tour/mixin-class-composition.md b/_ru/tour/mixin-class-composition.md index ff7c1d60d9..d8cbb24f30 100644 --- a/_ru/tour/mixin-class-composition.md +++ b/_ru/tour/mixin-class-composition.md @@ -1,20 +1,20 @@ --- layout: tour -title: Композиция классов с примесями - -discourse: true - +title: Композиция классов с трейтами partof: scala-tour - num: 7 language: ru next-page: higher-order-functions previous-page: tuples prerequisite-knowledge: inheritance, traits, abstract-classes, unified-types - --- + Примеси (Mixin) - это трейты, которые используются для создания класса. +{% tabs mixin-first-exemple class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-first-exemple %} + ```scala mdoc abstract class A { val message: String @@ -31,10 +31,44 @@ val d = new D println(d.message) // I'm an instance of class B println(d.loudMessage) // I'M AN INSTANCE OF CLASS B ``` -У класса `D` есть суперкласс `B` и примесь `C`. Классы могут иметь только один суперкласс, но много примесей (используя ключевыое слово `extends` и `with` соответственно). Примеси и суперкласс могут иметь один и тот же супертип. + +У класса `D` есть суперкласс `B` и трейт `C`. +Классы могут иметь только один суперкласс, но много трейтов (используя ключевое слово `extends` и `with` соответственно). +Трейты и суперкласс могут иметь один и тот же супертип. + +{% endtab %} + +{% tab 'Scala 3' for=mixin-first-exemple %} + +```scala +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, C + +val d = D() +println(d.message) // I'm an instance of class B +println(d.loudMessage) // I'M AN INSTANCE OF CLASS B +``` + +У класса `D` есть суперкласс `B` и трейт `C`. +Классы могут иметь только один суперкласс, но много трейтов +(используя ключевое слово `extends` и разделитель `,` соответственно). +Трейты и суперкласс могут иметь один и тот же супертип. + +{% endtab %} + +{% endtabs %} Теперь давайте рассмотрим более интересный пример, начиная с абстрактного класса: +{% tabs mixin-abstract-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-abstract-iterator %} + ```scala mdoc abstract class AbsIterator { type T @@ -42,10 +76,30 @@ abstract class AbsIterator { def next(): T } ``` + +{% endtab %} + +{% tab 'Scala 3' for=mixin-abstract-iterator %} + +```scala +abstract class AbsIterator: + type T + def hasNext: Boolean + def next(): T +``` + +{% endtab %} + +{% endtabs %} + Класс имеет абстрактный тип `T` и методы стандартного итератора. Далее создаем конкретную реализацию класса (все абстрактные члены `T`, `hasNext`, и `next` должны быть реализованы): +{% tabs mixin-concrete-string-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-concrete-string-iterator %} + ```scala mdoc class StringIterator(s: String) extends AbsIterator { type T = Char @@ -58,26 +112,87 @@ class StringIterator(s: String) extends AbsIterator { } } ``` + +{% endtab %} + +{% tab 'Scala 3' for=mixin-concrete-string-iterator %} + +```scala +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 +``` + +{% endtab %} + +{% endtabs %} + `StringIterator` принимает `String` и может быть использован для обхода по строке (например, чтоб проверить содержит ли строка определенный символ). Теперь давайте создадим трейт который тоже наследуется от `AbsIterator`. +{% tabs mixin-extended-abstract-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-extended-abstract-iterator %} + ```scala mdoc trait RichIterator extends AbsIterator { def foreach(f: T => Unit): Unit = while (hasNext) f(next()) } ``` -У этого трейта реализован метод `foreach` который постоянно вызывает переданную ему функцию `f: T => Unit` на каждом новом элементе (`next()`) до тех пор пока в итераторе содержатся элементы (`while (hasNext)`). Поскольку `RichIterator` это трейт, ему не нужно реализовывать членов абстрактного класса `AbsIterator`. -Мы бы хотели объединить функциональность `StringIterator` и `RichIterator` в один класс. +У этого трейта реализован метод `foreach`, который постоянно вызывает переданную ему функцию `f: T => Unit` +на каждом новом элементе (`next()`) до тех пор пока в итераторе содержатся элементы (`while (hasNext)`). +Поскольку `RichIterator` - это трейт, ему не нужно реализовывать элементы абстрактного класса `AbsIterator`. + +{% endtab %} + +{% tab 'Scala 3' for=mixin-extended-abstract-iterator %} + +```scala +trait RichIterator extends AbsIterator: + def foreach(f: T => Unit): Unit = while hasNext do f(next()) +``` + +У этого трейта реализован метод `foreach`, который постоянно вызывает переданную ему функцию `f: T => Unit` +на каждом новом элементе (`next()`) до тех пор пока в итераторе содержатся элементы (`while hasNext`). +Поскольку `RichIterator` - это трейт, ему не нужно реализовывать элементы абстрактного класса `AbsIterator`. + +{% endtab %} + +{% endtabs %} + +Мы бы хотели объединить функциональность `StringIterator` и `RichIterator` в один класс. + +{% tabs mixin-combination-class class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-combination-class %} ```scala mdoc -object StringIteratorTest extends App { - class RichStringIter extends StringIterator("Scala") with RichIterator - val richStringIter = new RichStringIter - richStringIter foreach println -} +class RichStringIter extends StringIterator("Scala") with RichIterator +val richStringIter = new RichStringIter +richStringIter.foreach(println) +``` + +{% endtab %} + +{% tab 'Scala 3' for=mixin-combination-class %} + +```scala +class RichStringIter extends StringIterator("Scala"), RichIterator +val richStringIter = RichStringIter() +richStringIter.foreach(println) ``` -Новый класс `RichStringIter` включает `StringIterator` как суперкласс и `RichIterator` как примесь. -Используя только одиночное наследование мы бы не могли добиться того же уровня гибкости. +{% endtab %} + +{% endtabs %} + +Новый класс `RichStringIter` включает `StringIterator` как суперкласс и `RichIterator` как трейт. + +Используя только одиночное наследование мы бы не смогли добиться того же уровня гибкости. diff --git a/_ru/tour/multiple-parameter-lists.md b/_ru/tour/multiple-parameter-lists.md index c4656f5193..99f84700d5 100644 --- a/_ru/tour/multiple-parameter-lists.md +++ b/_ru/tour/multiple-parameter-lists.md @@ -1,89 +1,231 @@ --- layout: tour title: Множественные списки параметров (Каррирование) - -discourse: true - partof: scala-tour - num: 10 language: ru next-page: case-classes previous-page: nested-functions - --- -Методы могут объявляться с несколькими списками параметров. При этом когда такой метод вызывается с меньшим количеством списков параметров, это приводит к созданию новой функции, которая ожидает на вход не достающий список параметров. Формально это называется [частичное применение](https://en.wikipedia.org/wiki/Partial_application). +Методы могут объявляться с несколькими списками параметров. -Например, - -```scala mdoc -val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) -val numberFunc = numbers.foldLeft(List[Int]()) _ +### Пример -val squares = numberFunc((xs, x) => xs :+ x*x) -print(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) +Вот пример, определенный для трейта `Iterable` из API Scala коллекций: -val cubes = numberFunc((xs, x) => xs :+ x*x*x) -print(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +{% tabs foldLeft_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=foldLeft_definition %} + +```scala +trait Iterable[A] { + ... + def foldLeft[B](z: B)(op: (B, A) => B): B + ... +} ``` -Рассмотрим такие примеры из класса [Traversable](/overviews/collections/trait-traversable.html) коллекции Scala: +{% endtab %} -```scala mdoc:fail +{% tab 'Scala 3' for=foldLeft_definition %} + +```scala +trait Iterable[A]: +... def foldLeft[B](z: B)(op: (B, A) => B): B +... ``` -`foldLeft` применяет бинарный оператор `op` к начальному значению `z` и ко всем остальным элементам коллекции слева направо. Ниже приведен пример его использования. +{% endtab %} -Начиная с начального значения 0, `foldLeft` применяет функцию `(m, n) => m + n` к каждому элементу списка и предыдущему накопленному значению. +{% endtabs %} -```scala mdoc:nest +`foldLeft` применяет бинарный оператор `op` к начальному значению `z` и ко всем остальным элементам коллекции слева направо. +Ниже приведен пример его использования. + +Начиная с начального значения `0`, `foldLeft` применяет функцию `(m, n) => m + n` к каждому элементу списка +и предыдущему накопленному значению. + +{% tabs foldLeft_use %} + +{% tab 'Scala 2 и 3' for=foldLeft_use %} + +```scala mdoc val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(0)((m, n) => m + n) -print(res) // 55 +println(res) // 55 ``` -Множественные списки параметров имеют избыточный синтаксис, поэтому их следует использовать экономно. Можем предложить следующие варианты для использования множественных списков (каррирования): +{% endtab %} -#### Отдельный функциональный параметр - Функцию `op` можно выделить в отдельный функциональный параметр у `foldLeft`, благодаря такому выделению становится возможен более элегантный стиль передачи анонимной функции в метод. Без такого выделения код выглядел бы следующим образом: -```scala -numbers.foldLeft(0, {(m: Int, n: Int) => m + n}) +{% endtabs %} + +### Варианты для использования + +Предлагаемые варианты для использования множественных списков параметров включают: + +#### Вывод типа + +Исторически сложилось, что в Scala вывод типов происходит по одному списку параметров за раз. +Скажем, у вас есть следующий метод: + +{% tabs foldLeft1_definition %} + +{% tab 'Scala 2 и 3' for=foldLeft1_definition %} + +```scala mdoc +def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ??? +``` + +{% endtab %} + +{% endtabs %} + +Затем при желании вызвать его следующим образом, можно обнаружить, что метод не компилируется: + +{% tabs foldLeft1_wrong_use %} + +{% tab 'Scala 2 и 3' for=foldLeft1_wrong_use %} + +```scala mdoc:fail +def notPossible = foldLeft1(numbers, 0, _ + _) ``` - - Обратите внимание, что использование отдельного функционального параметра позволяет нам использовать автоматическое выведение типа для него, что делает код еще более кратким, это было бы невозможно без каррирования. - + +{% endtab %} + +{% endtabs %} + +вам нужно будет вызвать его одним из следующих способов: + +{% tabs foldLeft1_good_use %} + +{% tab 'Scala 2 и 3' for=foldLeft1_good_use %} + ```scala mdoc -numbers.foldLeft(0)(_ + _) +def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _) +def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b) ``` - Если в утверждении `numbers.foldLeft(0)(_ + _)` зафиксировать отдельный параметр `z`, мы получим частично определенную функцию, которую можно переиспользовать, как показано ниже: -```scala mdoc:nest -val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) -val numberFunc = numbers.foldLeft(List[Int]())_ // z = Empty.List[Int] -val squares = numberFunc((xs, x) => xs:+ x*x) -print(squares.toString()) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) +{% endtab %} + +{% endtabs %} -val cubes = numberFunc((xs, x) => xs:+ x*x*x) -print(cubes.toString()) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +Это связано с тем, что Scala не может вывести тип функции `_ + _`, так как она все еще выводит `A` и `B`. +Путем перемещения параметра `op` в собственный список параметров, `A` и `B` выводятся в первом списке. +Затем эти предполагаемые типы будут доступны для второго списка параметров +и `_ + _` станет соответствовать предполагаемому типу `(Int, Int) => Int`. + +{% tabs foldLeft2_definition_and_use %} + +{% tab 'Scala 2 и 3' for=foldLeft2_definition_and_use %} + +```scala mdoc +def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ??? +def possible = foldLeft2(numbers, 0)(_ + _) +``` + +{% endtab %} + +{% endtabs %} + +Последнее определение метода не нуждается в подсказках типа и может вывести все типы своих параметров. + +#### Неявные параметры + +Чтоб указать что параметр используется [_неявно_ (_implicit_)](/ru/tour/implicit-parameters.html) +необходимо задавать несколько списков параметров. +Примером может служить следующее: + +{% tabs execute_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=execute_definition %} + +```scala mdoc +def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ??? +``` + +{% endtab %} + +{% tab 'Scala 3' for=execute_definition %} + +```scala +def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ??? ``` - `foldLeft` и `foldRight` может быть использован в любой из следующих вариаций, +{% endtab %} + +{% endtabs %} + +#### Частичное применение + +Методы могут объявляться с несколькими списками параметров. +При этом когда такой метод вызывается с меньшим количеством списков параметров, +это приводит к созданию новой функции, +которая ожидает на вход недостающий список параметров. +Формально это называется [частичное применение](https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5). + +Например, + +{% tabs foldLeft_partial %} + +{% tab 'Scala 2 и 3' for=foldLeft_partial %} + ```scala mdoc:nest val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +val numberFunc = numbers.foldLeft(List[Int]()) _ -numbers.foldLeft(0)((sum, item) => sum + item) // Общая Форма -numbers.foldRight(0)((sum, item) => sum + item) // Общая Форма +val squares = numberFunc((xs, x) => xs :+ x*x) +println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) -numbers.foldLeft(0)(_+_) // Форма с каррированием -numbers.foldRight(0)(_+_) // Форма с каррированием +val cubes = numberFunc((xs, x) => xs :+ x*x*x) +println(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) ``` - -#### Неявные параметры - Чтоб указать что параметр используется неявно (`implicit`) необходимо задавать несколько списков параметров. Примером может служить следующее: +{% endtab %} -```scala -def execute(arg: Int)(implicit ec: ExecutionContext) = ??? +{% endtabs %} + +### Сравнение с «каррированием» + +Иногда можно встретить, что метод с несколькими списками параметров называется «каррированный». + +Как говорится [в статье на Википедии о каррировании](https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), + +> Каррирование — преобразование функции от многих аргументов в набор вложенных функций, +> каждая из которых является функцией от одного аргумента. + +Мы не рекомендуем использовать слово «каррирование» в отношении множественных списков параметров Scala по двум причинам: + +1. В Scala множественные параметры и множественные списки параметров задаются + и реализуются непосредственно как часть языка, а не преобразуются из функций с одним параметром. + +2. Существует опасность путаницы с методами из стандартной Scala библиотеки + [`curried`]() + и [`uncurried`](), + которые вообще не включают множественные списки параметров. + +Тем не менее, несомненно, есть сходство между множественными списками параметров и каррированием. +Хотя они различаются в месте определения, +в месте вызова могут тем не менее выглядеть одинаково, как в этом примере: + +{% tabs about_currying %} + +{% tab 'Scala 2 и 3' for=about_currying %} + +```scala mdoc +// версия с множественными списками параметров +def addMultiple(n1: Int)(n2: Int) = n1 + n2 +// два различных способа получить каррированную версию +def add(n1: Int, n2: Int) = n1 + n2 +val addCurried1 = (add _).curried +val addCurried2 = (n1: Int) => (n2: Int) => n1 + n2 +// независимо от определения, вызов всех трех идентичен +addMultiple(3)(4) // 7 +addCurried1(3)(4) // 7 +addCurried2(3)(4) // 7 ``` + +{% endtab %} + +{% endtabs %} diff --git a/_ru/tour/named-arguments.md b/_ru/tour/named-arguments.md index 32b83664e4..da9202a051 100644 --- a/_ru/tour/named-arguments.md +++ b/_ru/tour/named-arguments.md @@ -1,34 +1,61 @@ --- layout: tour title: Именованные Аргументы - -discourse: true - partof: scala-tour - num: 34 language: ru next-page: packages-and-imports previous-page: default-parameter-values prerequisite-knowledge: function-syntax - --- При вызове методов можно конкретно указывать название задаваемого аргумента следующим образом: +{% tabs named-arguments-when-good %} + +{% tab 'Scala 2 и 3' for=named-arguments-when-good %} + ```scala mdoc -def printName(first: String, last: String): Unit = { - println(first + " " + last) -} +def printName(first: String, last: String): Unit = + println(s"$first $last") -printName("John", "Smith") // Prints "John Smith" -printName(first = "John", last = "Smith") // Prints "John Smith" -printName(last = "Smith", first = "John") // Prints "John Smith" +printName("John", "Public") // выводит "John Public" +printName(first = "John", last = "Public") // выводит "John Public" +printName(last = "Public", first = "John") // выводит "John Public" +printName("Elton", last = "John") // выводит "Elton John" ``` -Обратите внимание, что при указании имени параметра, порядок аргумента может быть изменен. Однако если какие-то аргументы именованные, а другие нет, то аргументы без имени должны стоять на первом месте и располагаться в том порядке, в котором описаны параметры метода. + +{% endtab %} + +{% endtabs %} + +Это полезно, когда два параметра имеют один и тот же тип и аргументы могут быть случайно перепутаны. + +Обратите внимание, что именованные аргументы могут быть указаны в любом порядке. +Однако, если аргументы расположены не в порядке параметров метода (читается слева направо), +остальные аргументы должны быть названы. + +В следующем примере именованные аргументы позволяют опустить параметр `middle`. +В случае ошибки, если первый аргумент не на своем месте, необходимо будет указать второй аргумент. + +{% tabs named-arguments-when-error %} + +{% tab 'Scala 2 и 3' for=named-arguments-when-error %} ```scala mdoc:fail -printName(last = "Smith", "john") // ошибка: позиция после именованного аргумента +def printFullName(first: String, middle: String = "Q.", last: String): Unit = + println(s"$first $middle $last") + +printFullName(first = "John", last = "Public") // выводит "John Q. Public" +printFullName("John", last = "Public") // выводит "John Q. Public" +printFullName("John", middle = "Quincy", "Public") // выводит "John Quincy Public" +printFullName(last = "Public", first = "John") // выводит "John Q. Public" +printFullName(last = "Public", "John") // ошибка: позиция после именованного аргумента ``` -Обратите внимание, что именованные аргументы не работают при вызове Java методов. +{% endtab %} + +{% endtabs %} + +Именованные аргументы работают при вызове Java методов, но только в том случае, +если используемая Java библиотека была скомпилирована с флагом `-parameters`. diff --git a/_ru/tour/nested-functions.md b/_ru/tour/nested-functions.md index 663e0fdc36..8bc05a9d70 100644 --- a/_ru/tour/nested-functions.md +++ b/_ru/tour/nested-functions.md @@ -1,34 +1,55 @@ --- layout: tour title: Вложенные Методы - -discourse: true - partof: scala-tour - num: 9 language: ru next-page: multiple-parameter-lists previous-page: higher-order-functions - --- В Scala возможно объявление метода вкладывать в тело другого метода. Это реализовано в следующем примере, в котором метод `factorial` используется для вычисления факториала заданного числа: -{% scalafiddle %} +{% tabs Nested_functions_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=Nested_functions_definition %} + ```scala mdoc - 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)) +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)) +``` + +{% endtab %} + +{% tab 'Scala 3' for=Nested_functions_definition %} + +```scala +def factorial(x: Int): Int = + def fact(x: Int, accumulator: Int): Int = + if x <= 1 then accumulator + else fact(x - 1, x * accumulator) + fact(x, 1) + +println("Factorial of 2: " + factorial(2)) +println("Factorial of 3: " + factorial(3)) + ``` -{% endscalafiddle %} + +{% endtab %} + +{% endtabs %} + +{% tabs Nested_functions_result %} + +{% tab 'Scala 2 и 3' for=Nested_functions_result %} Результат выполнения программы: @@ -36,3 +57,7 @@ previous-page: higher-order-functions Factorial of 2: 2 Factorial of 3: 6 ``` + +{% endtab %} + +{% endtabs %} diff --git a/_ru/tour/operators.md b/_ru/tour/operators.md index 09ea35ea76..c8ec47bb3e 100644 --- a/_ru/tour/operators.md +++ b/_ru/tour/operators.md @@ -1,30 +1,45 @@ --- layout: tour title: Операторы - -discourse: true - partof: scala-tour - num: 30 language: ru next-page: by-name-parameters previous-page: type-inference prerequisite-knowledge: case-classes - --- + В Скале операторы - это обычные методы. В качестве _инфиксного оператора_ может быть использован любой метод с одним параметром. Например, `+` может вызываться с использованием точки: + +{% tabs operators_1 %} +{% tab 'Scala 2 и 3' for=operators_1 %} + ``` 10.+(1) ``` +{% endtab %} +{% endtabs %} + Однако легче воспринимать код, когда такие методы записаны как инфиксный оператор: + +{% tabs operators_2 %} +{% tab 'Scala 2 и 3' for=operators_2 %} + ``` 10 + 1 ``` +{% endtab %} +{% endtabs %} + ## Создание и использование операторов + В качестве оператора можно использовать любой допустимый символ. Включая имена на подобии `add` или символ (символы) типа `+`. + +{% tabs operators_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_3 %} + ```scala mdoc case class Vec(x: Double, y: Double) { def +(that: Vec) = Vec(this.x + that.x, this.y + that.y) @@ -37,8 +52,30 @@ val vector3 = vector1 + vector2 vector3.x // 3.0 vector3.y // 3.0 ``` + +{% endtab %} +{% tab 'Scala 3' for=operators_3 %} + +```scala +case class Vec(x: Double, y: Double): + def +(that: Vec) = 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 +``` + +{% endtab %} +{% endtabs %} + У класса Vec есть метод `+`, который мы использовали для добавления `vector1` и `vector2`. Используя круглые скобки, можно строить сложные выражения с читаемым синтаксисом. Пример создания класса `MyBool`, которое включает в себя методы `and` и `or` +{% tabs operators_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_4 %} + ```scala mdoc case class MyBool(x: Boolean) { def and(that: MyBool): MyBool = if (x) that else this @@ -47,17 +84,38 @@ case class MyBool(x: Boolean) { } ``` +{% endtab %} +{% tab 'Scala 3' for=operators_4 %} + +```scala +case class MyBool(x: Boolean): + def and(that: MyBool): MyBool = if x then that else this + def or(that: MyBool): MyBool = if x then this else that + def negate: MyBool = MyBool(!x) +``` + +{% endtab %} +{% endtabs %} + Теперь можно использовать операторы `and` и `or` в качестве инфиксных операторов: +{% tabs operators_5 %} +{% tab 'Scala 2 и 3' for=operators_5 %} + ```scala mdoc def not(x: MyBool) = x.negate def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) ``` +{% endtab %} +{% endtabs %} + Это помогает сделать объявление `xor` более читабельным. ## Порядок очередности + Когда выражение использует несколько операторов, операторы оцениваются на основе приоритета первого символа. Таблица приоритетов символов: + ``` (символы которых нет снизу) * / % @@ -70,12 +128,29 @@ def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) | (буквы, $, _) ``` + Такой приоритет распространяется на любые функции, которые вы задаете. Например, следующее выражение: + +{% tabs operators_7 %} +{% tab 'Scala 2 и 3' for=operators_7 %} + ``` a + b ^? c ?^ d less a ==> b | c ``` + +{% endtab %} +{% endtabs %} + эквивалентно + +{% tabs operators_8 %} +{% tab 'Scala 2 и 3' for=operators_8 %} + ``` ((a + b) ^? (c ?^ d)) less ((a ==> b) | c) ``` + +{% endtab %} +{% endtabs %} + `?^` имеет высший приоритет, потому что начинается с символа `?`. Второй по старшинству приоритет имеет `+`, за которым следуют `==>`, `^?`, `|`, и `less`. diff --git a/_ru/tour/package-objects.md b/_ru/tour/package-objects.md index 1a0ba1b7f2..89c392ea46 100644 --- a/_ru/tour/package-objects.md +++ b/_ru/tour/package-objects.md @@ -1,28 +1,53 @@ --- layout: tour title: Объекты Пакета - -discourse: true - partof: scala-tour - num: 36 language: ru previous-page: packages-and-imports --- -# Объекты Пакета +Часто бывает удобно иметь определения, доступные для всего пакета, +когда не нужно придумывать имя для оболочки `object`, которая их содержит. -У каждого пакета может существовать связанный с этим пакетом объект (package object), общий для всех членов пакета. Такой объект может быть только один. Любые выражения, содержащиеся в объекте пакета, считаются членами самого пакета. +{% tabs pkg-obj-vs-top-lvl_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_1 %} -Объекты пакета могут содержать произвольные виды выражений, а не только переменные и методы. Например, они часто используются для хранения псевдонимов типа и наборов неявных преобразований доступных всему пакету. Объекты пакета могут также наследоваться от классов и трейтов Scala. +Scala 2 предоставляет _объекты пакета_ (_package objects_) в виде удобного контейнера, общего для всего пакета. + +Объекты пакета могут содержать произвольные виды выражений, а не только переменные и методы. +Например, они часто используются для хранения псевдонимов типа и наборов неявных преобразований доступных всему пакету. +Объекты пакета могут также наследоваться от классов и трейтов Scala. + +> В будущей версии Scala 3 объекты пакета будут удалены в пользу определений верхнего уровня. По соглашению, исходный код объекта пакета обычно помещается в файл под названием `package.scala`. +Каждому пакету разрешено иметь один объект пакета. +Любые выражения, содержащиеся в объекте пакета, считаются членами самого пакета. + +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_1 %} + +В Scala 3 любое определение может быть объявлено на верхнем уровне пакета. +Например, классы, перечисления, методы и переменные. + +Любые определения, размещенные на верхнем уровне пакета, считаются членами самого пакета. + +> В Scala 2 верхнеуровневый метод, определения типов и переменных должны были быть заключены в **объект пакета**. +> Их все еще можно использовать в Scala 3 для обратной совместимости. +> Вы можете увидеть, как они работают, переключая вкладки. + +{% endtab %} +{% endtabs %} + См. пример ниже. Предположим, есть старший класс `Fruit` и три наследуемых от него объекта `Fruit` в пакете. `gardening.fruits`: +{% tabs pkg-obj-vs-top-lvl_2 %} +{% tab 'Scala 2 и 3' for=pkg-obj-vs-top-lvl_2 %} + ``` // в файле gardening/fruits/Fruit.scala package gardening.fruits @@ -33,9 +58,15 @@ object Plum extends Fruit("Plum", "blue") object Banana extends Fruit("Banana", "yellow") ``` +{% endtab %} +{% endtabs %} + Теперь предположим, что мы хотим поместить переменную `planted` и метод `showFruit` непосредственно в пакет `gardening`. Вот как это делается: +{% tabs pkg-obj-vs-top-lvl_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_3 %} + ``` // в файле gardening/fruits/package.scala package gardening @@ -47,11 +78,31 @@ package object fruits { } ``` -Для примера, следующий объект `PrintPlanted` импортирует `planted` и `showFruit` точно так же, как с вариантом импорта класса `Fruit`, используя групповой стиль импорта пакета gardening.fruits: +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_3 %} + +``` +// в файле gardening/fruits/package.scala +package gardening.fruits + +val planted = List(Apple, Plum, Banana) +def showFruit(fruit: Fruit): Unit = + println(s"${fruit.name}s are ${fruit.color}") +``` + +{% endtab %} +{% endtabs %} + +Для примера, следующий объект `PrintPlanted` импортирует `planted` и `showFruit` точно так же, как с вариантом импорта класса `Fruit`, +используя групповой стиль импорта пакета `gardening.fruits`: + +{% tabs pkg-obj-vs-top-lvl_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_4 %} ``` // в файле PrintPlanted.scala import gardening.fruits._ + object PrintPlanted { def main(args: Array[String]): Unit = { for (fruit <- planted) { @@ -61,10 +112,58 @@ object PrintPlanted { } ``` -Объекты пакета ведут себя также, как и любые другие объекты. Это означает, что вы можете использовать наследование, при этом сразу нескольких трейтов: +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_4 %} ``` -package object fruits extends FruitAliases with FruitHelpers { - // здесь располагаются вспомогательные классы и переменные -} +// в файле PrintPlanted.scala +import gardening.fruits.* + +@main def printPlanted(): Unit = + for fruit <- planted do + showFruit(fruit) +``` + +{% endtab %} +{% endtabs %} + +### Объединение нескольких определений на уровне пакета + +Часто в вашем проекте может быть несколько повторно используемых определений, +заданных в различных модулях, которые вы хотите агрегировать на верхнем уровне пакета. + +Например, некоторые вспомогательные методы в трейте `FruitHelpers` +и некоторые псевдонимы терминов/типов в свойстве `FruitAliases`. +Вот как вы можете разместить все их определения на уровне пакета `fruit`: + +{% tabs pkg-obj-vs-top-lvl_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_5 %} + +Объекты пакета ведут себя также, как и любые другие объекты. +Это означает, что вы можете использовать наследование, при этом сразу нескольких трейтов: + ``` +package gardening + +// `fruits` наследует свои элементы от родителей. +package object fruits extends FruitAliases with FruitHelpers +``` + +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_5 %} + +В Scala 3 предпочтительно использовать `export` для объединения членов из нескольких объектов в единую область видимости. +Здесь мы определяем приватные объекты, которые смешиваются с вспомогательными трейтами, +а затем экспортируют их элементы на верхнем уровне: + +``` +package gardening.fruits + +private object FruitAliases extends FruitAliases +private object FruitHelpers extends FruitHelpers + +export FruitHelpers.*, FruitAliases.* +``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/packages-and-imports.md b/_ru/tour/packages-and-imports.md index 2a3e6ba6ff..4624fa7238 100644 --- a/_ru/tour/packages-and-imports.md +++ b/_ru/tour/packages-and-imports.md @@ -1,29 +1,35 @@ --- layout: tour title: Пакеты и Импорт - -discourse: true - partof: scala-tour - num: 35 language: ru previous-page: annotations next-page: package-objects --- -# Пакеты и Импорт +# Пакеты и Импорт + Scala использует пакеты для указания пространства имен, они позволяют создавать модульную структуру кода. ## Создание пакета + Пакеты создаются путем объявления одного или нескольких имен пакетов в верхней части файла Scala. +{% tabs packages-and-imports_1 %} +{% tab 'Scala 2 и 3' for=packages-and-imports_1 %} + ``` package users class User ``` + +{% endtab %} +{% endtabs %} + По соглашению пакеты называют тем же именем, что и каталог, содержащий файл Scala. Однако Scala не обращает внимания на расположение файлов. Структура каталогов sbt-проекта для `package users` может выглядеть следующим образом: + ``` - ExampleProject - build.sbt @@ -37,8 +43,15 @@ class User UserPreferences.scala - test ``` -Обратите внимание, что каталог `users` находится внутри каталога `scala` и как в пакете содержатся несколько файлов Scala. Каждый файл Scala в пакете может иметь одно и то же объявление пакета. Другой способ объявления пакетов - с помощью фигурных скобок: -``` + +Обратите внимание, что каталог `users` находится внутри каталога `scala` и как в пакете содержатся несколько файлов Scala. +Каждый файл Scala в пакете может иметь одно и то же объявление пакета. +Другой способ объявления пакетов - вложить их друг в друга:: + +{% tabs packages-and-imports_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_2 %} + +```scala package users { package administrators { class NormalUser @@ -48,18 +61,47 @@ package users { } } ``` + +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_2 %} + +```scala +package users: + package administrators: + class NormalUser + + package normalusers: + class NormalUser +``` + +{% endtab %} +{% endtabs %} + Как видите, такой способ позволяет вкладывать пакеты друг в друга, а также обеспечивает отличный контроль за областью видимости и возможностью изоляции. Имя пакета должно быть все в нижнем регистре, и если код разрабатывается в организации имеющей сайт, то следует использовать имя следующего формата: `<домен-верхнего-уровня>.<доменное-имя>.<название-проекта>`. Например, если бы у Google был проект под названием `SelfDrivingCar`, название пакета выглядело бы следующим образом: -``` + +{% tabs packages-and-imports_3 %} +{% tab 'Scala 2 и 3' for=packages-and-imports_3 %} + +```scala package com.google.selfdrivingcar.camera class Lens ``` + +{% endtab %} +{% endtabs %} + Что может соответствовать следующей структуре каталога: `SelfDrivingCar/src/main/scala/com/google/selfdrivingcar/camera/Lens.scala`. ## Импорт + Указание `import` открывает доступ к членам (классам, трейтам, функциям и т.д.) в других пакетах. Указание `import` не требуется для доступа к членам одного и того же пакета. Указание `import` избирательны: + +{% tabs packages-and-imports_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_4 %} + ``` import users._ // групповой импорт всего пакета users import users.User // импортировать только User @@ -67,20 +109,65 @@ import users.{User, UserPreferences} // импортировать только import users.{UserPreferences => UPrefs} // импортировать и переименовать ``` +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_4 %} + +``` +import users.* // групповой импорт всего пакета users, кроме given +import users.given // импорт всех given пакета users +import users.User // импортировать только User +import users.{User, UserPreferences} // импортировать только User, UserPreferences +import users.UserPreferences as UPrefs // импортировать и переименовать +``` + +{% endtab %} +{% endtabs %} + Одним из отличий Scala от Java является то, что импорт можно использовать где угодно: +{% tabs packages-and-imports_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_5 %} + ```scala mdoc def sqrtplus1(x: Int) = { import scala.math.sqrt sqrt(x) + 1.0 } ``` -В случае возникновения конфликта имен и необходимости импортировать что-либо из корня проекта, имя пакета должно начинаться с префикса `_root_`: + +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_5 %} + +```scala +def sqrtplus1(x: Int) = + import scala.math.sqrt + sqrt(x) + 1.0 ``` + +{% endtab %} +{% endtabs %} + +В случае возникновения конфликта имен и необходимости импортировать что-либо из корня проекта, имя пакета должно начинаться с префикса `_root_`: + +{% tabs packages-and-imports_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_6 %} + +```scala package accounts import _root_.users._ ``` +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_6 %} + +```scala +package accounts + +import _root_.users.* +``` + +{% endtab %} +{% endtabs %} Примечание: Пакеты `scala` и `java.lang`, а также `object Predef` импортируются по умолчанию. diff --git a/_ru/tour/pattern-matching.md b/_ru/tour/pattern-matching.md index 8d329636da..b46810564b 100644 --- a/_ru/tour/pattern-matching.md +++ b/_ru/tour/pattern-matching.md @@ -1,23 +1,23 @@ --- layout: tour title: Сопоставление с примером - -discourse: true - partof: scala-tour - num: 12 language: ru next-page: singleton-objects previous-page: case-classes prerequisite-knowledge: case-classes, string-interpolation, subtyping - --- Сопоставление с примером (Pattern matching) - это механизм сравнения значений с определенным примером. При успешном совпадении значение может быть разложено на составные части. Мы рассматриваем сопоставление с примером, как более мощную версию `switch` оператора из Java. Eго также можно использовать вместо серии if/else выражений. ## Синтаксис + Синтаксис сопоставления с примером состоит из значения, ключевого слова `match` (сопоставить) и по крайней мере, одного пункта с примером `case`, с которым мы хотим сопоставить наше значение. + +{% tabs pattern-matching-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-1 %} + ```scala mdoc import scala.util.Random @@ -27,50 +27,99 @@ x match { case 0 => "zero" case 1 => "one" case 2 => "two" - case _ => "many" + case _ => "other" } ``` -Значение константы `x` выше представляет собой случайное целое число от 0 до 10. `x` становится левым операндом оператора `match`, а справа - выражением с четырьмя примерами (называемые еще _вариантами_). Последний вариант `_` - позволяет "поймать все оставшиеся варианты" т. е. для любого числа больше 2. + +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-1 %} + +```scala +import scala.util.Random + +val x: Int = Random.nextInt(10) + +x match + case 0 => "zero" + case 1 => "one" + case 2 => "two" + case _ => "other" +``` + +{% endtab %} +{% endtabs %} + +Значение константы `x` выше представляет собой случайное целое число от 0 до 9. `x` становится левым операндом оператора `match`, а справа - выражением с четырьмя примерами (называемые еще _вариантами_). Последний вариант `_` - позволяет "поймать все оставшиеся варианты" т. е. для любого числа больше 2. Сопоставление с примером возвращает значение. + +{% tabs pattern-matching-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-2 %} + ```scala mdoc def matchTest(x: Int): String = x match { case 1 => "one" case 2 => "two" - case _ => "many" + case _ => "other" } -matchTest(3) // many -matchTest(1) // one +matchTest(3) // выводит "other" +matchTest(1) // выводит "one" +``` + +{% endtab %} + +{% tab 'Scala 3' for=pattern-matching-2 %} + +```scala +def matchTest(x: Int): String = x match + case 1 => "one" + case 2 => "two" + case _ => "other" + +matchTest(3) // выводит "other" +matchTest(1) // выводит "one" ``` + +{% endtab %} +{% endtabs %} + Это сопоставляющее выражение имеет тип String, так как все варианты сопоставления возвращают String. Поэтому функция `matchTest` возвращает String. ## Сопоставление с классами образцами -Классы образцы особенно полезны для сопоставления. +Классы образцы особенно полезны для сопоставления. + +{% tabs notification %} +{% tab 'Scala 2 и 3' for=notification %} ```scala mdoc -abstract class Notification +sealed trait 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 +``` +{% endtab %} +{% endtabs %} -``` -`Notification` - абстрактный суперкласс, от которого наследуются три конкретных типа реализаций классов образцов `Email`, `SMS`, и `VoiceRecording`. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов. +`Notification` - запечатанный трейт, от которого наследуются три конкретных типа реализаций классов образцов `Email`, `SMS`, и `VoiceRecording`. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов. При сопоставлении с классом образцом мы можем сразу извлекать параметры из которых состоит класс (благодаря автоматическому использованию [объекта экстрактора](extractor-objects.html)): -``` +{% tabs pattern-matching-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-4 %} + +```scala def showNotification(notification: Notification): String = { notification match { - case Email(email, title, _) => - s"You got an email from $email with title: $title" + case Email(sender, title, _) => + s"You got an email from $sender 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" + s"You received a Voice Recording from $name! Click the link to hear it: $link" } } val someSms = SMS("12345", "Are you there?") @@ -78,17 +127,46 @@ val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") println(showNotification(someSms)) // выводит "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" +println(showNotification(someVoiceRecording)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123" +``` + +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-4 %} + +```scala +def showNotification(notification: Notification): String = + notification match + case Email(sender, title, _) => + s"You got an email from $sender 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)) // выводит "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" ``` + +{% endtab %} +{% endtabs %} + Функция `showNotification` принимает в качестве параметра абстрактный тип `Notification` который проверяет по образцам (т.е. выясняет, является ли он классом `Email`, `SMS` или `VoiceRecording`). В `case Email(email, title, _)`поля `email` и `title` используются в возвращаемом значении, а вот поле `body` игнорируется благодаря символу `_`. ## Ограждения примеров + Ограждения примеров - это просто логические выражения, которые используются для того, чтобы сделать выбор более специфичным (убрать лишние варианты). Просто добавьте `if <логическое выражение>` после примера. -``` +{% tabs pattern-matching-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-5 %} + +```scala def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { notification match { - case Email(email, _, _) if importantPeopleInfo.contains(email) => + case Email(sender, _, _) if importantPeopleInfo.contains(sender) => "You got an email from special someone!" case SMS(number, _) if importantPeopleInfo.contains(number) => "You got an SMS from special someone!" @@ -99,23 +177,59 @@ def showImportantNotification(notification: Notification, importantPeopleInfo: S val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") -val someSms = SMS("867-5309", "Are you there?") +val someSms = SMS("123-4567", "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)) +println(showImportantNotification(someSms, importantPeopleInfo)) // выводит "You got an SMS from 123-4567! Message: Are you there?" +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123" +println(showImportantNotification(importantEmail, importantPeopleInfo)) // выводит "You got an email from special someone!" + +println(showImportantNotification(importantSms, importantPeopleInfo)) // выводит "You got an SMS from special someone!" ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-5 %} + +```scala +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = + notification match + case Email(sender, _, _) if importantPeopleInfo.contains(sender) => + "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) // в этом варианте считается подходящими параметры любого типа. Значит этот вариант выполняется во всех случаях и передает исходный параметр в функцию showNotification + +val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") + +val someSms = SMS("123-4567", "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)) // выводит "You got an SMS from 123-4567! Message: Are you there?" +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // выводит "You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123" +println(showImportantNotification(importantEmail, importantPeopleInfo)) // выводит "You got an email from special someone!" + +println(showImportantNotification(importantSms, importantPeopleInfo)) // выводит "You got an SMS from special someone!" +``` + +{% endtab %} +{% endtabs %} + В варианте `case Email(email, _, _) if importantPeopleInfo.contains(email)`, пример сравнивается только если `email` находится в списке `importantPeopleInfo`. ## Сопоставление только с типом + Вы можете сопоставлять только по типу как в примере: + +{% tabs pattern-matching-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-6 %} + ```scala mdoc -abstract class Device +sealed trait Device case class Phone(model: String) extends Device { def screenOff = "Turning screen off" } @@ -123,29 +237,99 @@ case class Computer(model: String) extends Device { def screenSaverOn = "Turning screen saver on..." } -def goIdle(device: Device) = device match { +def goIdle(device: Device): String = device match { case p: Phone => p.screenOff case c: Computer => c.screenSaverOn } ``` + +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-6 %} + +```scala +sealed trait 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): String = device match + case p: Phone => p.screenOff + case c: Computer => c.screenSaverOn +``` + +{% endtab %} +{% endtabs %} + метод `goIdle` реализует изменение поведения в зависимости от типа `Device`. По соглашению в качестве названия варианта используется первая буква типа (в данном случае `p` и `c`). -## Запечатанные классы -Трейты и классы могут быть помечены как `sealed` это означает, что подтипы должны быть объявлены в одном файле, гарантируя тем самым, что все подтипы будут известны. +## Запечатанные типы -```scala mdoc -sealed abstract class Furniture -case class Couch() extends Furniture -case class Chair() extends Furniture +Вы могли заметить, что в приведенных выше примерах базовые типы уточняются с помощью ключевого слова `sealed`. +Это обеспечивает дополнительную безопасность, поскольку компилятор проверяет, +указаны ли все случаи в выражении `match`, если базовым типом является `sealed`. + +Например, в методе `showNotification`, определенном выше, +если мы "забудем" один пример, скажем, `VoiceRecording`, +компилятор выдаст предупреждение: + +{% tabs pattern-matching-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-7 %} -def findPlaceToSit(piece: Furniture): String = piece match { - case a: Couch => "Lie on the couch" - case b: Chair => "Sit on the chair" +```scala +def showNotification(notification: Notification): String = { + notification match { + case Email(sender, title, _) => + s"You got an email from $sender with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" + } } ``` -Это полезно для сопоставления с примером, ведь мы будем заранее знать все доступные варианты и нам не нужен вариант "все остальные". + +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-7 %} + +```scala +def showNotification(notification: Notification): String = + notification match + case Email(sender, title, _) => + s"You got an email from $sender with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" +``` + +{% endtab %} +{% endtabs %} + +Это определение выдает следующее предупреждение: + +``` +match may not be exhaustive. + +It would fail on pattern case: VoiceRecording(_, _) +``` + +Компилятор даже предоставляет примеры входных данных, которые потерпят неудачу при сопоставлении! + +С другой стороны, проверка полноты требует, чтобы вы определили все подтипы базового типа в том же файле, +что и базовый тип (иначе бы компилятор не знал все возможные варианты). +Например, если вы попытаетесь определить новый тип `Notification` вне файла, +который определяет `sealed trait Notification`, это приведет к ошибке компиляции: + +``` +case class Telepathy(message: String) extends Notification + ^ + Cannot extend sealed trait Notification in a different source file +``` ## Замечания Сопоставление с примером наиболее полезно для сопоставления алгебраических типов, выраженных через [классы образцы](case-classes.html). Scala также позволяет создавать образцы независимо от классов образцов, через использование метода `unapply` в [объектах экстракторах](extractor-objects.html). + +## Дополнительные ресурсы + +- Дополнительная информация о сопоставлении с примером доступна [в книге Scala](/ru/scala3/book/control-structures.html#match-выражения). diff --git a/_ru/tour/polymorphic-methods.md b/_ru/tour/polymorphic-methods.md index 3c017b89f6..115cec0d94 100644 --- a/_ru/tour/polymorphic-methods.md +++ b/_ru/tour/polymorphic-methods.md @@ -1,23 +1,21 @@ --- layout: tour title: Полиморфные методы - -discourse: true - partof: scala-tour - num: 28 language: ru next-page: type-inference previous-page: implicit-conversions prerequisite-knowledge: unified-types - --- -Также как и у обобщенных классов, у методов есть полиморфизм по типу, с таким же синтаксисом (параметр типа указывается в квадратных скобках сразу после названия метода). +Также как и у обобщенных классов, у методов есть полиморфизм по типу, с таким же синтаксисом (параметр типа указывается в квадратных скобках сразу после названия метода). Вот пример: +{% tabs polymorphic-methods_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=polymorphic-methods_1 %} + ```scala mdoc def listOfDuplicates[A](x: A, length: Int): List[A] = { if (length < 1) @@ -29,8 +27,25 @@ println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) ``` +{% endtab %} +{% tab 'Scala 3' for=polymorphic-methods_1 %} + +```scala +def listOfDuplicates[A](x: A, length: Int): List[A] = + if length < 1 then + 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) +``` + +{% endtab %} +{% endtabs %} + Метод `listOfDuplicates` принимает параметр типа `A` и параметры значений `x` и `length`. Значение `x` имеет тип `A`. Если `length < 1` мы возвращаем пустой список. В противном случае мы добавляем `x`к списку, которые возвращаем через рекурсивный вызовов. (Обратите внимание, что `::` означает добавление элемента слева к списку справа). В первом вызове метода мы явно указываем параметр типа, записывая `[Int]`. Поэтому первым аргументом должен быть `Int` и тип возвращаемого значения будет `List[Int]`. -Во втором вызове показано, что вам не всегда нужно явно указывать параметр типа. Часто компилятор сам может вывести тип исходя из контекста или типа передаваемых аргументов. В этом варианте `"La"` - это `String`, поэтому компилятор знает, что `A` должен быть `String`. +Во втором вызове показано, что вам не всегда нужно явно указывать параметр типа. Часто компилятор сам может вывести тип исходя из контекста или типа передаваемых аргументов. В этом варианте `"La"` - это `String`, поэтому компилятор знает, что `A` должен быть `String`. diff --git a/_ru/tour/regular-expression-patterns.md b/_ru/tour/regular-expression-patterns.md index e8c2ea8404..2058d76ee7 100644 --- a/_ru/tour/regular-expression-patterns.md +++ b/_ru/tour/regular-expression-patterns.md @@ -1,20 +1,19 @@ --- layout: tour title: Регулярные Выражения - -discourse: true - partof: scala-tour - num: 15 language: ru next-page: extractor-objects previous-page: singleton-objects - --- Регулярные выражения (Regular expression) - это специальный шаблон для поиска данных, задаваемый в виде текстовой строки. Любая строка может быть преобразована в регулярное выражение методом `.r`. +{% tabs regex-patterns_numberPattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_numberPattern %} + ```scala mdoc import scala.util.matching.Regex @@ -26,14 +25,36 @@ numberPattern.findFirstMatchIn("awesomepassword") match { } ``` +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_numberPattern %} + +```scala +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") +``` + +{% endtab %} + +{% endtabs %} + В приведенном выше примере `numberPattern` - это `Regex` (регулярное выражение), которое мы используем, чтобы убедиться, что пароль содержит число. Используя круглые скобки можно объединять сразу несколько групп регулярных выражений. +{% tabs regex-patterns_keyValPattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_keyValPattern %} + ```scala mdoc import scala.util.matching.Regex -val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r +val keyValPattern: Regex = "([0-9a-zA-Z- ]+): ([0-9a-zA-Z-#()/. ]+)".r val input: String = """background-color: #A03300; @@ -48,7 +69,36 @@ val input: String = for (patternMatch <- keyValPattern.findAllMatchIn(input)) println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") ``` + +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_keyValPattern %} + +```scala +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) do + println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") +``` + +{% endtab %} + +{% endtabs %} + Здесь мы обработали сразу и ключи и значения строки. В каждом совпадении есть подгруппа совпадений. Вот как выглядит результат: + ``` key: background-color value: #A03300 key: background-image value: url(img @@ -59,3 +109,68 @@ key: margin value: 0 key: height value: 108px key: width value: 100 ``` + +Кроме того, регулярные выражения можно использовать в качестве шаблонов (в выражениях `match`) +для удобного извлечения совпавших групп: + +{% tabs regex-patterns_saveContactInformation class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_saveContactInformation %} + +```scala mdoc +def saveContactInformation(contact: String): Unit = { + import scala.util.matching.Regex + + val emailPattern: Regex = """^(\w+)@(\w+(.\w+)+)$""".r + val phonePattern: Regex = """^(\d{3}-\d{3}-\d{4})$""".r + + contact match { + case emailPattern(localPart, domainName, _) => + println(s"Hi $localPart, we have saved your email address.") + case phonePattern(phoneNumber) => + println(s"Hi, we have saved your phone number $phoneNumber.") + case _ => + println("Invalid contact information, neither an email address nor phone number.") + } +} + +saveContactInformation("123-456-7890") +saveContactInformation("JohnSmith@sample.domain.com") +saveContactInformation("2 Franklin St, Mars, Milky Way") +``` + +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_saveContactInformation %} + +```scala +def saveContactInformation(contact: String): Unit = + import scala.util.matching.Regex + + val emailPattern: Regex = """^(\w+)@(\w+(.\w+)+)$""".r + val phonePattern: Regex = """^(\d{3}-\d{3}-\d{4})$""".r + + contact match + case emailPattern(localPart, domainName, _) => + println(s"Hi $localPart, we have saved your email address.") + case phonePattern(phoneNumber) => + println(s"Hi, we have saved your phone number $phoneNumber.") + case _ => + println("Invalid contact information, neither an email address nor phone number.") + +saveContactInformation("123-456-7890") +saveContactInformation("JohnSmith@sample.domain.com") +saveContactInformation("2 Franklin St, Mars, Milky Way") +``` + +{% endtab %} + +{% endtabs %} + +Вот как выглядит результат: + +``` +Hi, we have saved your phone number 123-456-7890. +Hi JohnSmith, we have saved your email address. +Invalid contact information, neither an email address nor phone number. +``` diff --git a/_ru/tour/self-types.md b/_ru/tour/self-types.md index 2ce9946450..06597bfe70 100644 --- a/_ru/tour/self-types.md +++ b/_ru/tour/self-types.md @@ -1,24 +1,24 @@ --- layout: tour title: Самоописываемые типы - -discourse: true - partof: scala-tour - num: 25 language: ru next-page: implicit-parameters previous-page: compound-types topics: self-types prerequisite-knowledge: nested-classes, mixin-class-composition - --- -Самоописываемый тип(Self type) - это способ объявить, что трейт должен быть смешан с другим трейтом, даже если он не расширяет его напрямую. Что открывает доступ к членам зависимости без импортирования. + +Самоописываемый тип (Self type) - это способ объявить, что трейт должен быть смешан с другим трейтом, даже если он не расширяет его напрямую. Что открывает доступ к членам зависимости без импортирования. Самоописываемый тип - это способ сузить тип `this` или другого идентификатора, который ссылается на `this`. Синтаксис похож на синтаксис обычной функции, но означает кое-что иное. Чтобы использовать самоописываемый тип в трейте напишите: идентификатор, тип другого трейта, который хотите добавить и `=>` (например, `someIdentifier: SomeOtherTrait =>`). + +{% tabs self-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=self-types_1 %} + ```scala mdoc trait User { def username: String @@ -30,7 +30,7 @@ trait Tweeter { } class VerifiedTweeter(val username_ : String) extends Tweeter with User { // Мы добавили User потому этого требует Tweeter - def username = s"real $username_" + def username = s"real $username_" } val realBeyoncé = new VerifiedTweeter("Beyoncé") @@ -38,3 +38,26 @@ realBeyoncé.tweet("Just spilled my glass of lemonade") // выведет "real ``` Поскольку мы указали `this: User =>` в трейте `Tweeter`, теперь переменная `username` находится в пределах видимости для метода `tweet`. Это также означает что `VerifiedTweeter` при наследовании от `Tweeter` должен быть смешан с `User` (используя `with User`). + +{% endtab %} +{% tab 'Scala 3' for=self-types_1 %} + +```scala +trait User: + def username: String + +trait Tweeter: + this: User => // переназначил this + def tweet(tweetText: String) = println(s"$username: $tweetText") + +class VerifiedTweeter(val username_ : String) extends Tweeter, User: // Мы добавили User потому этого требует Tweeter + def username = s"real $username_" + +val realBeyoncé = VerifiedTweeter("Beyoncé") +realBeyoncé.tweet("Just spilled my glass of lemonade") // выведет "real Beyoncé: Just spilled my glass of lemonade" +``` + +Поскольку мы указали `this: User =>` в трейте `Tweeter`, теперь переменная `username` находится в пределах видимости для метода `tweet`. Это также означает что `VerifiedTweeter` при наследовании от `Tweeter` должен быть смешан с `User` (используя `, User`). + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/singleton-objects.md b/_ru/tour/singleton-objects.md index d39f5c170e..5a0da42d5c 100644 --- a/_ru/tour/singleton-objects.md +++ b/_ru/tour/singleton-objects.md @@ -1,41 +1,74 @@ --- layout: tour title: Объекты Одиночки - -discourse: true - partof: scala-tour - num: 13 language: ru next-page: regular-expression-patterns previous-page: pattern-matching prerequisite-knowledge: classes, methods, private-methods, packages, option --- + Все объекты являются одиночками (Singleton Object) - то есть существуют в единственном экземпляре. Он создается лениво, когда на него ссылаются, также как ленивые значения (lazy val). На самом верхнем уровне объект является одиночкой. Как член класса или как локальная переменная, он ведет себя точно так же как ленивое значение (lazy val). + # Объявление одиночного объекта + Объект - является значением. Объявление объекта происходит схожим с классом образом, но используется ключевое слово `object`: + +{% tabs object-definition-box %} + +{% tab 'Scala 2 и 3' for=object-definition-box %} + ```scala mdoc object Box ``` +{% endtab %} + +{% endtabs %} + Вот пример объекта с методом: -``` + +{% tabs singleton-logger-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=singleton-logger-example %} + +```scala package logging object Logger { def info(message: String): Unit = println(s"INFO: $message") } ``` + +{% endtab %} + +{% tab 'Scala 3' for=singleton-logger-example %} + +```scala +package logging + +object Logger: + def info(message: String): Unit = println(s"INFO: $message") +``` + +{% endtab %} + +{% endtabs %} + Метод `info` может быть импортирован в любом месте программы. Создание подобных методов является распространенным вариантом использования одиночных объектов. Давайте посмотрим, как использовать `info` в другом пакете: -``` +{% tabs singleton-usage-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=singleton-usage-example %} + +```scala import logging.Logger.info class Project(name: String, daysToComplete: Int) @@ -47,15 +80,44 @@ class Test { } ``` +{% endtab %} + +{% tab 'Scala 3' for=singleton-usage-example %} + +```scala +import logging.Logger.info + +class Project(name: String, daysToComplete: Int) + +class Test: + val project1 = Project("TPS Reports", 1) + val project2 = Project("Website redesign", 5) + info("Created projects") // Prints "INFO: Created projects" +``` + +{% endtab %} + +{% endtabs %} + Метод `info` виден благодаря указанию импорта `import logging.Logger.info`. +Для импорта требуется "постоянный путь" к импортируемому символу, а путь к объекту неизменен. -Замечание: Если `object` не является объектом верхнего уровня, но вложен в другой класс или объект, то объект, как и любой другой член, "зависим от пути". +Замечание: Если `object` не является объектом верхнего уровня, но вложен в другой класс или объект, +то объект, как и любой другой член, "зависим от пути". +Это означает, что для двух видов напитков, `class Milk` и `class OrangeJuice`, +элемент класса `object NutritionInfo` "зависит" от включающего его экземпляра, будь то `Milk` или `OrangeJuice`. +`milk.NutritionInfo` полностью отличается от `oj.NutritionInfo`. ## Объекты компаньоны Объект с тем же именем, что и класс называется _объект компаньон_ (companion object). И наоборот, класс является классом-компаньоном объекта. Класс или объект компаньон может получить доступ к приватным членам своего спутника. Используйте объект компаньон для методов и значений, которые не специфичны для экземпляров класса компаньона. -``` -import scala.math._ + +{% tabs companion-object-circle class=tabs-scala-version %} + +{% tab 'Scala 2' for=companion-object-circle %} + +```scala +import scala.math.pow case class Circle(radius: Double) { import Circle._ @@ -71,9 +133,37 @@ val circle1 = Circle(5.0) circle1.area ``` +{% endtab %} + +{% tab 'Scala 3' for=companion-object-circle %} + +```scala +import scala.math.pow + +case class Circle(radius: Double): + import Circle.* + def area: Double = calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + + +val circle1 = Circle(5.0) + +circle1.area +``` + +{% endtab %} + +{% endtabs %} + Класс `Circle` имеет член `area`, который специфичен для каждого конкретного экземпляра, а метод `calculateArea` одиночного объекта `Circle`, доступен для каждого экземпляра класса `Circle`. Объект компаньон может также содержать методы создающие конкретные экземпляры класса спутника: +{% tabs companion-object-email class=tabs-scala-version %} + +{% tab 'Scala 2' for=companion-object-email %} + ```scala mdoc class Email(val username: String, val domainName: String) @@ -92,16 +182,48 @@ scalaCenterEmail match { s"""Registered an email |Username: ${email.username} |Domain name: ${email.domainName} - """) + """.stripMargin) case None => println("Error: could not parse email") } ``` + +{% endtab %} + +{% tab 'Scala 3' for=companion-object-email %} + +```scala +class Email(val username: String, val domainName: String) + +object Email: + def fromString(emailString: String): Option[Email] = + emailString.split('@') match + case Array(a, b) => Some(Email(a, b)) + case _ => None + +val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") +scalaCenterEmail match + case Some(email) => println( + s"""Registered an email + |Username: ${email.username} + |Domain name: ${email.domainName} + """.stripMargin) + case None => println("Error: could not parse email") +``` + +{% endtab %} + +{% endtabs %} + `object Email` содержит производящий метод `fromString`, который создает экземпляр `Email` из строки. Мы возвращаем результат как `Option[Email]` на случай возникновения ошибок парсинга. Примечание: Если у класса или объекта есть компаньон, они должны быть размещены в одном и том же файле. Чтобы задать компаньонов в REPL, либо определите их на той же строке, либо перейдите в режим `:paste`. -## Примечания для Java-программистов ## +## Примечания для Java-программистов `static` члены в Java смоделированы как обычные члены объекта компаньона в Scala. При использовании объекта компаньона из Java-кода, члены будут определены в сопутствующем классе компаньоне с `static` модификатором. Это называется _пробрасывание статики_. Такое происходит, даже если вы сами не определили класс компаньон. + +## Дополнительные ресурсы + +- Узнайте больше об объектах компаньонах в [Scala Book](/ru/scala3/book/domain-modeling-tools.html#сопутствующие-объекты) diff --git a/_ru/tour/tour-of-scala.md b/_ru/tour/tour-of-scala.md index ac523d9914..62f9fbe592 100644 --- a/_ru/tour/tour-of-scala.md +++ b/_ru/tour/tour-of-scala.md @@ -1,59 +1,60 @@ --- layout: tour title: Введение - -discourse: true - partof: scala-tour - num: 1 language: ru next-page: basics --- ## Добро пожаловать к обзору + Здесь вы увидите вводное описание наиболее часто используемых возможностей Scala. Этот обзор предназначен для новичков в изучении языка. -Это лишь небольшая экскурсия, а не полный курс освоения языка. Для глубокого погружения рекомендуем почитать [книги](/books.html) или воспользоваться курсами -[на других ресурсах](/learn.html). +Это лишь небольшая экскурсия, а не полный курс освоения языка. Для глубокого погружения рекомендуем почитать [книги](/books.html) или воспользоваться курсами +[на других ресурсах](/online-courses.html). ## Что такое Scala? + Scala - это современный мультипарадигмальный язык программирования, разработанный для выражения общих концепций программирования в простой, удобной и типобезопасной манере. Элегантно объединяя особенности объектно-ориентированных и функциональных языков. -## Scala объектно ориентированный ## +## Scala объектно ориентированный + Scala - это чистый объектно-ориентированный язык в том смысле, что [каждое значение - это объект](unified-types.html). Типы и поведение объектов описаны в [классах](classes.html) и [трейтах](traits.html)(характеристиках объектов). Классы расширяются за счет механизма наследования и гибкого [смешивания классов](mixin-class-composition.html), который используется для замены множественного наследования. -## Scala функциональный ## +## Scala функциональный + Scala также является функциональным языком в том смысле, что [каждая функция - это значение](unified-types.html). Scala предоставляет [легкий синтаксис](basics.html) для определения анонимных функций, поддерживает [функции высшего порядка](higher-order-functions.html), поддерживает [вложенные функции](nested-functions.html), а также [каррирование](multiple-parameter-lists.html). Scala имеют встроенную поддержку алгебраических типов данных, которые используются в большинстве функциональных языках программирования (эта поддержка базируется на механизме [сопоставления с примером](pattern-matching.html), где в качестве примера выступают [классы образцы](case-classes.html) ). [Объекты](singleton-objects.html) предоставляют удобный способ группировки функций, не входящих в класс. Вдобавок к этому, концепция сопоставления с примером логично переносится на [обработку XML-данных](https://github.com/scala/scala-xml/wiki/XML-Processing) используя в качестве примера [регулярные выражения](regular-expression-patterns.html), при поддержке функционала [объектов экстракторов](extractor-objects.html). Для еще большего удобства обработки данных представлена схема формирования запросов с использованием [for-выражения](for-comprehensions.html). Такие возможности делают Scala идеальным решением для разработки приложений по типу веб-сервисов. -## Scala статически типизированный ## +## Scala статически типизированный + Scala оснащен выразительной системой типов, которая обеспечивает безопасное и гармоничное использование абстракций. В частности, система типов поддерживает: -* [обобщенные классы](generic-classes.html) -* [вариантность типов](variances.html) -* [верхние](upper-type-bounds.html) и [нижние](lower-type-bounds.html) границы типов -* [внутренние классы](inner-classes.html) и [члены абстрактного типа](abstract-type-members.html), как часть объектов -* [составные типы](compound-types.html) -* [самоописываемые типы](self-types.html) -* [неявные параметры](implicit-parameters.html) и [неявные преобразования](implicit-conversions.html) -* [полиморфные методы](polymorphic-methods.html) +- [обобщенные классы](generic-classes.html) +- [вариантность типов](variances.html) +- [верхние](upper-type-bounds.html) и [нижние](lower-type-bounds.html) границы типов +- [внутренние классы](inner-classes.html) и [члены абстрактного типа](abstract-type-members.html), как часть объектов +- [составные типы](compound-types.html) +- [самоописываемые типы](self-types.html) +- [неявные параметры](implicit-parameters.html) и [неявные преобразования](implicit-conversions.html) +- [полиморфные методы](polymorphic-methods.html) -[Выведение типов](type-inference.html) означает, что разработчику не обязательно добавлять в код избыточную информацию о типах. +[Выведение типов](type-inference.html) означает, что разработчику не обязательно добавлять в код избыточную информацию о типах. Такой функционал обеспечивает основу для безопасного переиспользования абстракций и типобезопасного развития программного обеспечения. -## Scala расширяемый ## +## Scala расширяемый Зачастую разработка приложений для очень специфичных областей требует специфичных для этих областей языковых возможностей, либо отдельных специализированных языков программирования. Вместо этого Scala предлагает уникальные механизмы, для легкой модификации и расширения самого языка. Во многих случаях такое можно сделать без использования средств мета-программирования, таких как макросы. Например: -* [Неявные классы](https://docs.scala-lang.org/overviews/core/implicit-classes.html) позволяют добавлять новые методы к уже существующим. -* [Интерполяция строк](/overviews/core/string-interpolation.html) позволяет добавлять обработку строк (расширяется разработчиком с помощью интерполяторов). +- [Неявные классы](https://docs.scala-lang.org/overviews/core/implicit-classes.html) позволяют добавлять новые методы к уже существующим. +- [Интерполяция строк](/ru/scala3/book/string-interpolation.html) позволяет добавлять обработку строк (расширяется разработчиком с помощью интерполяторов). -## Scala совместимый +## Scala совместимый Scala полностью совместим с популярной средой Java Runtime Environment (JRE). Взаимодействие с основным объектно-ориентированным языком программирования Java происходит максимально гладко. Новые функции Java, такие как SAM, [лямбды](higher-order-functions.html), [аннотации](annotations.html) и [дженерики](generic-classes.html), имеют прямые аналоги в Scala. diff --git a/_ru/tour/traits.md b/_ru/tour/traits.md index 3eec6fd35c..ef958c94cf 100644 --- a/_ru/tour/traits.md +++ b/_ru/tour/traits.md @@ -1,30 +1,37 @@ --- layout: tour title: Трейты - -discourse: true - partof: scala-tour - num: 5 language: ru next-page: tuples previous-page: named-arguments topics: traits prerequisite-knowledge: expressions, classes, generics, objects, companion-objects - --- Трейты (Traits) используются, чтобы обмениваться между классами информацией о структуре и полях. Они похожи на интерфейсы из Java 8. Классы и объекты могут расширять трейты, но трейты не могут быть созданы и поэтому не имеют параметров. ## Объявление трейта + Минимальное объявление трейта - это просто ключевое слово `trait` и его имя: +{% tabs trait-hair-color %} +{% tab 'Scala 2 и 3' for=trait-hair-color %} + ```scala mdoc trait HairColor ``` +{% endtab %} +{% endtabs %} + Трейты наиболее полезны в качестве обобщенного типа с абстрактными методами. + +{% tabs trait-iterator-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-iterator-definition %} + ```scala mdoc trait Iterator[A] { def hasNext: Boolean @@ -32,10 +39,30 @@ trait Iterator[A] { } ``` +{% endtab %} + +{% tab 'Scala 3' for=trait-iterator-definition %} + +```scala +trait Iterator[A]: + def hasNext: Boolean + def next(): A +``` + +{% endtab %} + +{% endtabs %} + При наследовании от трейта `Iterator[A]` требует указание типа `A` а также реализация методов `hasNext` и `next`. ## Использование трейтов + Чтобы использовать трейты, необходимо наследовать класс от него, используя ключевое слово `extends`. Затем необходимо реализовать все абстрактные члены трейта, используя ключевое слово `override`: + +{% tabs trait-intiterator-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-intiterator-definition %} + ```scala mdoc:nest trait Iterator[A] { def hasNext: Boolean @@ -45,7 +72,7 @@ trait Iterator[A] { class IntIterator(to: Int) extends Iterator[Int] { private var current = 0 override def hasNext: Boolean = current < to - override def next(): Int = { + override def next(): Int = { if (hasNext) { val t = current current += 1 @@ -54,15 +81,51 @@ class IntIterator(to: Int) extends Iterator[Int] { } } +val iterator = new IntIterator(10) +iterator.next() // вернет 0 +iterator.next() // вернет 1 +``` + +{% endtab %} + +{% tab 'Scala 3' for=trait-intiterator-definition %} + +```scala +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 then + val t = current + current += 1 + t + else + 0 +end IntIterator val iterator = new IntIterator(10) iterator.next() // вернет 0 iterator.next() // вернет 1 ``` + +{% endtab %} + +{% endtabs %} + Этот класс `IntIterator` использует параметр `to` в качестве верхней границы. Он наследуется от `Iterator[Int]`, что означает, что метод `next` должен возвращать Int. ## Подтипы -Туда, где требуется определенный тип трейта, мы можем передавать любой наследованный от требуемого трейта класс + +Туда, где требуется определенный тип трейта, мы можем передавать любой наследованный от требуемого трейта класс + +{% tabs trait-pet-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-pet-example %} + ```scala mdoc import scala.collection.mutable.ArrayBuffer @@ -81,4 +144,36 @@ animals.append(dog) animals.append(cat) animals.foreach(pet => println(pet.name)) // выведет "Harry" и "Sally" ``` + +{% endtab %} + +{% tab 'Scala 3' for=trait-pet-example %} + +```scala +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 = Dog("Harry") +val cat = Cat("Sally") + +val animals = ArrayBuffer.empty[Pet] +animals.append(dog) +animals.append(cat) +animals.foreach(pet => println(pet.name)) // выведет "Harry" и "Sally" +``` + +{% endtab %} + +{% endtabs %} + У трейта `Pet` есть абстрактное поле `name`, которое реализовано в классах `Cat` and `Dog`. В последней строке мы вызываем `pet.name`, который должен быть реализован в любом подтипе, унаследованном от трейта `Pet`. + +## Дополнительные ресурсы + +- Узнайте больше о трейтах в [Scala Book](/ru/scala3/book/domain-modeling-tools.html#трейты) +- Использование трейтов для определения [Enum](/ru/scala3/book/domain-modeling-fp.html#моделирование-данных) diff --git a/_ru/tour/tuples.md b/_ru/tour/tuples.md index 8d30c607c5..8f1550c473 100644 --- a/_ru/tour/tuples.md +++ b/_ru/tour/tuples.md @@ -1,95 +1,142 @@ --- layout: tour title: Кортежи - -discourse: true - partof: scala-tour - num: 6 language: ru next-page: mixin-class-composition previous-page: traits topics: tuples - --- -В Scala, кортеж (Тuple) это класс контейнер содержащий упорядоченный набор элементов различного типа. -Кортежи неизменяемы. +В Scala, кортеж (Тuple) - это контейнер содержащий упорядоченный набор элементов различного типа. +Кортежи неизменяемы. Кортежи могут пригодиться, когда нам нужно вернуть сразу несколько значений из функции. Кортеж может быть создан как: +{% tabs tuple-construction %} + +{% tab 'Scala 2 и 3' for=tuple-construction %} + ```scala mdoc -val ingredient = ("Sugar" , 25):Tuple2[String, Int] +val ingredient = ("Sugar", 25) ``` -Такая запись создает кортеж размерности 2, содержащий пару элементов String и Int. -Кортежи в Скале - представлены серией классов: Tuple2, Tuple3 и т.д., до Tuple22. -Таким образом, создавая кортеж с n элементами (n лежащими между 2 и 22), Скала просто создает один из соответствующих классов, который параметризован типом входящих в состав элементов. +{% endtab %} -В нашем примере, составляющие тип Tuple2[String, Int]. +{% endtabs %} + +Такая запись создает кортеж, содержащий пару элементов `String` и `Int`. + +Выводимый тип `ingredient` - это `(String, Int)`. ## Доступ к элементам -Доступ к элементам кортежа осуществляется при помощи синтаксиса подчеркивания. -'tuple._n' дает n-ый элемент (столько, сколько существует элементов). +{% tabs tuple-indexed-access class=tabs-scala-version %} + +{% tab 'Scala 2' for=tuple-indexed-access %} + +Один из способов доступа к элементам кортежа — по их позиции. +`tuple._n` дает n-ый элемент (столько, сколько существует элементов). ```scala mdoc println(ingredient._1) // Sugar - println(ingredient._2) // 25 ``` -## Распаковка данных кортежа +{% endtab %} -Scala кортежи также поддерживают [распаковку](extractor-objects.html). +{% tab 'Scala 3' for=tuple-indexed-access %} -```scala mdoc -val (name, quantity) = ingredient +Один из способов доступа к элементам кортежа — по их позиции. +Доступ к отдельным элементам осуществляется с помощью `tuple(0)`, `tuple(1)` и так далее. + +```scala +println(ingredient(0)) // Sugar +println(ingredient(1)) // 25 +``` + +{% endtab %} + +{% endtabs %} -println(name) // Sugar +## Сопоставление с образцом для кортежей +Кортеж также можно распаковать с помощью сопоставления с образцом: + +{% tabs tuple-extraction %} + +{% tab 'Scala 2 и 3' for=tuple-extraction %} + +```scala mdoc +val (name, quantity) = ingredient +println(name) // Sugar println(quantity) // 25 ``` -Распаковка данных кортежа может быть использована в [сопоставлении с примером](pattern-matching.html) +{% endtab %} + +{% endtabs %} + +Здесь выводимый тип `name` - `String` и выводимый тип `quantity` - `Int`. + +Вот еще один пример сопоставления с образцом кортежа: + +{% tabs tuple-foreach-patmat %} + +{% tab 'Scala 2 и 3' for=tuple-foreach-patmat %} ```scala mdoc -val planetDistanceFromSun = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6 ), ("Mars", 227.9), ("Jupiter", 778.3)) - -planetDistanceFromSun.foreach{ tuple => { - - tuple match { - - case ("Mercury", distance) => println(s"Mercury is $distance millions km far from Sun") - - case p if(p._1 == "Venus") => println(s"Venus is ${p._2} millions km far from Sun") - - case p if(p._1 == "Earth") => println(s"Blue planet is ${p._2} millions km far from Sun") - - case _ => println("Too far....") - - } - - } - +val planets = + List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6), + ("Mars", 227.9), ("Jupiter", 778.3)) +planets.foreach { + case ("Earth", distance) => + println(s"Our planet is $distance million kilometers from the sun") + case _ => } ``` -Или в ['for' выражении](for-comprehensions.html). +{% endtab %} + +{% endtabs %} + +Или, в _for-comprehension_: + +{% tabs tuple-for-extraction class=tabs-scala-version %} + +{% tab 'Scala 2' for=tuple-for-extraction %} ```scala mdoc val numPairs = List((2, 5), (3, -7), (20, 56)) - for ((a, b) <- numPairs) { - println(a * b) - } ``` -Значение () типа Unit по свой сути совпадает со значением () типа Tuple0. Может быть только одно значение такого типа, так как в нём нет элементов. +{% endtab %} + +{% tab 'Scala 3' for=tuple-for-extraction %} + +```scala +val numPairs = List((2, 5), (3, -7), (20, 56)) +for (a, b) <- numPairs do + println(a * b) +``` + +{% endtab %} + +{% endtabs %} + +## Кортежи и кейс-классы + +Иногда бывает трудно выбирать между кортежами и кейс-классами. +Кейс-классы содержат именованные элементы. Имена могут улучшить читаемость некоторых типов кода. +В приведенном выше примере мы могли бы определить планеты, как `case class Planet(name: String, distance: Double)`, +а не использовать кортежи. + +## Дополнительные ресурсы -Иногда бывает трудно выбирать между кортежами и классами образцами. Как правило, классы образцы являются предпочтительным выбором, если класс-контейнер содержащий элементы сам по себе имеет значимый смысл. +- Дополнительная информация о кортежах - в книге [Scala Book](/ru/scala3/book/taste-collections.html#кортежи) diff --git a/_ru/tour/type-inference.md b/_ru/tour/type-inference.md index 5e5e39cf17..52f0836467 100644 --- a/_ru/tour/type-inference.md +++ b/_ru/tour/type-inference.md @@ -1,11 +1,7 @@ --- layout: tour title: Выведение Типа - -discourse: true - partof: scala-tour - num: 29 language: ru next-page: operators @@ -16,26 +12,56 @@ previous-page: polymorphic-methods ## Не указывая тип +{% tabs type-inference_1 %} +{% tab 'Scala 2 и 3' for=type-inference_1 %} + ```scala mdoc val businessName = "Montreux Jazz Café" ``` + +{% endtab %} +{% endtabs %} + Компилятор может определить, что тип константы `businessName` является `String`. Аналогичным образом это работает и для методов: +{% tabs type-inference_2 %} +{% tab 'Scala 2 и 3' for=type-inference_2 %} + ```scala mdoc def squareOf(x: Int) = x * x ``` + +{% endtab %} +{% endtabs %} + Компилятор может определить, что возвращаемый тип является `Int`, поэтому явного указания типа не требуется. Для рекурсивных методов компилятор не в состоянии вывести тип. Вот программа, которая не скомпилируется по этой причине: +{% tabs type-inference_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=type-inference_3 %} + ```scala mdoc:fail def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) ``` +{% endtab %} +{% tab 'Scala 3' for=type-inference_3 %} + +```scala +def fac(n: Int) = if n == 0 then 1 else n * fac(n - 1) +``` + +{% endtab %} +{% endtabs %} + Также необязательно указывать параметры типа при вызове [полиморфных методов](polymorphic-methods.html) или [обобщенных классов](generic-classes.html). Компилятор Scala определит тип параметра из контекста и из типов фактически передаваемых параметров метода/конструктора. Вот два примера: +{% tabs type-inference_4 %} +{% tab 'Scala 2 и 3' for=type-inference_4 %} + ```scala mdoc case class MyPair[A, B](x: A, y: B) val p = MyPair(1, "scala") // тип: MyPair[Int, String] @@ -44,32 +70,53 @@ def id[T](x: T) = x val q = id(1) // тип: Int ``` +{% endtab %} +{% endtabs %} + Компилятор использует типы аргументов `MyPair` для определения типа `A` и `B`. Тоже самое для типа `x`. ## Параметры Для параметров компилятор никогда не выводит тип. Однако, в некоторых случаях, он может вывести типы для параметров анонимной функции при передаче ее в качестве аргумента. +{% tabs type-inference_5 %} +{% tab 'Scala 2 и 3' for=type-inference_5 %} + ```scala mdoc Seq(1, 3, 4).map(x => x * 2) // List(2, 6, 8) ``` +{% endtab %} +{% endtabs %} + Параметр у map - `f: A => B` (функциональный параметр переводящий тип из A в B). Поскольку мы разместили целые числа в нашей последовательности `Seq`, компилятор знает, что элемент `A` является `Int` (т.е. `x` является целым числом). Поэтому компилятор может определить из выражения `x * 2`, что результат (`B`) является типом `Int`. ## Когда _не следует_ полагаться на выведение типа Обычно считается, наиболее удобочитаемым объявить тип членов, которые открыты для публичного использования через API. Поэтому мы рекомендуем вам явно указывать тип для любых API, которые будут доступны пользователям вашего кода. -Кроме того, выведение может иногда приводить к слишком специфичному типу. Предположим, мы напишем: +Кроме того, выведение может иногда приводить к слишком специфичному типу. Предположим, мы напишем: + +{% tabs type-inference_6 %} +{% tab 'Scala 2 и 3' for=type-inference_6 %} ```scala var obj = null ``` +{% endtab %} +{% endtabs %} + Тогда мы не сможем далее сделать это переназначение: +{% tabs type-inference_7 %} +{% tab 'Scala 2 и 3' for=type-inference_7 %} + ```scala mdoc:fail obj = new AnyRef ``` +{% endtab %} +{% endtabs %} + Такое не будет компилироваться, потому что для `obj` предполагался тип `Null`. Поскольку единственным значением этого типа является `null`, то невозможно присвоить другое значение. diff --git a/_ru/tour/unified-types.md b/_ru/tour/unified-types.md index 1bd87556b7..e5f947ed89 100644 --- a/_ru/tour/unified-types.md +++ b/_ru/tour/unified-types.md @@ -1,24 +1,19 @@ --- layout: tour title: Единобразие типов - -discourse: true - partof: scala-tour - num: 3 language: ru next-page: classes previous-page: basics prerequisite-knowledge: classes, basics - --- В Scala все значения имеют тип, включая числовые значения и функции. Диаграмма ниже иллюстрирует подмножество иерархии типов. Scala Type Hierarchy -## Иерархия типов Scala ## +## Иерархия типов Scala [`Any`](https://www.scala-lang.org/api/2.12.1/scala/Any.html) это супертип всех типов, также называемый верхним типом. Он определяет несколько универсальных методов, таких как `equals`, `hashCode` и `toString`. У `Any` есть два прямых подкласса: `AnyVal` и `AnyRef`. @@ -28,6 +23,9 @@ prerequisite-knowledge: classes, basics Вот пример, демонстрирующий, что строки, целые числа, символы, логические значения и функции являются объектами, как и любой другой объект: +{% tabs unified-types-1 %} +{% tab 'Scala 2 и 3' for=unified-types-1 %} + ```scala mdoc val list: List[Any] = List( "a string", @@ -40,6 +38,9 @@ val list: List[Any] = List( list.foreach(element => println(element)) ``` +{% endtab %} +{% endtabs %} + Объявляем переменную `list` типа `List[Any]`. Список инициализируется элементами различных типов, но все они являются экземпляром `scala.Any`, так что вы можете добавить их в список. Ниже приведен вывод программы: @@ -53,30 +54,44 @@ true ``` ## Приведение типа + Числовые типы могут быть приведены следующим образом: Scala Type Hierarchy Например: +{% tabs unified-types-2 %} +{% tab 'Scala 2 и 3' for=unified-types-2 %} + ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (заметьте, что некоторая точность теряется в этом случае.) +val y: Float = x.toFloat // 9.8765434E8 (заметьте, что некоторая точность теряется в этом случае.) val face: Char = '☺' val number: Int = face // 9786 ``` +{% endtab %} +{% endtabs %} + Приведение типа - однонаправленно. Следующий пример не скомпилируется: +{% tabs unified-types-3 %} +{% tab 'Scala 2 и 3' for=unified-types-3 %} + ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // обратно не подходит ``` +{% endtab %} +{% endtabs %} + Вы также можете приводить к своему подтипу. Об этом мы поговорим позже в ходе нашего обзора. ## Nothing и Null -`Nothing` является подтипом всех типов, также называемым нижним типом. Нет значения, которое имеет тип `Nothing`. Обычно он используется чтоб дать сигнал о не вычислимости, например брошено исключение, выход из программы, бесконечное зацикливание (т.е. это тип выражения, которое не вычисляется). + +`Nothing` является подтипом всех типов, также называемым нижним типом. Нет значения, которое имеет тип `Nothing`. Обычно он используется чтоб дать сигнал о не вычислимости, например брошено исключение, выход из программы, бесконечное зацикливание (т.е. это тип выражения, которое не вычисляется). `Null` подтип всех ссылочных типов (т.е. любой подтип AnyRef). Он имеет одно значение, определяемое ключевым словом литерала `null`. `Null` предоставляется в основном для функциональной совместимости с другими языками JVM и почти никогда не должен использоваться в коде Scala. Об альтернативах `null` мы поговорим позднее. diff --git a/_ru/tour/upper-type-bounds.md b/_ru/tour/upper-type-bounds.md index 60ca550d59..7a9238016e 100644 --- a/_ru/tour/upper-type-bounds.md +++ b/_ru/tour/upper-type-bounds.md @@ -1,21 +1,20 @@ --- layout: tour title: Верхнее Ограничение Типа - -discourse: true - partof: scala-tour categories: tour num: 20 language: ru next-page: lower-type-bounds previous-page: variances - --- В Scala [параметры типа](generic-classes.html) и [члены абстрактного типа](abstract-type-members.html) могут быть ограничены определенными диапазонами. Такие диапазоны ограничивают конкретные значение типа и, возможно, предоставляют больше информации о членах таких типов. _Верхнее ограничение типа_ `T <: A` указывает на то что тип `T` относится к подтипу типа `A`. Приведем пример, демонстрирующий верхнее ограничение для типа класса `PetContainer`: +{% tabs upper-type-bounds class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds %} + ```scala mdoc abstract class Animal { def name: String @@ -43,10 +42,53 @@ val dogContainer = new PetContainer[Dog](new Dog) val catContainer = new PetContainer[Cat](new Cat) ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds %} + +```scala +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 = PetContainer[Dog](Dog()) +val catContainer = PetContainer[Cat](Cat()) +``` + +{% endtab %} +{% endtabs %} + +{% tabs upper-type-bounds_error class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_error %} + ```scala mdoc:fail // это не скомпилируется val lionContainer = new PetContainer[Lion](new Lion) ``` + +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_error %} + +```scala +// это не скомпилируется +val lionContainer = PetContainer[Lion](Lion()) +``` + +{% endtab %} +{% endtabs %} + Класс `PetContainer` принимает тип `P`, который должен быть подтипом `Pet`. `Dog` и `Cat` - это подтипы `Pet`, поэтому мы можем создать новые `PetContainer[Dog]` и `PetContainer[Cat]`. Однако, если мы попытаемся создать `PetContainer[Lion]`, то получим следующую ошибку: `type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]` diff --git a/_ru/tour/variances.md b/_ru/tour/variances.md index c59549f340..c122728f51 100644 --- a/_ru/tour/variances.md +++ b/_ru/tour/variances.md @@ -1,31 +1,50 @@ --- layout: tour title: Вариантность - -discourse: true - partof: scala-tour - num: 19 language: ru next-page: upper-type-bounds previous-page: generic-classes - --- -Вариантность (Variances) - это указание определенной специфики взаимосвязи между связанными типам. Scala поддерживает вариантную аннотацию типов у [обобщенных классов](generic-classes.html), что позволяет им быть ковариантными, контрвариантными или инвариантными (если нет никакого указание на вариантность). Использование вариантности в системе типов позволяет устанавливать понятные взаимосвязи между сложными типами, в то время как отсутствие вариантности может ограничить повторное использование абстракции класса. +Вариантность (Variances) - это указание определенной специфики взаимосвязи между связанными типами. +Scala поддерживает вариантную аннотацию типов у [обобщенных классов](generic-classes.html), +что позволяет им быть ковариантными, контрвариантными или инвариантными (если нет никакого указания на вариантность). +Использование вариантности в системе типов позволяет устанавливать понятные взаимосвязи между сложными типами, +в то время как отсутствие вариантности может ограничить повторное использование абстракции класса. + +{% tabs variances_1 %} +{% tab 'Scala 2 и 3' for=variances_1 %} ```scala mdoc class Foo[+A] // ковариантный класс -class Bar[-A] // контрвариантный класс -class Baz[A] // инвариантными класс +class Bar[-A] // контравариантный класс +class Baz[A] // инвариантный класс ``` -### Ковариантность +{% endtab %} +{% endtabs %} + +### Инвариантность + +По умолчанию параметры типа в Scala инвариантны: отношения подтипа между параметрами типа не отражаются в параметризованном типе. +Чтобы понять, почему это работает именно так, рассмотрим простой параметризованный тип, изменяемый контейнер. -Параметр типа `A` обобщенного класса можно сделать ковариантным с помощью аннотации `+A`. Для некоторого класса `List[+A]`, указание `A` в виде коварианта подразумевает, что для двух типов `A` и `B`, где `A` является подтипом `B`, `List[A]` представляет собой подтип `List[B]`. Что позволяет нам создавать очень полезные и интуитивно понятные взаимоотношения между типами с использованием обобщений (generics). +{% tabs invariance_1 %} +{% tab 'Scala 2 и 3' for=invariance_1 %} -Рассмотрим простую структуру классов: +```scala mdoc +class Box[A](var content: A) +``` + +{% endtab %} +{% endtabs %} + +Мы собираемся поместить в него значения типа `Animal` (животное). Этот тип определяется следующим образом: + +{% tabs invariance_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=invariance_2 %} ```scala mdoc abstract class Animal { @@ -35,120 +54,241 @@ case class Cat(name: String) extends Animal case class Dog(name: String) extends Animal ``` -И `Cat` (кошка) и `Dog`(собака) являются подтипами `Animal`(животное). Стандартная библиотека Scala имеет обобщенный неизменяемый тип `List[+A]`, где параметр типа `A` является ковариантным. Это означает, что `List[Cat]` - это `List[Animal]`, а `List[Dog]` - это также `List[Animal]`. Интуитивно понятно, что список кошек и список собак - это список животных и вы должны быть в состоянии заменить любого из них на `List[Animal]`. +{% endtab %} +{% tab 'Scala 3' for=invariance_2 %} + +```scala +abstract class Animal: + def name: String + +case class Cat(name: String) extends Animal +case class Dog(name: String) extends Animal +``` + +{% endtab %} +{% endtabs %} -В следующем примере метод `printAnimalNames` принимает в качестве аргумента список животных и выводит их имена в новой строке. Если бы `List[A]` не был ковариантным, последние два вызова метода не компилировались бы, что сильно ограничило бы полезность метода `printAnimalNames`. +Можно сказать, что `Cat` (кот) - это подтип `Animal`, `Dog` (собака) - также подтип `Animal`. +Это означает, что следующее допустимо и пройдет проверку типов: + +{% tabs invariance_3 %} +{% tab 'Scala 2 и 3' for=invariance_3 %} ```scala mdoc -object CovarianceTest extends App { - def printAnimalNames(animals: List[Animal]): Unit = { - animals.foreach { animal => - println(animal.name) - } - } +val myAnimal: Animal = Cat("Felix") +``` - val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) - val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) +{% endtab %} +{% endtabs %} - printAnimalNames(cats) - // Whiskers - // Tom +А контейнеры? +Является ли `Box[Cat]` подтипом `Box[Animal]`, как `Cat` подтип `Animal`? +На первый взгляд может показаться, что это правдоподобно, +но если мы попытаемся это сделать, компилятор сообщит об ошибке: - printAnimalNames(dogs) - // Fido - // Rex -} +{% tabs invariance_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=invariance_4 %} + +```scala mdoc:fail +val myCatBox: Box[Cat] = new Box[Cat](Cat("Felix")) +val myAnimalBox: Box[Animal] = myCatBox // не компилируется +val myAnimal: Animal = myAnimalBox.content ``` -### Контрвариантность +{% endtab %} +{% tab 'Scala 3' for=invariance_4 %} -Параметр типа `A` обобщенного класса можно сделать контрвариантным с помощью аннотации `-A`. Это создает схожее, но противоположное ковариантным, взаимоотношения между типом параметра и подтипами класса. То есть, для некого класса `Writer[-A]`, указание `A` контрвариантным подразумевает, что для двух типов `A` и `B` где `A` является подтипом `B`, `Writer[B]` является подтипом `Writer[A]`. +```scala +val myCatBox: Box[Cat] = Box[Cat](Cat("Felix")) +val myAnimalBox: Box[Animal] = myCatBox // не компилируется +val myAnimal: Animal = myAnimalBox.content +``` -Рассмотрим классы `Cat`, `Dog`, и `Animal`, описанные выше для следующего примера: +{% endtab %} +{% endtabs %} -```scala mdoc -abstract class Printer[-A] { - def print(value: A): Unit -} +Почему это может быть проблемой? +Мы можем достать из контейнера кота, и это все еще животное, не так ли? Ну да. +Но это не все, что мы можем сделать. Мы также можем заменить в контейнере кота другим животным. + +{% tabs invariance_5 %} +{% tab 'Scala 2 и 3' for=invariance_5 %} + +```scala + myAnimalBox.content = Dog("Fido") ``` -`Printer[A]` - это простой класс, который знает, как распечатать некоторый тип `A`. Давайте определим подклассы для конкретных типов: +{% endtab %} +{% endtabs %} -```scala mdoc -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) -} +{% tabs invariance_6 %} +{% tab 'Scala 2 и 3' for=invariance_6 %} + +```scala + val myCat: Cat = myCatBox.content // myCat стал бы собакой Fido! ``` -Если `Printer[Cat]` знает, как распечатать любой класс `Cat` в консоли, а `Printer[Animal]` знает, как распечатать любое `Animal` в консоли, то разумно если `Printer[Animal]` также знает, как распечатать любое `Cat`. Обратного отношения нет, потому что `Printer[Cat]` не знает, как распечатать любой `Animal` в консоли. Чтоб иметь возможность заменить `Printer[Cat]` на `Printer[Animal]`, необходимо `Printer[A]` сделать контрвариантным. +{% endtab %} +{% endtabs %} -```scala mdoc -object ContravarianceTest extends App { - val myCat: Cat = Cat("Boots") +Из этого мы должны сделать вывод, что между `Box[Cat]` и `Box[Animal]` не может быть отношения подтипа, +хотя между `Cat` и `Animal` это отношение есть. - 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) -} -``` +Но что, если мы не сможем поместить собаку в контейнер? +Тогда мы бы могли просто вернуть нашего кота, и это не проблема, чтобы можно было следовать отношениям подтипа. +Оказывается, это действительно то, что мы можем сделать. -Результатом работы этой программы будет: +{% tabs covariance_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=covariance_1 %} +```scala mdoc +class ImmutableBox[+A](val content: A) +val catbox: ImmutableBox[Cat] = new ImmutableBox[Cat](Cat("Felix")) +val animalBox: ImmutableBox[Animal] = catbox // теперь код компилируется ``` -The cat's name is: Boots -The animal's name is: Boots + +{% endtab %} +{% tab 'Scala 3' for=covariance_1 %} + +```scala +class ImmutableBox[+A](val content: A) +val catbox: ImmutableBox[Cat] = ImmutableBox[Cat](Cat("Felix")) +val animalBox: ImmutableBox[Animal] = catbox // теперь код компилируется ``` -### Инвариантность +{% endtab %} +{% endtabs %} -Обобщенные классы в Scala по умолчанию являются инвариантными. Это означает, что они не являются ни ковариантными, ни контрвариантными друг другу. В контексте следующего примера класс `Container` является инвариантным. Между `Container[Cat]` и `Container[Animal]`, нет ни прямой, ни обратной взаимосвязи. +Мы говорим, что `ImmutableBox` _ковариантен_ в `A` - на это указывает `+` перед `A`. + +Более формально это дает нам следующее отношение: +если задано некоторое `class Cov[+T]`, то если `A` является подтипом `B`, то `Cov[A]` является подтипом `Cov[B]`. +Это позволяет создавать очень полезные и интуитивно понятные отношения подтипов с помощью обобщения. + +В следующем менее надуманном примере метод `printAnimalNames` принимает список животных в качестве аргумента +и печатает их имена с новой строки. +Если бы `List[A]` не был бы ковариантным, последние два вызова метода не компилировались бы, +что сильно ограничивало бы полезность метода `printAnimalNames`. + +{% tabs covariance_2 %} +{% tab 'Scala 2 и 3' for=covariance_2 %} ```scala mdoc -class Container[A](value: A) { - private var _value: A = value - def getValue: A = _value - def setValue(value: A): Unit = { - _value = value +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")) + +// печатает: "Whiskers", "Tom" +printAnimalNames(cats) + +// печатает: "Fido", "Rex" +printAnimalNames(dogs) ``` -Может показаться что `Container[Cat]` должен также являться и `Container[Animal]`, но позволить мутабельному обобщенному классу быть ковариантным было бы небезопасно. В данном примере очень важно, чтобы `Container` был инвариантным. Предположим, что `Container` на самом деле был ковариантным, что-то вроде этого могло случиться: +{% endtab %} +{% endtabs %} + +### Контрвариантность + +Мы видели, что можем достичь ковариантности, убедившись, что не сможем поместить что-то в ковариантный тип, а только что-то получить. +Что, если бы у нас было наоборот, что-то, что можно положить, но нельзя вынуть? +Такая ситуация возникает, если у нас есть что-то вроде сериализатора, который принимает значения типа `A` +и преобразует их в сериализованный формат. + +{% tabs contravariance_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=contravariance_1 %} + +```scala mdoc +abstract class Serializer[-A] { + def serialize(a: A): String +} +val animalSerializer: Serializer[Animal] = new Serializer[Animal] { + def serialize(animal: Animal): String = s"""{ "name": "${animal.name}" }""" +} +val catSerializer: Serializer[Cat] = animalSerializer +catSerializer.serialize(Cat("Felix")) ``` -val catContainer: Container[Cat] = new Container(Cat("Felix")) -val animalContainer: Container[Animal] = catContainer -animalContainer.setValue(Dog("Spot")) -val cat: Cat = catContainer.getValue // Ой, мы бы закончили присвоением собаки к коту. + +{% endtab %} +{% tab 'Scala 3' for=contravariance_1 %} + +```scala +abstract class Serializer[-A]: + def serialize(a: A): String + +val animalSerializer: Serializer[Animal] = Serializer[Animal](): + def serialize(animal: Animal): String = s"""{ "name": "${animal.name}" }""" + +val catSerializer: Serializer[Cat] = animalSerializer +catSerializer.serialize(Cat("Felix")) ``` -К счастью, компилятор остановит нас прежде, чем мы зайдем так далеко. +{% endtab %} +{% endtabs %} -### Другие Примеры +Мы говорим, что `Serializer` _контравариантен_ в `A`, и на это указывает `-` перед `A`. +Более общий сериализатор является подтипом более конкретного сериализатора. -Другим примером, который может помочь понять вариантность, является трейт `Function1[-T, +R]` из стандартной библиотеки Scala. `Function1` представляет собой функцию с одним параметром, где первый тип `T` представляет собой тип параметра, а второй тип `R` представляет собой тип результата. Функция `Function1` является контрвариантной в рамках типа принимаемого аргумента, а ковариантной - в рамках возвращаемого типа. Для этого примера мы будем использовать явное обозначение типа `A =>B` чтоб продемонстрировать `Function1[A, B]`. +Более формально это дает нам обратное отношение: если задано некоторое `class Contra[-T]`, +то если `A` является подтипом `B`, `Contra[B]` является подтипом `Contra[A]`. -Рассмотрим схожий пример `Cat`, `Dog`, `Animal` в той же взаимосвязи что и раньше, плюс следующее: +### Неизменность и вариантность -```scala mdoc -abstract class SmallAnimal extends Animal -case class Mouse(name: String) extends SmallAnimal +Неизменяемость является важной частью проектного решения, связанного с использованием вариантности. +Например, коллекции Scala систематически различают [изменяемые и неизменяемые коллекции](https://docs.scala-lang.org/ru/overviews/collections-2.13/overview.html). +Основная проблема заключается в том, что ковариантная изменяемая коллекция может нарушить безопасность типов. +Вот почему `List` - ковариантная коллекция, а `scala.collection.mutable.ListBuffer` - инвариантная коллекция. +`List` - это коллекция в `package scala.collection.immutable`, поэтому она гарантированно будет неизменяемой для всех. +Принимая во внимание, что `ListBuffer` изменяем, то есть вы можете обновлять, добавлять или удалять элементы `ListBuffer`. + +Чтобы проиллюстрировать проблему ковариантности и изменчивости, предположим, +что `ListBuffer` ковариантен, тогда следующий проблемный пример скомпилируется (на самом деле он не компилируется): + +{% tabs immutability_and_variance_2 %} +{% tab 'Scala 2 и 3' %} + +```scala mdoc:fail +import scala.collection.mutable.ListBuffer + +val bufInt: ListBuffer[Int] = ListBuffer[Int](1,2,3) +val bufAny: ListBuffer[Any] = bufInt +bufAny(0) = "Hello" +val firstElem: Int = bufInt(0) ``` -Предположим, мы работаем с функциями, которые принимают типы животных и возвращают типы еды, которую они едят. Если мы хотим `Cat => SmallAnimal` (потому что кошки едят маленьких животных), но вместо этого мы получим функцию `Animal => Mouse`, то наша программа все равно будет работать. Интуитивно функция `Animal => Mouse` все равно будет принимать `Cat` в качестве аргумента, тк `Cat` является `Animal`, и возвращать `Mouse` - который также является и `SmallAnimal`. Поскольку мы можем безопасно заменить первое вторым, можно сказать, что `Animal => Mouse` аналогично `Cat => SmallAnimal`. +{% endtab %} +{% endtabs %} + +Если бы приведенный выше код был бы возможен, то вычисление `firstElem` завершилась бы ошибкой с `ClassCastException`, +потому что `bufInt(0)` теперь содержит `String`, а не `Int`. + +Инвариантность `ListBuffer` означает, что `ListBuffer[Int]` не является подтипом `ListBuffer[Any]`, +несмотря на то, что `Int` является подтипом `Any`, +и поэтому `bufInt` не может быть присвоен в качестве значения `bufAny`. ### Сравнение с другими языками -В языках, похожих на Scala, разные способы поддержи вариантности. Например, указания вариантности в Scala очень похожи на то, как это делается в C#, где такие указания добавляются при объявлении абстракции класса (вариантность при объявлении). Однако в Java, указание вариантности задается непосредственно при использовании абстракции класса (вариантность при использовании). +В языках, похожих на Scala, разные способы поддержки вариантности. +Например, указания вариантности в Scala очень похожи на то, как это делается в C#, +где такие указания добавляются при объявлении абстракции класса (вариантность при объявлении). +Однако в Java, указание вариантности задается непосредственно при использовании абстракции класса (вариантность при использовании). + +Тенденция Scala к неизменяемым типам делает ковариантные и контравариантные типы более распространенными, +чем в других языках, поскольку изменяемый универсальный тип должен быть инвариантным. diff --git a/_sass/base/helper.scss b/_sass/base/helper.scss index 1b32d31165..8b6f9c46d2 100755 --- a/_sass/base/helper.scss +++ b/_sass/base/helper.scss @@ -3,12 +3,50 @@ //------------------------------------------------ .wrap { - @include outer-container; - @include padding(0 20px); + @include outer-container; + @include padding(0 20px); +} + +.place-inline { + // add vertical margin + @include outer-container; + @include margin(20px 0); +} + +.inline-sticky-top { + position: sticky; + top: 15px; + margin-bottom: 25px; + background: #fff; + -webkit-box-shadow: 0 0 18px 20px #fff; + -moz-box-shadow: 0 0 18px 20px #fff; + box-shadow: 0 0 18px 20px #fff; + z-index: 5; +} + +.inline-sticky-top.inline-sticky-top-higher { + z-index: 7; +} + +.wrap-inline { + // add vertical padding + @include outer-container; + @include padding(20px 0); +} + +.wrap-tab { + @extend .wrap-narrow; + margin-top: 20px; + margin-bottom: 20px; +} + +.wrap-narrow { + @include outer-container; + @include padding(0 10px); } .dot { - font-size: 10px; - color: rgba($base-font-color-light, 0.6); - margin: 0 3px; + font-size: 10px; + color: rgba($base-font-color-light, 0.6); + margin: 0 3px; } diff --git a/_sass/components/alt-details.scss b/_sass/components/alt-details.scss new file mode 100644 index 0000000000..c86febae5d --- /dev/null +++ b/_sass/components/alt-details.scss @@ -0,0 +1,83 @@ +// ALT-DETAILS +//------------------------------------------------ +//------------------------------------------------ + +.alt-details.help-info { + .alt-details-toggle { + background-color: $brand-primary; + color: white; + + &:hover { + background-color: darken($brand-primary, 15%); + } + } + + .alt-details-detail { + background: #fae6e6 + } +} + +.alt-details { + @include span-columns(12); + + .alt-details-toggle { + @include span-columns(12); + font-family: $base-font-family; + line-height: normal; + text-align: center; + border: none; + background-color: $brand-tertiary; + padding: 5px 10px; + border-radius: $border-radius-large; + font-size: $font-size-medium; + cursor: pointer; + font-weight: 500; + color: $gray-dark; + + &:hover { + background-color: darken($brand-tertiary, 15%); + } + + &:after { + // show a right arrow at the end of the toggle element + content: "\f138"; // + font-family: "Font Awesome 5 Free"; + font-weight: 900; + font-size: 15px; + float: right; + margin-top: 2px; + } + + } + + .alt-details-control { + margin: 0; + } + + .alt-details-control+.alt-details-toggle+.alt-details-detail { + // by default, hide the details + position: absolute; + top: -999em; + left: -999em; + } + + .alt-details-control:checked+.alt-details-toggle+.alt-details-detail { + // show the details when the control is checked + position: static; + } + + .alt-details-control:checked+.alt-details-toggle:after { + // change the marker on the toggle label to a down arrow + content: "\f13a"; // + } + + .alt-details-detail { + // The detail box appears to be underneath the toggle button + // so we add a padding to the top and push it up. + border-bottom: $base-border-gray; + border-left: $base-border-gray; + border-right: $base-border-gray; + padding-top: 15px; + margin-top: 15px; + } +} diff --git a/_sass/components/code.scss b/_sass/components/code.scss index e771eb6ad1..15e8c641e3 100755 --- a/_sass/components/code.scss +++ b/_sass/components/code.scss @@ -23,3 +23,45 @@ } } + +.code-snippet-area { + width: 100%; + margin-top: 1em; + margin-bottom: 1em; + position: relative; // so we can position the buttons + + &:hover { + + // display the copy buttons on hover of the whole area + .code-snippet-buttons { + opacity: 0.95; + + &:active { + transition: none; + opacity: 0.7; + } + } + } + + .code-snippet-buttons { + opacity: 0; // default invisible until hover + transition: $base-transition; + position: absolute; // so we can position the buttons + right: 3px; + top: 5px; + + button { + border: none; + background: #fff; + font-size: $font-size-medium; + color: $gray-darker; + cursor: pointer; + } + } + + .code-snippet-display { + margin-top: 0px; + margin-bottom: 0px; + width: 100%; + } +} diff --git a/_sass/components/heading-anchor.scss b/_sass/components/heading-anchor.scss new file mode 100644 index 0000000000..a38b6d96bf --- /dev/null +++ b/_sass/components/heading-anchor.scss @@ -0,0 +1,12 @@ +// HEADING ANCHOR +//------------------------------------------------ +//------------------------------------------------ + +.heading-anchor { + visibility: hidden; + padding-left: 0.5em; + + &:hover { + text-decoration: none; + } +} diff --git a/_sass/components/tab.scss b/_sass/components/tab.scss index b5a9bb89c3..8207f3eb52 100755 --- a/_sass/components/tab.scss +++ b/_sass/components/tab.scss @@ -2,15 +2,21 @@ //------------------------------------------------ //------------------------------------------------ +// dynamic tab switching based on https: //levelup.gitconnected.com/tabbed-interfaces-without-javascript-661bab1eaec8 + .nav-tab { border-bottom: $base-border-gray; @include display(flex); @include align-items(center); @include justify-content(flex-start); - margin-bottom: 10px; + overflow-x: auto; // scrollbar when width is too small. + overflow-y: hidden; .item-tab { + + label, a { + transition: none; // do not animate the transition to active color: $base-font-color-light; display: block; padding: 0 20px 10px; @@ -23,21 +29,107 @@ color: $base-font-color; } - &.active { - border-bottom: 2px solid $brand-primary; - color: $brand-primary; - pointer-events: none; + &:hover { + cursor: pointer; } } + label { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + } + @include bp(small) { @include justify-content(space-between); + .item-tab { - a { - padding: 0 10px 10px; - font-size: $font-size-medium; + label, + a { + transition: none; // do not animate the transition to active + padding: 0 10px 10px; + font-size: $font-size-medium; + } } + } +} + +/* Active Tabs for standard screen size */ +.nav-tab>.item-tab>a.active, // used in the blog +.tabsection>input:nth-child(1):checked~.nav-tab .item-tab:nth-child(1) label, +.tabsection>input:nth-child(2):checked~.nav-tab .item-tab:nth-child(2) label, +.tabsection>input:nth-child(3):checked~.nav-tab .item-tab:nth-child(3) label, +.tabsection>input:nth-child(4):checked~.nav-tab .item-tab:nth-child(4) label, +.tabsection>input:nth-child(5):checked~.nav-tab .item-tab:nth-child(5) label, +.tabsection>input:nth-child(6):checked~.nav-tab .item-tab:nth-child(6) label, +.tabsection>input:nth-child(7):checked~.nav-tab .item-tab:nth-child(7) label, +.tabsection>input:nth-child(8):checked~.nav-tab .item-tab:nth-child(8) label, +.tabsection>input:nth-child(9):checked~.nav-tab .item-tab:nth-child(9) label { + border-bottom: 2px solid $brand-primary; + padding: 0 20px 8px; // so text does not jump up when active + color: $brand-primary; + pointer-events: none; +} + +/* Active Tabs for small screen size */ +@include bp(small) { + .nav-tab>.item-tab>a.active, + .tabsection>input:nth-child(1):checked~.nav-tab .item-tab:nth-child(1) label, + .tabsection>input:nth-child(2):checked~.nav-tab .item-tab:nth-child(2) label, + .tabsection>input:nth-child(3):checked~.nav-tab .item-tab:nth-child(3) label, + .tabsection>input:nth-child(4):checked~.nav-tab .item-tab:nth-child(4) label, + .tabsection>input:nth-child(5):checked~.nav-tab .item-tab:nth-child(5) label, + .tabsection>input:nth-child(6):checked~.nav-tab .item-tab:nth-child(6) label, + .tabsection>input:nth-child(7):checked~.nav-tab .item-tab:nth-child(7) label, + .tabsection>input:nth-child(8):checked~.nav-tab .item-tab:nth-child(8) label, + .tabsection>input:nth-child(9):checked~.nav-tab .item-tab:nth-child(9) label { + padding: 0 10px 8px; // match narrower padding for inactive tabs + } +} + +.tabsection { + padding: 0.1px 0; // ensure inner content is correctly padded + border-left: 2px solid $base-border-color-gray; + + .nav-tab { + padding-left: 0; + margin-bottom: 0; + + .item-tab { + padding: 0; + margin-bottom: 0; + list-style: none; } } + + input { // hide the input because they are not interactive + display: block; /* "enable" hidden elements in IE/edge */ + position: absolute; /* then hide them off-screen */ + left: -100%; + } + + .tabcontent { + section { // by default, hide all sections until the user clicks on the associated label + position: absolute; + top: -999em; + left: -999em; + } + } +} + +.tabsection>input:nth-child(1):checked~.tabcontent section:nth-child(1), +.tabsection>input:nth-child(2):checked~.tabcontent section:nth-child(2), +.tabsection>input:nth-child(3):checked~.tabcontent section:nth-child(3), +.tabsection>input:nth-child(4):checked~.tabcontent section:nth-child(4), +.tabsection>input:nth-child(5):checked~.tabcontent section:nth-child(5), +.tabsection>input:nth-child(6):checked~.tabcontent section:nth-child(6), +.tabsection>input:nth-child(7):checked~.tabcontent section:nth-child(7), +.tabsection>input:nth-child(8):checked~.tabcontent section:nth-child(8), +.tabsection>input:nth-child(9):checked~.tabcontent section:nth-child(9) { + position: static; } diff --git a/_sass/components/wip-notice.scss b/_sass/components/wip-notice.scss index b4a011e9fa..3c84c75ac0 100644 --- a/_sass/components/wip-notice.scss +++ b/_sass/components/wip-notice.scss @@ -2,7 +2,8 @@ position: relative; top: 1em; padding: 1em; - background: rgba(255,255,255,0.7); + border-radius: 5px; + background: $gray-light; a { color: $brand-primary; diff --git a/_sass/layout/details-summary.scss b/_sass/layout/details-summary.scss new file mode 100644 index 0000000000..1f18d4cd64 --- /dev/null +++ b/_sass/layout/details-summary.scss @@ -0,0 +1,3 @@ +details > summary > p { + display: inline; +} diff --git a/_sass/layout/doc-navigation.scss b/_sass/layout/doc-navigation.scss index 7fa75f5d6a..7539881c3f 100644 --- a/_sass/layout/doc-navigation.scss +++ b/_sass/layout/doc-navigation.scss @@ -65,7 +65,7 @@ $nav-height: 46px; position: absolute; margin-top: 10px; display: none; - z-index: 1; + z-index: 40; box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); @include bp(medium) { diff --git a/_sass/layout/documentation.scss b/_sass/layout/documentation.scss index dd9863596d..c4e56bd3d4 100644 --- a/_sass/layout/documentation.scss +++ b/_sass/layout/documentation.scss @@ -62,75 +62,43 @@ font-weight: $font-bold; padding: 1px 5px; } -} - -a.new-version-notice { - display: block; - background: rgb(255, 255, 200); - font-size: large; - text-align: center; -} - -// Styles for the index of docs.scala-lang.org -.landing-page .title-page { - background: $gray-li; - h1 { - color: rgb(134,161,166); + .tag-inline { + float: none; } -} -.landing-page .table-of-content .wrap { - - position: relative; - - h5 { - margin: 0; - } - - .language-header { - padding: 30px; - background: $brand-tertiary; + h1, + h3, + h4, + h5, + h6 { + .heading-anchor { + color: $base-font-color; + } - h1 { - font-size: 1.875rem; - font-family: $base-font-family; - text-transform: uppercase; - text-shadow: $text-shadow; - color: #fff; + &:hover { + .heading-anchor { + visibility: visible; + } } } - .language-footer { - bottom: 0; - width: 100%; - - .go-btn { - display: block; - padding: 20px; - height: 66px; - background: #fff; - border-top: 1px solid lighten($base-font-color-light, 35%); - color: lighten($base-font-color-light, 10%); - font-weight: $font-regular; - font-size: $font-regular; - text-decoration: none; + h2 { + .heading-anchor { + color: $brand-tertiary; + } - &:hover { - cursor: pointer; - background: #E5EAEA; - color: rgba(0, 0, 0, 0.4); + &:hover { + .heading-anchor { + visibility: visible; } } } - - &:first-of-type { - margin-bottom: 20px; - } } -.landing-page .table-of-content .wrap.scala3 { - .language-header { - background: $brand-tertiary-dotty; - } +a.new-version-notice { + display: block; + background: rgb(255, 255, 200); + font-size: large; + text-align: center; } diff --git a/_sass/layout/footer.scss b/_sass/layout/footer.scss index d21eb9e50e..e6f690fd4e 100755 --- a/_sass/layout/footer.scss +++ b/_sass/layout/footer.scss @@ -84,7 +84,7 @@ text-align: center; z-index: 2; color: #f0f3f3; - background-color: #dc322f; + background-color: $brand-primary; border-radius: 50%; display: none; diff --git a/_sass/layout/glossary.scss b/_sass/layout/glossary.scss index e55ef1c9aa..8346ea1b40 100644 --- a/_sass/layout/glossary.scss +++ b/_sass/layout/glossary.scss @@ -55,7 +55,7 @@ } li { - h4 { + h3 { text-transform: none; margin-bottom: 2px; font-weight: $font-black; diff --git a/_sass/layout/header.scss b/_sass/layout/header.scss index ab392dfd77..ff1b4fdb70 100755 --- a/_sass/layout/header.scss +++ b/_sass/layout/header.scss @@ -18,6 +18,23 @@ padding: 10px 40px; } + &.alert-warning { + background: $warning-bg; + color: $warning-text; + + a { + color: $warning-link; + font-weight: bold; + text-decoration: underline; + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } + span { position: absolute; right: 20px; diff --git a/_sass/layout/inner-main.scss b/_sass/layout/inner-main.scss index 6233be6fdb..64074851c8 100755 --- a/_sass/layout/inner-main.scss +++ b/_sass/layout/inner-main.scss @@ -2,17 +2,21 @@ //------------------------------------------------ //------------------------------------------------ +#inner-main>section:nth-child(2) { + // corresponds to area with the content below the title + position: relative; + top: -80px; // have it overlap with the title area +} + #inner-main { background: $gray-lighter; padding-bottom: $padding-xlarge; - - section:nth-child(2) { - position: relative; - top: -80px; - } - .inner-box { + margin-bottom: 30px; + &:last-child { + margin-bottom: 0; + } padding: $padding-medium; background: #fff; @include border-radius($border-radius-base); @@ -43,6 +47,10 @@ order: 1; margin-bottom: 30px; } + + @include bp(medium) { + order: 3; // move TOC to the bottom on mobile + } } .content-nav-blog { @@ -77,13 +85,6 @@ &:nth-child(2n) { clear: none; } - - &:active, - &:focus, - &:hover { - text-decoration: none; - background: none; - } } } } diff --git a/_sass/layout/online-courses.scss b/_sass/layout/online-courses.scss new file mode 100644 index 0000000000..e9410fcf02 --- /dev/null +++ b/_sass/layout/online-courses.scss @@ -0,0 +1,45 @@ +.online-courses { + margin-bottom: 30px; +} + +.online-courses-wrapper { + @include clearfix; +} + +.online-courses-image { + @include span-columns(5); + @include bp(medium) { + @include span-columns(12); + } + + img { + margin-bottom: 0; + } +} + +.online-courses-content { + @include span-columns(7); + @include bp(medium) { + @include span-columns(12); + } + + h2 { + margin-top: 16px; + margin-bottom: 16px; + } + + blockquote, + p, + pre, + table, + ul { + margin-bottom: 8px; + } + + ol, + ul { + li { + margin-bottom: 4px; + } + } +} diff --git a/_sass/layout/sips.scss b/_sass/layout/sips.scss index 631c07762c..7fa2d5114e 100644 --- a/_sass/layout/sips.scss +++ b/_sass/layout/sips.scss @@ -103,41 +103,29 @@ .sips { - .pending, - .completed, - .dormant, - .rejected { - .date { - display: block; - font-size: $font-size-small; - text-transform: uppercase; - font-weight: $font-bold; - color: $base-font-color-light; - margin-top: 4px; - } + .sip-list { strong { font-weight: $font-black; + display: block; + } + + .no-fragmentation { + break-inside: avoid; } .tag { float: left; - position: relative; display: block; - top: 3px; color: #fff; text-transform: uppercase; font-size: 11px; font-weight: 700; padding: 1px 5px; + margin-left: 1px; + margin-top: 1px; } - } - .pending { - display: flex; - // flex-direction: column; - // flex-wrap: wrap; - // max-height: 400px; ul { list-style: inside disc; -webkit-column-count: 2; /* Chrome, Safari, Opera */ @@ -163,41 +151,4 @@ } } - .other-sips { - @include display(flex); - @include flex-direction(row); - @include flex-wrap(wrap); - @include justify-content(space-between); - - .completed, - .dormant, - .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/table-of-content.scss b/_sass/layout/table-of-content.scss index 642244d0c1..e846256f73 100755 --- a/_sass/layout/table-of-content.scss +++ b/_sass/layout/table-of-content.scss @@ -65,7 +65,9 @@ @include justify-content(flex-start); margin-bottom: 10px; - .fa { + .fa, + .fa-regular, + .fa-brands { font-size: 1.563rem; margin-right: 14px; color: $brand-primary; @@ -92,9 +94,9 @@ } } - &:active, - &:focus, - &:hover { + &.doc-item-link:active, + &.doc-item-link:focus, + &.doc-item-link:hover { text-decoration: none; background: $gray-lighter; } diff --git a/_sass/layout/title-page.scss b/_sass/layout/title-page.scss index e0a5353faf..208f0b9565 100755 --- a/_sass/layout/title-page.scss +++ b/_sass/layout/title-page.scss @@ -2,8 +2,8 @@ //------------------------------------------------ //------------------------------------------------ -.scala3 .title-page { - background: $brand-tertiary-dotty; +.outdated-page .title-page { + background: $brand-tertiary-outdated; } .title-page { diff --git a/_sass/layout/toc.scss b/_sass/layout/toc.scss index 0e5e7f71d1..dfacedd379 100644 --- a/_sass/layout/toc.scss +++ b/_sass/layout/toc.scss @@ -11,10 +11,6 @@ position: relative; } - @include bp(medium) { - display: none; - } - .contents { font-weight: 700; } @@ -23,14 +19,11 @@ .inner-toc { max-height: 60vh; overflow-y: auto; + position: relative; @include bp(large) { max-height: none; } - - &.book { - max-height: none; - } } #toc { @@ -83,11 +76,6 @@ } } -// disable floating for the book, since the TOC can become very large -.scala3 .sidebar-toc-wrapper { - position: relative; -} - .book #toc { ul { margin-top: 5px; diff --git a/_sass/layout/type-md.scss b/_sass/layout/type-md.scss index 0696bee09f..6548b1ff60 100755 --- a/_sass/layout/type-md.scss +++ b/_sass/layout/type-md.scss @@ -87,6 +87,11 @@ margin-bottom: 18px; } + p + .code-snippet-area { + // remove the margin-top for code snippet following a paragraph + margin-top: 0px; + } + h4, h5 { margin-bottom: 0.5rem; } @@ -144,35 +149,52 @@ li, p, - tr, - td, + dt, + dd, + pre { + // common code for all code (inline and blocks) + code { + border: $base-border-gray; + } + } + + li, + p, dt, dd { 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: 1px 3px; - margin: 0 2px; + background-color: #fff; + color: #859900; + @include border-radius($border-radius-medium); + padding: 2px 6px; } } - pre { - margin-bottom: 36px; + tr, + td{ + code { + font-family: 'Consolas'; + font-size: 0.9375rem; + } + } + + + pre { code { - padding: 20px; + overflow-x: auto; + display: block; font-size: $font-size-medium; @include border-radius($border-radius-base); + padding: 10px 7px; } } table { width: 100%; text-align: left; + border-collapse: collapse; thead { font-weight: $font-bold; @@ -180,8 +202,8 @@ td, th { - border-bottom: $base-border-gray; - padding: 6px 0; + border: $base-border-gray; + padding: 6px; } } @@ -197,6 +219,11 @@ font-style: italic; @include border-radius($border-radius-base); + &.help-info { + border: 2px dashed $brand-tertiary-dotty; + color: $brand-tertiary-dotty; + } + p { margin: 0; } diff --git a/_sass/utils/_variables.scss b/_sass/utils/_variables.scss index 33277a1b03..d018dbae37 100755 --- a/_sass/utils/_variables.scss +++ b/_sass/utils/_variables.scss @@ -7,6 +7,7 @@ $brand-primary: #DC322F; $brand-secondary: #859900; $brand-tertiary: #5CC6E4; +$brand-tertiary-outdated: #a9c0c6; $brand-tertiary-dotty: #E45C77; //------------------------------------------------- $gray-darker: #002B36; @@ -17,6 +18,10 @@ $gray-light: #E5EAEA; $gray-lighter: #F0F3F3; $apple-blue: #6dccf5; +$warning-bg: #FFA500; +$warning-link: #185eb3; +$warning-text: #000; + //------------------------------------------------- $headings-font-color: $gray-dark; $base-font-color: #4A5659; @@ -74,6 +79,8 @@ $padding-small: 20px; //------------------------------------------------ $border-radius-base: 3px; $border-radius-small: 2px; +$border-radius-medium: 10px; +$border-radius-large: 15px; // Breakpoints //------------------------------------------------ diff --git a/_scala3-reference/changed-features.md b/_scala3-reference/changed-features.md deleted file mode 100644 index c23a9031be..0000000000 --- a/_scala3-reference/changed-features.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Other Changed Features" -type: chapter -num: 51 -previous-page: /scala3/reference/other-new-features/type-test -next-page: /scala3/reference/changed-features/numeric-literals ---- - -The following pages document the features that have changed in Scala 3, compared to Scala 2. diff --git a/_scala3-reference/changed-features/compiler-plugins.md b/_scala3-reference/changed-features/compiler-plugins.md deleted file mode 100644 index c77796d71d..0000000000 --- a/_scala3-reference/changed-features/compiler-plugins.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Changes in Compiler Plugins" -type: section -num: 67 -previous-page: /scala3/reference/changed-features/eta-expansion -next-page: /scala3/reference/changed-features/lazy-vals-init ---- - -Compiler plugins are supported by Dotty (and Scala 3) since 0.9. There are two notable changes -compared to `scalac`: - -- No support for analyzer plugins -- Added support for research plugins - -[Analyzer plugins][1] in `scalac` run during type checking and may influence -normal type checking. This is a very powerful feature but for production usages, -a predictable and consistent type checker is more important. - -For experimentation and research, Scala 3 introduces _research plugin_. Research plugins -are more powerful than `scalac` analyzer plugins as they let plugin authors customize -the whole compiler pipeline. One can easily replace the standard typer by a custom one or -create a parser for a domain-specific language. However, research plugins are only -enabled for nightly or snaphot releases of Scala 3. - -Common plugins that add new phases to the compiler pipeline are called -_standard plugins_ in Scala 3. In terms of features, they are similar to -`scalac` plugins, despite minor changes in the API. - -## Using Compiler Plugins - -Both standard and research plugins can be used with `scalac` by adding the `-Xplugin:` option: - -```shell -scalac -Xplugin:pluginA.jar -Xplugin:pluginB.jar Test.scala -``` - -The compiler will examine the jar provided, and look for a property file named -`plugin.properties` in the root directory of the jar. The property file specifies -the fully qualified plugin class name. The format of a property file is as follows: - -```properties -pluginClass=dividezero.DivideZero -``` - -This is different from `scalac` plugins that required a `scalac-plugin.xml` file. - -Starting from 1.1.5, `sbt` also supports Scala 3 compiler plugins. Please refer to the -[`sbt` documentation][2] for more information. - -## Writing a Standard Compiler Plugin - -Here is the source code for a simple compiler plugin that reports integer divisions by -zero as errors. - -```scala -package dividezero - -import dotty.tools.dotc.ast.Trees.* -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Constants.Constant -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Decorators.* -import dotty.tools.dotc.core.StdNames.* -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin} -import dotty.tools.dotc.transform.{Pickler, Staging} - -class DivideZero extends StandardPlugin: - val name: String = "divideZero" - override val description: String = "divide zero check" - - def init(options: List[String]): List[PluginPhase] = - (new DivideZeroPhase) :: Nil - -class DivideZeroPhase extends PluginPhase: - import tpd.* - - val phaseName = "divideZero" - - override val runsAfter = Set(Pickler.name) - override val runsBefore = Set(Staging.name) - - override def transformApply(tree: Apply)(implicit ctx: Context): Tree = - tree match - case Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) - if rcvr.tpe <:< defn.IntType => - report.error("dividing by zero", tree.pos) - case _ => - () - tree -end DivideZeroPhase -``` - -The plugin main class (`DivideZero`) must extend the trait `StandardPlugin` -and implement the method `init` that takes the plugin's options as argument -and returns a list of `PluginPhase`s to be inserted into the compilation pipeline. - -Our plugin adds one compiler phase to the pipeline. A compiler phase must extend -the `PluginPhase` trait. In order to specify when the phase is executed, we also -need to specify a `runsBefore` and `runsAfter` constraints that are list of phase -names. - -We can now transform trees by overriding methods like `transformXXX`. - -## Writing a Research Compiler Plugin - -Here is a template for research plugins. - -```scala -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.plugins.ResearchPlugin - -class DummyResearchPlugin extends ResearchPlugin: - val name: String = "dummy" - override val description: String = "dummy research plugin" - - def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = - phases -end DummyResearchPlugin -``` - -A research plugin must extend the trait `ResearchPlugin` and implement the -method `init` that takes the plugin's options as argument as well as the compiler -pipeline in the form of a list of compiler phases. The method can replace, remove -or add any phases to the pipeline and return the updated pipeline. - - -[1]: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala -[2]: https://www.scala-sbt.org/1.x/docs/Compiler-Plugins.html diff --git a/_scala3-reference/changed-features/eta-expansion-spec.md b/_scala3-reference/changed-features/eta-expansion-spec.md deleted file mode 100644 index 78d021003c..0000000000 --- a/_scala3-reference/changed-features/eta-expansion-spec.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Automatic Eta Expansion - More Details" ---- - -## Motivation - -Scala maintains a convenient distinction between _methods_ and _functions_. -Methods are part of the definition of a class that can be invoked in objects while functions are complete objects themselves, making them first-class entities. For example, they can be assigned to variables. -These two mechanisms are bridged in Scala by a mechanism called -[_eta-expansion_](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#eta-expansion-section) -(also called eta-abstraction), which converts a reference to a method into a function. Intuitively, a method `m` can be passed around by turning it into an object: the function `x => m(x)`. - -In this snippet which assigns a method to a `val`, the compiler will perform _automatic eta-expansion_, as shown in the comment: - -```scala -def m(x: Int, y: String) = ??? -val f = m // becomes: val f = (x: Int, y: String) => m(x, y) -``` - -In Scala 2, a method reference `m` is converted to a function value only if the expected type is a function type, which means the conversion in the example above would not have been triggered, because `val f` does not have a type ascription. To still get eta-expansion, a shortcut `m _` would force the conversion. - -For methods with one or more parameters like in the example above, this restriction has now been dropped. The syntax `m _` is no longer needed and will be deprecated in the future. - -## Automatic eta-expansion and partial application -In the following example `m` can be partially applied to the first two parameters. -Assigning `m` to `f1` will automatically eta-expand. - -```scala -def m(x: Boolean, y: String)(z: Int): List[Int] -val f1 = m -val f2 = m(true, "abc") -``` - -This creates two function values: - -```scala -f1: (Boolean, String) => Int => List[Int] -f2: Int => List[Int] -``` - -## Automatic eta-expansion and implicit parameter lists - -Methods with implicit parameter lists will always get applied to implicit arguments. - -```scala -def foo(x: Int)(implicit p: Double): Float = ??? -implicit val bla: Double = 1.0 - -val bar = foo // val bar: Int => Float = ... -``` - -## Automatic Eta-Expansion and query types - -A method with context parameters can be expanded to a value of a context type by writing the expected context type explicitly. - -```scala -def foo(x: Int)(using p: Double): Float = ??? -val bar: Double ?=> Float = foo(3) -``` - -## Rules - -- If `m` has an argument list with one or more parameters, we always eta-expand -- If `m` is has an empty argument list (i.e. has type `()R`): - 1. If the expected type is of the form `() => T`, we eta expand. - 2. If m is defined by Java, or overrides a Java defined method, we insert `()`. - 3. Otherwise we issue an error of the form: - -Thus, an unapplied method with an empty argument list is only converted to a function when a function type is expected. It is considered best practice to either explicitly apply the method to `()`, or convert it to a function with `() => m()`. - -The method value syntax `m _` is deprecated. - -## Reference - -For more information, see [PR #2701](https://github.com/lampepfl/dotty/pull/2701). diff --git a/_scala3-reference/changed-features/eta-expansion.md b/_scala3-reference/changed-features/eta-expansion.md deleted file mode 100644 index 91516edca5..0000000000 --- a/_scala3-reference/changed-features/eta-expansion.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: "Automatic Eta Expansion" -type: section -num: 66 -previous-page: /scala3/reference/changed-features/pattern-matching -next-page: /scala3/reference/changed-features/compiler-plugins ---- - -The conversion of _methods_ into _functions_ has been improved and happens automatically for methods with one or more parameters. - -```scala -def m(x: Boolean, y: String)(z: Int): List[Int] -val f1 = m -val f2 = m(true, "abc") -``` - -This creates two function values: -```scala -f1: (Boolean, String) => Int => List[Int] -f2: Int => List[Int] -``` - -The syntax `m _` is no longer needed and will be deprecated in the future. - -## Automatic eta-expansion and nullary methods - -Automatic eta expansion does not apply to "nullary" methods that take an empty parameter list. - -```scala -def next(): T -``` - -Given a simple reference to `next` does not auto-convert to a function. -One has to write explicitly `() => next()` to achieve that. -Once again since the `_` is going to be deprecated it's better to write it this way -rather than `next _`. - -The reason for excluding nullary methods from automatic eta expansion -is that Scala implicitly inserts the `()` argument, which would -conflict with eta expansion. Automatic `()` insertion is -[limited](../dropped-features/auto-apply.html) in Scala 3, but the fundamental ambiguity -remains. - -[More details](eta-expansion-spec.html) diff --git a/_scala3-reference/changed-features/implicit-conversions-spec.md b/_scala3-reference/changed-features/implicit-conversions-spec.md deleted file mode 100644 index 32bb641dbf..0000000000 --- a/_scala3-reference/changed-features/implicit-conversions-spec.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Implicit Conversions - More Details" ---- - -## Implementation - -An implicit conversion, or _view_, from type `S` to type `T` is -defined by either: - -- An `implicit def` which has type `S => T` or `(=> S) => T` -- An implicit value which has type `Conversion[S, T]` - -The standard library defines an abstract class `Conversion`: - -```scala -package scala -@java.lang.FunctionalInterface -abstract class Conversion[-T, +U] extends Function1[T, U]: - def apply(x: T): U -``` - -Function literals are automatically converted to `Conversion` values. - -Views are applied in three situations: - -1. If an expression `e` is of type `T`, and `T` does not conform to - the expression's expected type `pt`. In this case, an implicit `v` - which is applicable to `e` and whose result type conforms to `pt` - is searched. The search proceeds as in the case of implicit - parameters, where the implicit scope is the one of `T => pt`. If - such a view is found, the expression `e` is converted to `v(e)`. -1. In a selection `e.m` with `e` of type `T`, if the selector `m` does - not denote an accessible member of `T`. In this case, a view `v` - which is applicable to `e` and whose result contains an accessible - member named `m` is searched. The search proceeds as in the case of - implicit parameters, where the implicit scope is the one of `T`. If - such a view is found, the selection `e.m` is converted to `v(e).m`. -1. In an application `e.m(args)` with `e` of type `T`, if the selector - `m` denotes some accessible member(s) of `T`, but none of these - members is applicable to the arguments `args`. In this case, a view - `v` which is applicable to `e` and whose result contains a method - `m` which is applicable to `args` is searched. The search proceeds - as in the case of implicit parameters, where the implicit scope is - the one of `T`. If such a view is found, the application - `e.m(args)` is converted to `v(e).m(args)`. - -# Differences with Scala 2 implicit conversions - -In Scala 2, views whose parameters are passed by-value take precedence -over views whose parameters are passed by-name. This is no longer the -case in Scala 3. A type error reporting the ambiguous conversions will -be emitted in cases where this rule would be applied in Scala 2: - -```scala -implicit def conv1(x: Int): String = x.toString -implicit def conv2(x: => Int): String = x.toString - -val x: String = 0 // Compiles in Scala2 (uses `conv1`), - // type error in Scala 3 because of ambiguity. -``` - -In Scala 2, implicit values of a function type would be considered as -potential views. In Scala 3, these implicit value need to have type -`Conversion`: - -```scala -// Scala 2: -def foo(x: Int)(implicit conv: Int => String): String = x - -// Becomes with Scala 3: -def foo(x: Int)(implicit conv: Conversion[Int, String]): String = x - -// Call site is unchanged: -foo(4)(_.toString) - -// Scala 2: -implicit val myConverter: Int => String = _.toString - -// Becomes with Scala 3: -implicit val myConverter: Conversion[Int, String] = _.toString -``` - -Note that implicit conversions are also affected by the [changes to implicit resolution](implicit-resolution.html) between Scala 2 and Scala 3. - -## Motivation for the changes - -The introduction of [`scala.Conversion`](https://github.com/lampepfl/dotty/blob/master/library/src/scala/Conversion.scala) -in Scala 3 and the decision to restrict implicit values of this type to be -considered as potential views comes from the desire to remove surprising -behavior from the language: - -```scala -implicit val m: Map[Int, String] = Map(1 -> "abc") - -val x: String = 1 // Scala 2: assigns "abc" to x - // Scala 3: type error -``` - -This snippet contains a type error. The right-hand side of `val x` -does not conform to type `String`. In Scala 2, the compiler will use -`m` as an implicit conversion from `Int` to `String`, whereas Scala 3 -will report a type error, because `Map` isn't an instance of -`Conversion`. - -## Migration path - -Implicit values that are used as views should see their type changed to `Conversion`. - -For the migration of implicit conversions that are affected by the -changes to implicit resolution, refer to the [Changes in Implicit Resolution](implicit-resolution.html) for more information. - -## Reference - -For more information about implicit resolution, see [Changes in Implicit Resolution](implicit-resolution.html). -Other details are available in [PR #2065](https://github.com/lampepfl/dotty/pull/2065). diff --git a/_scala3-reference/changed-features/implicit-conversions.md b/_scala3-reference/changed-features/implicit-conversions.md deleted file mode 100644 index 39b4cde31e..0000000000 --- a/_scala3-reference/changed-features/implicit-conversions.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "Implicit Conversions" -type: section -num: 60 -previous-page: /scala3/reference/changed-features/implicit-resolution -next-page: /scala3/reference/changed-features/overload-resolution ---- - -An _implicit conversion_, also called _view_, is a conversion that -is applied by the compiler in several situations: - -1. When an expression `e` of type `T` is encountered, but the compiler - needs an expression of type `S`. -1. When an expression `e.m` where `e` has type `T` but `T` defines no - member `m` is encountered. - -In those cases, the compiler looks in the implicit scope for a -conversion that can convert an expression of type `T` to an expression -of type `S` (or to a type that defines a member `m` in the second -case). - -This conversion can be either: - -1. An `implicit def` of type `T => S` or `(=> T) => S` -1. An implicit value of type `scala.Conversion[T, S]` - -Defining an implicit conversion will emit a warning unless the import -`scala.language.implicitConversions` is in scope, or the flag -`-language:implicitConversions` is given to the compiler. - -## Examples - -The first example is taken from `scala.Predef`. Thanks to this -implicit conversion, it is possible to pass a `scala.Int` to a Java -method that expects a `java.lang.Integer` - -```scala -import scala.language.implicitConversions -implicit def int2Integer(x: Int): java.lang.Integer = - x.asInstanceOf[java.lang.Integer] -``` - -The second example shows how to use `Conversion` to define an -`Ordering` for an arbitrary type, given existing `Ordering`s for other -types: - -```scala -import scala.language.implicitConversions -implicit def ordT[T, S]( - implicit conv: Conversion[T, S], - ordS: Ordering[S] - ): Ordering[T] = - // `ordS` compares values of type `S`, but we can convert from `T` to `S` - (x: T, y: T) => ordS.compare(x, y) - -class A(val x: Int) // The type for which we want an `Ordering` - -// Convert `A` to a type for which an `Ordering` is available: -implicit val AToInt: Conversion[A, Int] = _.x - -implicitly[Ordering[Int]] // Ok, exists in the standard library -implicitly[Ordering[A]] // Ok, will use the implicit conversion from - // `A` to `Int` and the `Ordering` for `Int`. -``` - -[More details](implicit-conversions-spec.html) diff --git a/_scala3-reference/changed-features/implicit-resolution.md b/_scala3-reference/changed-features/implicit-resolution.md deleted file mode 100644 index 22d6ba7d1f..0000000000 --- a/_scala3-reference/changed-features/implicit-resolution.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: "Changes in Implicit Resolution" -type: section -num: 59 -previous-page: /scala3/reference/changed-features/type-inference -next-page: /scala3/reference/changed-features/implicit-conversions ---- - -This section describes changes to the implicit resolution that apply both to the new `given`s and to the old-style `implicit`s in Scala 3. -Implicit resolution uses a new algorithm which caches implicit results -more aggressively for performance. There are also some changes that -affect implicits on the language level. - -**1.** Types of implicit values and result types of implicit methods -must be explicitly declared. Excepted are only values in local blocks -where the type may still be inferred: -```scala - class C { - - val ctx: Context = ... // ok - - /*!*/ implicit val x = ... // error: type must be given explicitly - - /*!*/ implicit def y = ... // error: type must be given explicitly - } - val y = { - implicit val ctx = this.ctx // ok - ... - } -``` -**2.** Nesting is now taken into account for selecting an implicit. Consider for instance the following scenario: -```scala - def f(implicit i: C) = { - def g(implicit j: C) = { - implicitly[C] - } - } -``` -This will now resolve the `implicitly` call to `j`, because `j` is nested -more deeply than `i`. Previously, this would have resulted in an -ambiguity error. The previous possibility of an implicit search failure -due to _shadowing_ (where an implicit is hidden by a nested definition) -no longer applies. - -**3.** Package prefixes no longer contribute to the implicit search scope of a type. Example: -```scala - package p - - given a: A = A() - - object o: - given b: B = B() - type C -``` -Both `a` and `b` are visible as implicits at the point of the definition -of `type C`. However, a reference to `p.o.C` outside of package `p` will -have only `b` in its implicit search scope but not `a`. - -In more detail, here are the rules for what constitutes the implicit scope of -a type: - -**Definition:** A reference is an _anchor_ if it refers to an object, a class, a trait, an abstract type, an opaque type alias, or a match type alias. References to packages and package objects are anchors only under `-source:3.0-migration`. - -**Definition:** The _anchors_ of a type _T_ is a set of references defined as follows: - - 1. If _T_ is a reference to an anchor, _T_ itself plus, if _T_ is of the form _P#A_, the anchors of _P_. - 1. If _T_ is an alias of _U_, the anchors of _U_. - 1. If _T_ is a reference to a type parameter, the union of the anchors of both of its bounds. - 1. If _T_ is a singleton reference, the anchors of its underlying type, plus, - if _T_ is of the form _(P#x).type_, the anchors of _P_. - 1. If _T_ is the this-type _o.this_ of a static object _o_, the anchors of a term reference _o.type_ to that object. - 1. If _T_ is some other type, the union of the anchors of each constituent type of _T_. - - **Definition:** The _implicit scope_ of a type _T_ is the smallest set _S_ of term references such that - - 1. If _T_ is a reference to a class, _S_ includes a reference to the companion object - of the class, if it exists, as well as the implicit scopes of all of _T_'s parent classes. - 1. If _T_ is a reference to an object, _S_ includes _T_ itself as well as - the implicit scopes of all of _T_'s parent classes. - 1. If _T_ is a reference to an opaque type alias named _A_, _S_ includes - a reference to an object _A_ defined in the same scope as the type, if it exists, - as well as the implicit scope of _T_'s underlying type or bounds. - 1. If _T_ is a reference to an abstract type or match type alias - named _A_, _S_ includes a reference to an object _A_ defined in the same scope as the type, if it exists, as well as the implicit scopes of _T_'s given bounds. - 1. If _T_ is a reference to an anchor of the form _p.A_ then _S_ also includes - all term references on the path _p_. - 1. If _T_ is some other type, _S_ includes the implicit scopes of all anchors of _T_. - - -**4.** The treatment of ambiguity errors has changed. If an ambiguity is encountered in some recursive step of an implicit search, the ambiguity is propagated to the caller. - -Example: Say you have the following definitions: -```scala - class A - class B extends C - class C - implicit def a1: A - implicit def a2: A - implicit def b(implicit a: A): B - implicit def c: C -``` -and the query `implicitly[C]`. - -This query would now be classified as ambiguous. This makes sense, after all -there are two possible solutions, `b(a1)` and `b(a2)`, neither of which is better -than the other and both of which are better than the third solution, `c`. -By contrast, Scala 2 would have rejected the search for `A` as -ambiguous, and subsequently have classified the query `b(implicitly[A])` as a normal fail, -which means that the alternative `c` would be chosen as solution! - -Scala 2's somewhat puzzling behavior with respect to ambiguity has been exploited to implement -the analogue of a "negated" search in implicit resolution, where a query `Q1` fails if some -other query `Q2` succeeds and `Q1` succeeds if `Q2` fails. With the new cleaned up behavior -these techniques no longer work. But there is now a new special type `scala.util.NotGiven` -which implements negation directly. For any query type `Q`, `NotGiven[Q]` succeeds if and only if -the implicit search for `Q` fails. - -**5.** The treatment of divergence errors has also changed. A divergent implicit is treated as a normal failure, after which alternatives are still tried. This also makes sense: Encountering a divergent implicit means that we assume that no finite solution can be found on the corresponding path, but another path can still be tried. By contrast, -most (but not all) divergence errors in Scala 2 would terminate the implicit search as a whole. - -**6.** Scala 2 gives a lower level of priority to implicit conversions with call-by-name parameters relative to implicit conversions with call-by-value parameters. Scala 3 drops this distinction. So the following code snippet would be ambiguous in Scala 3: - -```scala - implicit def conv1(x: Int): A = new A(x) - implicit def conv2(x: => Int): A = new A(x) - def buzz(y: A) = ??? - buzz(1) // error: ambiguous -``` -**7.** The rule for picking a _most specific_ alternative among a set of overloaded or implicit alternatives is refined to take context parameters into account. All else being equal, an alternative that takes some context parameters is taken to be less specific than an alternative that takes none. If both alternatives take context parameters, we try to choose between them as if they were methods with regular parameters. The following paragraph in the [SLS §6.26.3](https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#overloading-resolution) is affected by this change: - -_Original version:_ - -> An alternative A is _more specific_ than an alternative B if the relative weight of A over B is greater than the relative weight of B over A. - -_Modified version:_ - -An alternative A is _more specific_ than an alternative B if - - - the relative weight of A over B is greater than the relative weight of B over A, or - - the relative weights are the same, and A takes no implicit parameters but B does, or - - the relative weights are the same, both A and B take implicit parameters, and A is more specific than B if all implicit parameters in either alternative are replaced by regular parameters. - -**8.** The previous disambiguation of implicits based on inheritance depth is refined to make it transitive. Transitivity is important to guarantee that search outcomes are compilation-order independent. Here's a scenario where the previous rules violated transitivity: -```scala - class A extends B - object A { given a ... } - class B - object B extends C { given b ... } - class C { given c } -``` - Here `a` is more specific than `b` since the companion class `A` is a subclass of the companion class `B`. Also, `b` is more specific than `c` - since `object B` extends class `C`. But `a` is not more specific than `c`. This means if `a, b, c` are all applicable implicits, it makes - a difference in what order they are compared. If we compare `b` and `c` - first, we keep `b` and drop `c`. Then, comparing `a` with `b` we keep `a`. But if we compare `a` with `c` first, we fail with an ambiguity error. - -The new rules are as follows: An implicit `a` defined in `A` is more specific than an implicit `b` defined in `B` if - - - `A` extends `B`, or - - `A` is an object and the companion class of `A` extends `B`, or - - `A` and `B` are objects, - `B` does not inherit any implicit members from base classes (*), - and the companion class of `A` extends the companion class of `B`. - -Condition (*) is new. It is necessary to ensure that the defined relation is transitive. - - - - - -[//]: # todo: expand with precise rules diff --git a/_scala3-reference/changed-features/imports.md b/_scala3-reference/changed-features/imports.md deleted file mode 100644 index 39568769a2..0000000000 --- a/_scala3-reference/changed-features/imports.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: "Imports" -type: section -num: 56 -previous-page: /scala3/reference/changed-features/wildcards -next-page: /scala3/reference/changed-features/type-checking ---- - -The syntax of wildcard and renaming imports (and exports) has changed. - -## Wildcard Imports - -Wildcard imports are now expressed with `*` instead of underscore. Example: -```scala -import scala.annotation.* // imports everything in the annotation package -``` - -If you want to import a member named `*` specifically, you can use backticks around it. - -```scala -object A: - def * = ... - def min = ... - -object B: - import A.`*` // imports just `*` - -object C: - import A.* // imports everything in A -``` - -## Renaming Imports - -To rename or exclude an import, we now use `as` instead of `=>`. A single renaming import no longer needs to be enclosed in braces. Examples: - -```scala -import A.{min as minimum, `*` as multiply} -import Predef.{augmentString as _, *} // imports everything except augmentString -import scala.annotation as ann -import java as j -``` - -### Migration - -To support cross-building, Scala 3.0 supports the old import syntax with `_` for wildcards and `=>` for renamings in addition to the new one. The old syntax -will be dropped in a future versions. Automatic rewritings from old to new syntax -are offered under settings `-source 3.1-migration -rewrite`. - -### Syntax - -``` -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec -ImportSpec ::= NamedSelector - | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ -NamedSelector ::= id [‘as’ (id | ‘_’)] -WildCardSelector ::= ‘*' | ‘given’ [InfixType] -ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} -``` diff --git a/_scala3-reference/changed-features/interpolation-escapes.md b/_scala3-reference/changed-features/interpolation-escapes.md deleted file mode 100644 index c61ef98281..0000000000 --- a/_scala3-reference/changed-features/interpolation-escapes.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Escapes in interpolations" ---- - -In Scala 2 there is no straightforward way to represent a single quote character `"` in a single quoted interpolation. A `\` character can't be used for that because interpolators themselves decide how to handle escaping, so the parser doesn't know whether the `"` should be escaped or used as a terminator. - -In Scala 3, we can use the `$` meta character of interpolations to escape a `"` character. Example: - -```scala - val inventor = "Thomas Edison" - val interpolation = s"as $inventor said: $"The three great essentials to achieve anything worth while are: Hard work, Stick-to-itiveness, and Common sense.$"" -``` diff --git a/_scala3-reference/changed-features/lazy-vals-init.md b/_scala3-reference/changed-features/lazy-vals-init.md deleted file mode 100644 index a065cebb2a..0000000000 --- a/_scala3-reference/changed-features/lazy-vals-init.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Lazy Vals Initialization -type: section -num: 68 -previous-page: /scala3/reference/changed-features/compiler-plugins -next-page: /scala3/reference/changed-features/main-functions ---- - -Scala 3 implements [Version 6](https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html#version-6---no-synchronization-on-this-and-concurrent-initialization-of-fields) -of the [SIP-20] improved lazy vals initialization proposal. - -## Motivation - -The newly proposed lazy val initialization mechanism aims to eliminate the acquisition of resources -during the execution of the lazy val initializer block, thus reducing the possibility of a deadlock. -The concrete deadlock scenarios that the new lazy val initialization scheme eliminates are -summarized in the [SIP-20] document. - -## Implementation - -Given a lazy field of the form: - -```scala -class Foo { - lazy val bar = -} -``` - -The Scala 3 compiler will generate code equivalent to: - -```scala -class Foo { - import scala.runtime.LazyVals - var value_0: Int = _ - var bitmap: Long = 0L - val bitmap_offset: Long = LazyVals.getOffset(classOf[LazyCell], "bitmap") - - def bar(): Int = { - while (true) { - val flag = LazyVals.get(this, bitmap_offset) - val state = LazyVals.STATE(flag, ) - - if (state == ) { - return value_0 - } else if (state == ) { - if (LazyVals.CAS(this, bitmap_offset, flag, , )) { - try { - val result = - value_0 = result - LazyVals.setFlag(this, bitmap_offset, , ) - return result - } - catch { - case ex => - LazyVals.setFlag(this, bitmap_offset, , ) - throw ex - } - } - } else /* if (state == || state == ) */ { - LazyVals.wait4Notification(this, bitmap_offset, flag, ) - } - } - } -} -``` - -The state of the lazy val `` is represented with 4 values: 0, 1, 2 and 3. The state 0 -represents a non-initialized lazy val. The state 1 represents a lazy val that is currently being -initialized by some thread. The state 2 denotes that there are concurrent readers of the lazy val. -The state 3 represents a lazy val that has been initialized. `` is the id of the lazy -val. This id grows with the number of volatile lazy vals defined in the class. - -## Note on recursive lazy vals - -Ideally recursive lazy vals should be flagged as an error. The current behavior for -recursive lazy vals is undefined (initialization may result in a deadlock). - -## Reference - -* [SIP-20] - -[SIP-20]: https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html diff --git a/_scala3-reference/changed-features/main-functions.md b/_scala3-reference/changed-features/main-functions.md deleted file mode 100644 index 6f035278ed..0000000000 --- a/_scala3-reference/changed-features/main-functions.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: "Main Methods" -type: section -num: 69 -previous-page: /scala3/reference/changed-features/lazy-vals-init -next-page: /scala3/reference/dropped-features ---- - -Scala 3 offers a new way to define programs that can be invoked from the command line: -A `@main` annotation on a method turns this method into an executable program. -Example: - -```scala -@main def happyBirthday(age: Int, name: String, others: String*) = - val suffix = - age % 100 match - case 11 | 12 | 13 => "th" - case _ => - age % 10 match - case 1 => "st" - case 2 => "nd" - case 3 => "rd" - case _ => "th" - val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") - for other <- others do bldr.append(" and ").append(other) - bldr.toString -``` - -This would generate a main program `happyBirthday` that could be called like this - -``` -> scala happyBirthday 23 Lisa Peter -Happy 23rd birthday, Lisa and Peter -``` - -A `@main` annotated method can be written either at the top-level or in a statically accessible object. The name of the program is in each case the name of the method, without any object prefixes. The `@main` method can have an arbitrary number of parameters. -For each parameter type there must be an instance of the `scala.util.CommandLineParser.FromString` type class -that is used to convert an argument string to the required parameter type. -The parameter list of a main method can end in a repeated parameter that then -takes all remaining arguments given on the command line. - -The program implemented from a `@main` method checks that there are enough arguments on -the command line to fill in all parameters, and that argument strings are convertible to -the required types. If a check fails, the program is terminated with an error message. - -Examples: - -``` -> scala happyBirthday 22 -Illegal command line after first argument: more arguments expected - -> scala happyBirthday sixty Fred -Illegal command line: java.lang.NumberFormatException: For input string: "sixty" -``` - -The Scala compiler generates a program from a `@main` method `f` as follows: - - - It creates a class named `f` in the package where the `@main` method was found - - The class has a static method `main` with the usual signature. It takes an `Array[String]` - as argument and returns `Unit`. - - The generated `main` method calls method `f` with arguments converted using - methods in the [`scala.util.CommandLineParser`](https://scala-lang.org/api/3.x/scala/util/CommandLineParser$.html) object. - -For instance, the `happyBirthDay` method above would generate additional code equivalent to the following class: - -```scala -final class happyBirthday: - import scala.util.CommandLineParser as CLP - def main(args: Array[String]): Unit = - try - happyBirthday( - CLP.parseArgument[Int](args, 0), - CLP.parseArgument[String](args, 1), - CLP.parseRemainingArguments[String](args, 2)) - catch - case error: CLP.ParseError => CLP.showError(error) -``` - -**Note**: The `` modifier above expresses that the `main` method is generated -as a static method of class `happyBirthDay`. It is not available for user programs in Scala. Regular "static" members are generated in Scala using objects instead. - -`@main` methods are the recommended scheme to generate programs that can be invoked from the command line in Scala 3. They replace the previous scheme to write program as objects with a special `App` parent class. In Scala 2, `happyBirthday` could be written also like this: - -```scala -object happyBirthday extends App: - // needs by-hand parsing of arguments vector - ... -``` - -The previous functionality of `App`, which relied on the "magic" [`DelayedInit`]({% link _scala3-reference/dropped-features/delayed-init.md %}) trait, is no longer available. [`App`](https://scala-lang.org/api/3.x/scala/App.html) still exists in limited form for now, but it does not support command line arguments and will be deprecated in the future. If programs need to cross-build -between Scala 2 and Scala 3, it is recommended to use an explicit `main` method with an `Array[String]` argument instead. diff --git a/_scala3-reference/changed-features/match-syntax.md b/_scala3-reference/changed-features/match-syntax.md deleted file mode 100644 index 7ebc85b6d0..0000000000 --- a/_scala3-reference/changed-features/match-syntax.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Match Expressions" -type: section -num: 62 -previous-page: /scala3/reference/changed-features/overload-resolution -next-page: /scala3/reference/changed-features/vararg-splices ---- - -The syntactical precedence of match expressions has been changed. -`match` is still a keyword, but it is used like an alphabetical operator. This has several consequences: - - 1. `match` expressions can be chained: - - ```scala - xs match { - case Nil => "empty" - case _ => "nonempty" - } match { - case "empty" => 0 - case "nonempty" => 1 - } - ``` - - (or, dropping the optional braces) - - ```scala - xs match - case Nil => "empty" - case _ => "nonempty" - match - case "empty" => 0 - case "nonempty" => 1 - ``` - - 2. `match` may follow a period: - - ```scala - if xs.match - case Nil => false - case _ => true - then "nonempty" - else "empty" - ``` - - 3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }` now has to be - written `(x: T) match { ... }`. - -## Syntax - -The new syntax of match expressions is as follows. - -``` -InfixExpr ::= ... - | InfixExpr MatchClause -SimpleExpr ::= ... - | SimpleExpr ‘.’ MatchClause -MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ -``` diff --git a/_scala3-reference/changed-features/numeric-literals.md b/_scala3-reference/changed-features/numeric-literals.md deleted file mode 100644 index 8d62ba4aab..0000000000 --- a/_scala3-reference/changed-features/numeric-literals.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -title: "Numeric Literals" -type: section -num: 52 -previous-page: /scala3/reference/changed-features -next-page: /scala3/reference/changed-features/structural-types ---- - -**Note**: This feature is not yet part of the Scala 3 language definition. It can be made available by a language import: - -```scala -import scala.language.experimental.genericNumberLiterals -``` - -In Scala 2, numeric literals were confined to the primitive numeric types `Int`, `Long`, `Float`, and `Double`. Scala 3 allows to write numeric literals also for user-defined types. Example: - -```scala -val x: Long = -10_000_000_000 -val y: BigInt = 0x123_abc_789_def_345_678_901 -val z: BigDecimal = 110_222_799_799.99 - -(y: BigInt) match - case 123_456_789_012_345_678_901 => -``` - -The syntax of numeric literals is the same as before, except there are no pre-set limits -how large they can be. - -### Meaning of Numeric Literals - -The meaning of a numeric literal is determined as follows: - -- If the literal ends with `l` or `L`, it is a `Long` integer (and must fit in its legal range). -- If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`. -- If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`. - -In each of these cases the conversion to a number is exactly as in Scala 2 or in Java. If a numeric literal does _not_ end in one of these suffixes, its meaning is determined by the expected type: - -1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is - treated as a standard literal of that type. -2. If the expected type is a fully defined type `T` that has a given instance of type - `scala.util.FromDigits[T]`, the literal is converted to a value of type `T` by passing it as an argument to - the `fromDigits` method of that instance (more details below). -3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an - exponent), or as an `Int` literal (if not). (This last possibility is again as in Scala 2 or Java.) - -With these rules, the definition - -```scala -val x: Long = -10_000_000_000 -``` - -is legal by rule (1), since the expected type is `Long`. The definitions - -```scala -val y: BigInt = 0x123_abc_789_def_345_678_901 -val z: BigDecimal = 111222333444.55 -``` - -are legal by rule (2), since both `BigInt` and `BigDecimal` have `FromDigits` instances -(which implement the `FromDigits` subclasses `FromDigits.WithRadix` and `FromDigits.Decimal`, respectively). -On the other hand, - -```scala -val x = -10_000_000_000 -``` - -gives a type error, since without an expected type `-10_000_000_000` is treated by rule (3) as an `Int` literal, but it is too large for that type. - -### The FromDigits Trait - -To allow numeric literals, a type simply has to define a `given` instance of the -`scala.util.FromDigits` type class, or one of its subclasses. `FromDigits` is defined -as follows: - -```scala -trait FromDigits[T]: - def fromDigits(digits: String): T -``` - -Implementations of the `fromDigits` convert strings of digits to the values of the -implementation type `T`. -The `digits` string consists of digits between `0` and `9`, possibly preceded by a -sign ("+" or "-"). Number separator characters `_` are filtered out before -the string is passed to `fromDigits`. - -The companion object `FromDigits` also defines subclasses of `FromDigits` for -whole numbers with a given radix, for numbers with a decimal point, and for -numbers that can have both a decimal point and an exponent: - -```scala -object FromDigits: - - /** A subclass of `FromDigits` that also allows to convert whole - * number literals with a radix other than 10 - */ - trait WithRadix[T] extends FromDigits[T]: - def fromDigits(digits: String): T = fromDigits(digits, 10) - def fromDigits(digits: String, radix: Int): T - - /** A subclass of `FromDigits` that also allows to convert number - * literals containing a decimal point ".". - */ - trait Decimal[T] extends FromDigits[T] - - /** A subclass of `FromDigits`that allows also to convert number - * literals containing a decimal point "." or an - * exponent `('e' | 'E')['+' | '-']digit digit*`. - */ - trait Floating[T] extends Decimal[T] -``` - -A user-defined number type can implement one of those, which signals to the compiler -that hexadecimal numbers, decimal points, or exponents are also accepted in literals -for this type. - -### Error Handling - -`FromDigits` implementations can signal errors by throwing exceptions of some subtype -of `FromDigitsException`. `FromDigitsException` is defined with three subclasses in the -`FromDigits` object as follows: - -```scala -abstract class FromDigitsException(msg: String) extends NumberFormatException(msg) - -class NumberTooLarge (msg: String = "number too large") extends FromDigitsException(msg) -class NumberTooSmall (msg: String = "number too small") extends FromDigitsException(msg) -class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg) -``` - -### Example - -As a fully worked out example, here is an implementation of a new numeric class, `BigFloat`, that accepts numeric literals. `BigFloat` is defined in terms of a `BigInt` mantissa and an `Int` exponent: - -```scala -case class BigFloat(mantissa: BigInt, exponent: Int): - override def toString = s"${mantissa}e${exponent}" -``` - -`BigFloat` literals can have a decimal point as well as an exponent. E.g. the following expression -should produce the `BigFloat` number `BigFloat(-123, 997)`: - -```scala --0.123E+1000: BigFloat -``` - -The companion object of `BigFloat` defines an `apply` constructor method to construct a `BigFloat` -from a `digits` string. Here is a possible implementation: - -```scala -object BigFloat: - import scala.util.FromDigits - - def apply(digits: String): BigFloat = - val (mantissaDigits, givenExponent) = - digits.toUpperCase.split('E') match - case Array(mantissaDigits, edigits) => - val expo = - try FromDigits.intFromDigits(edigits) - catch case ex: FromDigits.NumberTooLarge => - throw FromDigits.NumberTooLarge(s"exponent too large: $edigits") - (mantissaDigits, expo) - case Array(mantissaDigits) => - (mantissaDigits, 0) - val (intPart, exponent) = - mantissaDigits.split('.') match - case Array(intPart, decimalPart) => - (intPart ++ decimalPart, givenExponent - decimalPart.length) - case Array(intPart) => - (intPart, givenExponent) - BigFloat(BigInt(intPart), exponent) -``` - -To accept `BigFloat` literals, all that's needed in addition is a `given` instance of type -`FromDigits.Floating[BigFloat]`: - -```scala - given FromDigits: FromDigits.Floating[BigFloat] with - def fromDigits(digits: String) = apply(digits) -end BigFloat -``` - -Note that the `apply` method does not check the format of the `digits` argument. It is -assumed that only valid arguments are passed. For calls coming from the compiler -that assumption is valid, since the compiler will first check whether a numeric -literal has the correct format before it gets passed on to a conversion method. - -### Compile-Time Errors - -With the setup of the previous section, a literal like - -```scala -1e10_0000_000_000: BigFloat -``` - -would be expanded by the compiler to - -```scala -BigFloat.FromDigits.fromDigits("1e100000000000") -``` - -Evaluating this expression throws a `NumberTooLarge` exception at run time. We would like it to -produce a compile-time error instead. We can achieve this by tweaking the `BigFloat` class -with a small dose of metaprogramming. The idea is to turn the `fromDigits` method -into a macro, i.e. make it an inline method with a splice as right-hand side. -To do this, replace the `FromDigits` instance in the `BigFloat` object by the following two definitions: - -```scala -object BigFloat: - ... - - class FromDigits extends FromDigits.Floating[BigFloat]: - def fromDigits(digits: String) = apply(digits) - - given FromDigits with - override inline def fromDigits(digits: String) = ${ - fromDigitsImpl('digits) - } -``` - -Note that an inline method cannot directly fill in for an abstract method, since it produces -no code that can be executed at runtime. That is why we define an intermediary class -`FromDigits` that contains a fallback implementation which is then overridden by the inline -method in the `FromDigits` given instance. That method is defined in terms of a macro -implementation method `fromDigitsImpl`. Here is its definition: - -```scala - private def fromDigitsImpl(digits: Expr[String])(using ctx: Quotes): Expr[BigFloat] = - digits.value match - case Some(ds) => - try - val BigFloat(m, e) = apply(ds) - '{BigFloat(${Expr(m)}, ${Expr(e)})} - catch case ex: FromDigits.FromDigitsException => - ctx.error(ex.getMessage) - '{BigFloat(0, 0)} - case None => - '{apply($digits)} -end BigFloat -``` - -The macro implementation takes an argument of type `Expr[String]` and yields -a result of type `Expr[BigFloat]`. It tests whether its argument is a constant -string. If that is the case, it converts the string using the `apply` method -and lifts the resulting `BigFloat` back to `Expr` level. For non-constant -strings `fromDigitsImpl(digits)` is simply `apply(digits)`, i.e. everything is -evaluated at runtime in this case. - -The interesting part is the `catch` part of the case where `digits` is constant. -If the `apply` method throws a `FromDigitsException`, the exception's message is issued as a compile time error in the `ctx.error(ex.getMessage)` call. - -With this new implementation, a definition like - -```scala -val x: BigFloat = 1234.45e3333333333 -``` - -would give a compile time error message: - -```scala -3 | val x: BigFloat = 1234.45e3333333333 - | ^^^^^^^^^^^^^^^^^^ - | exponent too large: 3333333333 -``` diff --git a/_scala3-reference/changed-features/operators.md b/_scala3-reference/changed-features/operators.md deleted file mode 100644 index 5e05c6bfbc..0000000000 --- a/_scala3-reference/changed-features/operators.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "Rules for Operators" -type: section -num: 54 -previous-page: /scala3/reference/changed-features/structural-types -next-page: /scala3/reference/changed-features/wildcards ---- - -The rules for infix operators have changed in some parts: - -First, an alphanumeric method can be used as an infix operator only if its definition carries an `infix` modifier. Second, it is recommended (but not enforced) to -augment definitions of symbolic operators with [`@targetName` annotations](../other-new-features/targetName.html). Finally, -a syntax change allows infix operators to be written on the left in a multi-line expression. - -## The `infix` Modifier - -An `infix` modifier on a method definition allows using the method as an infix operation. Example: - -```scala -import scala.annotation.targetName - -trait MultiSet[T]: - - infix def union(other: MultiSet[T]): MultiSet[T] - - def difference(other: MultiSet[T]): MultiSet[T] - - @targetName("intersection") - def *(other: MultiSet[T]): MultiSet[T] - -end MultiSet - -val s1, s2: MultiSet[Int] - -s1 union s2 // OK -s1 `union` s2 // also OK but unusual -s1.union(s2) // also OK - -s1.difference(s2) // OK -s1 `difference` s2 // OK -s1 difference s2 // gives a deprecation warning - -s1 * s2 // OK -s1 `*` s2 // also OK, but unusual -s1.*(s2) // also OK, but unusual -``` - -Infix operations involving alphanumeric operators are deprecated, unless -one of the following conditions holds: - - - the operator definition carries an `infix` modifier, or - - the operator was compiled with Scala 2, or - - the operator is followed by an opening brace. - -An alphanumeric operator is an operator consisting entirely of letters, digits, the `$` and `_` characters, or -any Unicode character `c` for which `java.lang.Character.isIdentifierPart(c)` returns `true`. - -Infix operations involving symbolic operators are always allowed, so `infix` is redundant for methods with symbolic names. - -The `infix` modifier can also be given to a type: - -```scala -infix type or[X, Y] -val x: String or Int = ... -``` - -### Motivation - -The purpose of the `infix` modifier is to achieve consistency across a code base in how a method or type is applied. The idea is that the author of a method decides whether that method should be applied as an infix operator or in a regular application. Use sites then implement that decision consistently. - -### Details - - 1. `infix` is a soft modifier. It is treated as a normal identifier except when in modifier position. - - 2. If a method overrides another, their infix annotations must agree. Either both are annotated with `infix`, or none of them are. - - 3. `infix` modifiers can be given to method definitions. The first non-receiver parameter list of an `infix` method must define exactly one parameter. Examples: - - ```scala - infix def op1(x: S): R // ok - infix def op2[T](x: T)(y: S): R // ok - infix def op3[T](x: T, y: S): R // error: two parameters - - extension (x: A) - infix def op4(y: B): R // ok - infix def op5(y1: B, y2: B): R // error: two parameters - ``` - - 4. `infix` modifiers can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like - - ```scala - infix type op[X, Y] - ``` - - can be applied using infix syntax, i.e. `A op B`. - - 5. To smooth migration to Scala 3.0, alphanumeric operators will only be deprecated from Scala 3.1 onwards, -or if the `-source future` option is given in Dotty/Scala 3. - -## The `@targetName` Annotation - -It is recommended that definitions of symbolic operators carry a [`@targetName` annotation](../other-new-features/targetName.html) that provides an encoding of the operator with an alphanumeric name. This has several benefits: - - - It helps interoperability between Scala and other languages. One can call - a Scala-defined symbolic operator from another language using its target name, - which avoids having to remember the low-level encoding of the symbolic name. - - It helps legibility of stacktraces and other runtime diagnostics, where the - user-defined alphanumeric name will be shown instead of the low-level encoding. - - It serves as a documentation tool by providing an alternative regular name - as an alias of a symbolic operator. This makes the definition also easier - to find in a search. - -## Syntax Change - -Infix operators can now appear at the start of lines in a multi-line expression. Examples: - -```scala -val str = "hello" - ++ " world" - ++ "!" - -def condition = - x > 0 - || - xs.exists(_ > 0) - || xs.isEmpty -``` - -Previously, those expressions would have been rejected, since the compiler's semicolon inference -would have treated the continuations `++ " world"` or `|| xs.isEmpty` as separate statements. - -To make this syntax work, the rules are modified to not infer semicolons in front of leading infix operators. -A _leading infix operator_ is - - a symbolic identifier such as `+`, or `approx_==`, or an identifier in backticks that - - starts a new line, and - - is not following a blank line, and - - is followed by at least one whitespace character and a token that can start an expression. - - Furthermore, if the operator appears on its own line, the next line must have at least - the same indentation width as the operator. - -Example: - -```scala - freezing - | boiling -``` - -This is recognized as a single infix operation. Compare with: - -```scala - freezing - !boiling -``` - -This is seen as two statements, `freezing` and `!boiling`. The difference is that only the operator in the first example -is followed by a space. - -Another example: - -```scala - println("hello") - ??? - ??? match { case 0 => 1 } -``` - -This code is recognized as three different statements. `???` is syntactically a symbolic identifier, but -neither of its occurrences is followed by a space and a token that can start an expression. diff --git a/_scala3-reference/changed-features/overload-resolution.md b/_scala3-reference/changed-features/overload-resolution.md deleted file mode 100644 index 6b20329970..0000000000 --- a/_scala3-reference/changed-features/overload-resolution.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: "Changes in Overload Resolution" -type: section -num: 61 -previous-page: /scala3/reference/changed-features/implicit-conversions -next-page: /scala3/reference/changed-features/match-syntax ---- - -Overload resolution in Scala 3 improves on Scala 2 in two ways. -First, it takes all argument lists into account instead of -just the first argument list. -Second, it can infer parameter types of function values even if they -are in the first argument list. - -## Looking Beyond the First Argument List - -Overloading resolution now can take argument lists into account when -choosing among a set of overloaded alternatives. -For example, the following code compiles in Scala 3, while it results in an -ambiguous overload error in Scala 2: - -```scala -def f(x: Int)(y: String): Int = 0 -def f(x: Int)(y: Int): Int = 0 - -f(3)("") // ok -``` - -The following code compiles as well: - -```scala -def g(x: Int)(y: Int)(z: Int): Int = 0 -def g(x: Int)(y: Int)(z: String): Int = 0 - -g(2)(3)(4) // ok -g(2)(3)("") // ok -``` - -To make this work, the rules for overloading resolution in [SLS §6.26.3](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#overloading-resolution) are augmented -as follows: - -> In a situation where a function is applied to more than one argument list, if overloading -resolution yields several competing alternatives when `n >= 1` parameter lists are taken -into account, then resolution re-tried using `n + 1` argument lists. - -This change is motivated by the new language feature -[extension methods](../contextual/extension-methods.html), where emerges the need to do -overload resolution based on additional argument blocks. - -## Parameter Types of Function Values - -The handling of function values with missing parameter types has been improved. We can now -pass such values in the first argument list of an overloaded application, provided -that the remaining parameters suffice for picking a variant of the overloaded function. -For example, the following code compiles in Scala 3, while it results in a -missing parameter type error in Scala2: - -```scala -def f(x: Int, f2: Int => Int) = f2(x) -def f(x: String, f2: String => String) = f2(x) -f("a", _.toUpperCase) -f(2, _ * 2) -``` - -To make this work, the rules for overloading resolution in [SLS §6.26.3](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#overloading-resolution) are modified -as follows: - -Replace the sentence - -> Otherwise, let `S1,…,Sm` be the vector of types obtained by typing each argument with an undefined expected type. - -with the following paragraph: - -> Otherwise, let `S1,…,Sm` be the vector of known types of all argument types, where the _known type_ of an argument `E` -is determined as followed: - - - If `E` is a function value `(p_1, ..., p_n) => B` that misses some parameter types, the known type - of `E` is `(S_1, ..., S_n) => ?`, where each `S_i` is the type of parameter `p_i` if it is given, or `?` - otherwise. Here `?` stands for a _wildcard type_ that is compatible with every other type. - - Otherwise the known type of `E` is the result of typing `E` with an undefined expected type. - -A pattern matching closure - -```scala -{ case P1 => B1 ... case P_n => B_n } -```` - -is treated as if it was expanded to the function value - -```scala -x => x match { case P1 => B1 ... case P_n => B_n } -``` - -and is therefore also approximated with a `? => ?` type. diff --git a/_scala3-reference/changed-features/pattern-bindings.md b/_scala3-reference/changed-features/pattern-bindings.md deleted file mode 100644 index be13cee3ac..0000000000 --- a/_scala3-reference/changed-features/pattern-bindings.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: "Pattern Bindings" -type: section -num: 64 -previous-page: /scala3/reference/changed-features/vararg-splices -next-page: /scala3/reference/changed-features/pattern-matching ---- - -In Scala 2, pattern bindings in `val` definitions and `for` expressions are -loosely typed. Potentially failing matches are still accepted at compile-time, -but may influence the program's runtime behavior. -From Scala 3.2 on, type checking rules will be tightened so that warnings are reported at compile-time instead. - -## Bindings in Pattern Definitions - -```scala -val xs: List[Any] = List(1, 2, 3) -val (x: String) :: _ = xs // error: pattern's type String is more specialized - // than the right-hand side expression's type Any -``` -This code gives a compile-time warning in Scala 3.2 (and also earlier Scala 3.x under the `-source future` setting) whereas it will fail at runtime with a `ClassCastException` in Scala 2. In Scala 3.2, a pattern binding is only allowed if the pattern is _irrefutable_, that is, if the right-hand side's type conforms to the pattern's type. For instance, the following is OK: -```scala -val pair = (1, true) -val (x, y) = pair -``` -Sometimes one wants to decompose data anyway, even though the pattern is refutable. For instance, if at some point one knows that a list `elems` is non-empty one might -want to decompose it like this: -```scala -val first :: rest = elems // error -``` -This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.2 it will give a warning. One can avoid the warning by marking the right-hand side with an `@unchecked` annotation: -```scala -val first :: rest = elems: @unchecked // OK -``` -This will make the compiler accept the pattern binding. It might give an error at runtime instead, if the underlying assumption that `elems` can never be empty is wrong. - -## Pattern Bindings in `for` Expressions - -Analogous changes apply to patterns in `for` expressions. For instance: - -```scala -val elems: List[Any] = List((1, 2), "hello", (3, 4)) -for (x, y) <- elems yield (y, x) // error: pattern's type (Any, Any) is more specialized - // than the right-hand side expression's type Any -``` -This code gives a compile-time warning in Scala 3.2 whereas in Scala 2 the list `elems` -is filtered to retain only the elements of tuple type that match the pattern `(x, y)`. -The filtering functionality can be obtained in Scala 3 by prefixing the pattern with `case`: -```scala -for case (x, y) <- elems yield (y, x) // returns List((2, 1), (4, 3)) -``` - -## Syntax Changes - -Generators in for expressions may be prefixed with `case`. -``` -Generator ::= [‘case’] Pattern1 ‘<-’ Expr -``` - -## Migration - -The new syntax is supported in Scala 3.0. However, to enable smooth cross compilation between Scala 2 and Scala 3, the changed behavior and additional type checks are only enabled under the `-source future` setting. They will be enabled by default in version 3.2 of the language. diff --git a/_scala3-reference/changed-features/pattern-matching.md b/_scala3-reference/changed-features/pattern-matching.md deleted file mode 100644 index c0f3afa1f5..0000000000 --- a/_scala3-reference/changed-features/pattern-matching.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: "Option-less pattern matching" -type: section -num: 65 -previous-page: /scala3/reference/changed-features/pattern-bindings -next-page: /scala3/reference/changed-features/eta-expansion ---- - -The implementation of pattern matching in Scala 3 was greatly simplified compared to Scala 2. From a user perspective, this means that Scala 3 generated patterns are a *lot* easier to debug, as variables all show up in debug modes and positions are correctly preserved. - -Scala 3 supports a superset of Scala 2 [extractors](https://www.scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns). - -## Extractors - -Extractors are objects that expose a method `unapply` or `unapplySeq`: - -```Scala -def unapply[A](x: T)(implicit x: B): U -def unapplySeq[A](x: T)(implicit x: B): U -``` - -Extractors that expose the method `unapply` are called fixed-arity extractors, which -work with patterns of fixed arity. Extractors that expose the method `unapplySeq` are -called variadic extractors, which enables variadic patterns. - -### Fixed-Arity Extractors - -Fixed-arity extractors expose the following signature: - -```Scala -def unapply[A](x: T)(implicit x: B): U -``` - -The type `U` conforms to one of the following matches: - -- Boolean match -- Product match - -Or `U` conforms to the type `R`: - -```Scala -type R = { - def isEmpty: Boolean - def get: S -} -``` - -and `S` conforms to one of the following matches: - -- single match -- name-based match - -The former form of `unapply` has higher precedence, and _single match_ has higher -precedence over _name-based match_. - -A usage of a fixed-arity extractor is irrefutable if one of the following condition holds: - -- `U = true` -- the extractor is used as a product match -- `U = Some[T]` (for Scala 2 compatibility) -- `U <: R` and `U <: { def isEmpty: false }` - -### Variadic Extractors - -Variadic extractors expose the following signature: - -```Scala -def unapplySeq[A](x: T)(implicit x: B): U -``` - -The type `U` conforms to one of the following matches: - -- sequence match -- product-sequence match - -Or `U` conforms to the type `R`: - -```Scala -type R = { - def isEmpty: Boolean - def get: S -} -``` - -and `S` conforms to one of the two matches above. - -The former form of `unapplySeq` has higher priority, and _sequence match_ has higher -precedence over _product-sequence match_. - -A usage of a variadic extractor is irrefutable if one of the following conditions holds: - -- the extractor is used directly as a sequence match or product-sequence match -- `U = Some[T]` (for Scala 2 compatibility) -- `U <: R` and `U <: { def isEmpty: false }` - -## Boolean Match - -- `U =:= Boolean` -- Pattern-matching on exactly `0` patterns - -For example: - - - -```scala -object Even: - def unapply(s: String): Boolean = s.size % 2 == 0 - -"even" match - case s @ Even() => println(s"$s has an even number of characters") - case s => println(s"$s has an odd number of characters") - -// even has an even number of characters -``` - -## Product Match - -- `U <: Product` -- `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` -- Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` - -For example: - - - -```scala -class FirstChars(s: String) extends Product: - def _1 = s.charAt(0) - def _2 = s.charAt(1) - - // Not used by pattern matching: Product is only used as a marker trait. - def canEqual(that: Any): Boolean = ??? - def productArity: Int = ??? - def productElement(n: Int): Any = ??? - -object FirstChars: - def unapply(s: String): FirstChars = new FirstChars(s) - -"Hi!" match - case FirstChars(char1, char2) => - println(s"First: $char1; Second: $char2") - -// First: H; Second: i -``` - -## Single Match - -- If there is exactly `1` pattern, pattern-matching on `1` pattern with type `U` - - - -```scala -class Nat(val x: Int): - def get: Int = x - def isEmpty = x < 0 - -object Nat: - def unapply(x: Int): Nat = new Nat(x) - -5 match - case Nat(n) => println(s"$n is a natural number") - case _ => () - -// 5 is a natural number -``` - -## Name-based Match - -- `N > 1` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1 ... _N: PN` members in `U` -- Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` - -```Scala -object ProdEmpty: - def _1: Int = ??? - def _2: String = ??? - def isEmpty = true - def unapply(s: String): this.type = this - def get = this - -"" match - case ProdEmpty(_, _) => ??? - case _ => () -``` - - -## Sequence Match - -- `U <: X`, `T2` and `T3` conform to `T1` - -```Scala -type X = { - def lengthCompare(len: Int): Int // or, `def length: Int` - def apply(i: Int): T1 - def drop(n: Int): scala.Seq[T2] - def toSeq: scala.Seq[T3] -} -``` - -- Pattern-matching on _exactly_ `N` simple patterns with types `T1, T1, ..., T1`, where `N` is the runtime size of the sequence, or -- Pattern-matching on `>= N` simple patterns and _a vararg pattern_ (e.g., `xs: _*`) with types `T1, T1, ..., T1, Seq[T1]`, where `N` is the minimum size of the sequence. - - - -```scala -object CharList: - def unapplySeq(s: String): Option[Seq[Char]] = Some(s.toList) - -"example" match - case CharList(c1, c2, c3, c4, _, _, _) => - println(s"$c1,$c2,$c3,$c4") - case _ => - println("Expected *exactly* 7 characters!") - -// e,x,a,m -``` - -## Product-Sequence Match - -- `U <: Product` -- `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` -- `PN` conforms to the signature `X` defined in Seq Pattern -- Pattern-matching on exactly `>= N` patterns, the first `N - 1` patterns have types `P1, P2, ... P(N-1)`, - the type of the remaining patterns are determined as in Seq Pattern. - -```Scala -class Foo(val name: String, val children: Int *) -object Foo: - def unapplySeq(f: Foo): Option[(String, Seq[Int])] = - Some((f.name, f.children)) - -def foo(f: Foo) = f match - case Foo(name, ns : _*) => - case Foo(name, x, y, ns : _*) => -``` - -There are plans for further simplification, in particular to factor out *product -match* and *name-based match* into a single type of extractor. - -## Type testing - -Abstract type testing with `ClassTag` is replaced with `TypeTest` or the alias `Typeable`. - -- pattern `_: X` for an abstract type requires a `TypeTest` in scope -- pattern `x @ X()` for an unapply that takes an abstract type requires a `TypeTest` in scope - -[More details on `TypeTest`](../other-new-features/type-test.html) diff --git a/_scala3-reference/changed-features/structural-types-spec.md b/_scala3-reference/changed-features/structural-types-spec.md deleted file mode 100644 index eb7e653a78..0000000000 --- a/_scala3-reference/changed-features/structural-types-spec.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Programmatic Structural Types - More Details" ---- - -## Syntax - -``` -SimpleType ::= ... | Refinement -Refinement ::= ‘{’ RefineStatSeq ‘}’ -RefineStatSeq ::= RefineStat {semi RefineStat} -RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl -``` - -## Implementation of Structural Types - -The standard library defines a universal marker trait -[`scala.Selectable`](https://github.com/lampepfl/dotty/blob/master/library/src/scala/Selectable.scala): - -```scala -trait Selectable extends Any -``` - -An implementation of `Selectable` that relies on [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html) is -available in the standard library: `scala.reflect.Selectable`. Other -implementations can be envisioned for platforms where Java reflection -is not available. - -Implementations of `Selectable` have to make available one or both of -the methods `selectDynamic` and `applyDynamic`. The methods could be members of the `Selectable` implementation or they could be extension methods. - -The `selectDynamic` method takes a field name and returns the value associated with that name in the `Selectable`. -It should have a signature of the form: - -```scala -def selectDynamic(name: String): T -``` - -Often, the return type `T` is `Any`. - -Unlike `scala.Dynamic`, there is no special meaning for an `updateDynamic` method. -However, we reserve the right to give it meaning in the future. -Consequently, it is recommended not to define any member called `updateDynamic` in `Selectable`s. - -The `applyDynamic` method is used for selections that are applied to arguments. It takes a method name and possibly `Class`es representing its parameters types as well as the arguments to pass to the function. -Its signature should be of one of the two following forms: - -```scala -def applyDynamic(name: String)(args: Any*): T -def applyDynamic(name: String, ctags: Class[?]*)(args: Any*): T -``` - -Both versions are passed the actual arguments in the `args` parameter. The second version takes in addition a vararg argument of `java.lang.Class`es that identify the method's parameter classes. Such an argument is needed -if `applyDynamic` is implemented using Java reflection, but it could be -useful in other cases as well. `selectDynamic` and `applyDynamic` can also take additional context parameters in using clauses. These are resolved in the normal way at the callsite. - -Given a value `v` of type `C { Rs }`, where `C` is a class reference -and `Rs` are structural refinement declarations, and given `v.a` of type `U`, we consider three distinct cases: - -- If `U` is a value type, we map `v.a` to: - ```scala - v.selectDynamic("a").asInstanceOf[U] - ``` - -- If `U` is a method type `(T11, ..., T1n)...(TN1, ..., TNn): R` and it is not a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, ..., aNn)` to: - ```scala - v.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn) - .asInstanceOf[R] - ``` - If this call resolves to an `applyDynamic` method of the second form that takes a `Class[?]*` argument, we further rewrite this call to - ```scala - v.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn)( - a11, ..., a1n, ..., aN1, ..., aNn) - .asInstanceOf[R] - ``` - where each `c_ij` is the literal `java.lang.Class[?]` of the type of the formal parameter `Tij`, i.e., `classOf[Tij]`. - -- If `U` is neither a value nor a method type, or a dependent method - type, an error is emitted. - -Note that `v`'s static type does not necessarily have to conform to `Selectable`, nor does it need to have `selectDynamic` and `applyDynamic` as members. It suffices that there is an implicit -conversion that can turn `v` into a `Selectable`, and the selection methods could also be available as -[extension methods](../contextual/extension-methods.html). - -## Limitations of Structural Types - -- Dependent methods cannot be called via structural call. - -- Refinements may not introduce overloads: If a refinement specifies the signature - of a method `m`, and `m` is also defined in the parent type of the refinement, then - the new signature must properly override the existing one. - -- Subtyping of structural refinements must preserve erased parameter types: Assume - we want to prove `S <: T { def m(x: A): B }`. Then, as usual, `S` must have a member method `m` that can take an argument of type `A`. Furthermore, if `m` is not a member of `T` (i.e. the refinement is structural), an additional condition applies. In this case, the member _definition_ `m` of `S` will have a parameter - with type `A'` say. The additional condition is that the erasure of `A'` and `A` is the same. Here is an example: - - ```scala - class Sink[A] { def put(x: A): Unit = {} } - val a = Sink[String]() - val b: { def put(x: String): Unit } = a // error - b.put("abc") // looks for a method with a `String` parameter - ``` - The second to last line is not well-typed, - since the erasure of the parameter type of `put` in class `Sink` is `Object`, - but the erasure of `put`'s parameter in the type of `b` is `String`. - This additional condition is necessary, since we will have to resort - to some (as yet unknown) form of reflection to call a structural member - like `put` in the type of `b` above. The condition ensures that the statically - known parameter types of the refinement correspond up to erasure to the - parameter types of the selected call target at runtime. - - Most reflection dispatch algorithms need to know exact erased parameter types. For instance, if the example above would typecheck, the call - `b.put("abc")` on the last line would look for a method `put` in the runtime type of `b` that takes a `String` parameter. But the `put` method is the one from class `Sink`, which takes an `Object` parameter. Hence the call would fail at runtime with a `NoSuchMethodException`. - - One might hope for a "more intelligent" reflexive dispatch algorithm that does not require exact parameter type matching. Unfortunately, this can always run into ambiguities, as long as overloading is a possibility. For instance, continuing the example above, we might introduce a new subclass `Sink1` of `Sink` and change the definition of `a` as follows: - - ```scala - class Sink1[A] extends Sink[A] { def put(x: "123") = ??? } - val a: Sink[String] = Sink1[String]() - ``` - - Now there are two `put` methods in the runtime type of `b` with erased parameter - types `Object` and `String`, respectively. Yet dynamic dispatch still needs to go - to the first `put` method, even though the second looks like a better match. - - For the cases where we can in fact implement reflection without knowing precise parameter types (for instance if static overloading is replaced by dynamically dispatched multi-methods), there is an escape hatch. For types that extend `scala.Selectable.WithoutPreciseParameterTypes` the signature check is omitted. Example: - - ```scala - trait MultiMethodSelectable extends Selectable.WithoutPreciseParameterTypes: - // Assume this version of `applyDynamic` can be implemented without knowing - // precise parameter types `paramTypes`: - def applyDynamic(name: String, paramTypes: Class[_]*)(args: Any*): Any = ??? - - class Sink[A] extends MultiMethodSelectable: - def put(x: A): Unit = {} - - val a = new Sink[String] - val b: MultiMethodSelectable { def put(x: String): Unit } = a // OK - ``` -## Differences with Scala 2 Structural Types - -- Scala 2 supports structural types by means of Java reflection. Unlike - Scala 3, structural calls do not rely on a mechanism such as - `Selectable`, and reflection cannot be avoided. -- In Scala 2, refinements can introduce overloads. -- In Scala 2, mutable `var`s are allowed in refinements. In Scala 3, - they are no longer allowed. -- Scala 2 does not impose the "same-erasure" restriction on subtyping of structural types. It allows some calls to fail at runtime instead. - -## Context - -For more information, see [Rethink Structural Types](https://github.com/lampepfl/dotty/issues/1886). diff --git a/_scala3-reference/changed-features/structural-types.md b/_scala3-reference/changed-features/structural-types.md deleted file mode 100644 index fe20a3aa54..0000000000 --- a/_scala3-reference/changed-features/structural-types.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -title: "Programmatic Structural Types" -type: section -num: 53 -previous-page: /scala3/reference/changed-features/numeric-literals -next-page: /scala3/reference/changed-features/operators ---- - -## Motivation - -Some usecases, such as modelling database access, are more awkward in -statically typed languages than in dynamically typed languages: With -dynamically typed languages, it's quite natural to model a row as a -record or object, and to select entries with simple dot notation (e.g. -`row.columnName`). - -Achieving the same experience in statically typed -language requires defining a class for every possible row arising from -database manipulation (including rows arising from joins and -projections) and setting up a scheme to map between a row and the -class representing it. - -This requires a large amount of boilerplate, which leads developers to -trade the advantages of static typing for simpler schemes where colum -names are represented as strings and passed to other operators (e.g. -`row.select("columnName")`). This approach forgoes the advantages of -static typing, and is still not as natural as the dynamically typed -version. - -Structural types help in situations where we would like to support -simple dot notation in dynamic contexts without losing the advantages -of static typing. They allow developers to use dot notation and -configure how fields and methods should be resolved. - -## Example - -Here's an example of a structural type `Person`: - -```scala - class Record(elems: (String, Any)*) extends Selectable: - private val fields = elems.toMap - def selectDynamic(name: String): Any = fields(name) - - type Person = Record { val name: String; val age: Int } - ``` - -The type `Person` adds a _refinement_ to its parent type `Record` that defines the two fields `name` and `age`. We say the refinement is _structural_ since `name` and `age` are not defined in the parent type. But they exist nevertheless as members of class `Person`. For instance, the following -program would print "Emma is 42 years old.": - -```scala - val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person] - println(s"${person.name} is ${person.age} years old.") -``` - -The parent type `Record` in this example is a generic class that can represent arbitrary records in its `elems` argument. This argument is a -sequence of pairs of labels of type `String` and values of type `Any`. -When we create a `Person` as a `Record` we have to assert with a typecast -that the record defines the right fields of the right types. `Record` -itself is too weakly typed so the compiler cannot know this without -help from the user. In practice, the connection between a structural type -and its underlying generic representation would most likely be done by -a database layer, and therefore would not be a concern of the end user. - -`Record` extends the marker trait `scala.Selectable` and defines -a method `selectDynamic`, which maps a field name to its value. -Selecting a structural type member is done by calling this method. -The `person.name` and `person.age` selections are translated by -the Scala compiler to: - -```scala - person.selectDynamic("name").asInstanceOf[String] - person.selectDynamic("age").asInstanceOf[Int] -``` - -Besides `selectDynamic`, a `Selectable` class sometimes also defines a method `applyDynamic`. This can then be used to translate function calls of structural members. So, if `a` is an instance of `Selectable`, a structural call like `a.f(b, c)` would translate to - -```scala - a.applyDynamic("f")(b, c) -``` - -## Using Java Reflection - -Structural types can also be accessed using [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html). Example: - -```scala - type Closeable = { def close(): Unit } - - class FileInputStream: - def close(): Unit - - class Channel: - def close(): Unit -``` - -Here, we define a structural type `Closeable` that defines a `close` method. There are various classes that have `close` methods, we just list `FileInputStream` and `Channel` as two examples. It would be easiest if the two classes shared a common interface that factors out the `close` method. But such factorings are often not possible if different libraries are combined in one application. Yet, we can still have methods that work on -all classes with a `close` method by using the `Closeable` type. For instance, - -```scala - import scala.reflect.Selectable.reflectiveSelectable - - def autoClose(f: Closeable)(op: Closeable => Unit): Unit = - try op(f) finally f.close() -``` - -The call `f.close()` has to use Java reflection to identify and call the `close` method in the receiver `f`. This needs to be enabled by an import -of `reflectiveSelectable` shown above. What happens "under the hood" is then the following: - - - The import makes available an implicit conversion that turns any type into a - `Selectable`. `f` is wrapped in this conversion. - - - The compiler then transforms the `close` call on the wrapped `f` - to an `applyDynamic` call. The end result is: - - ```scala - reflectiveSelectable(f).applyDynamic("close")() - ``` - - The implementation of `applyDynamic` in `reflectiveSelectable`'s result -uses Java reflection to find and call a method `close` with zero parameters in the value referenced by `f` at runtime. - -Structural calls like this tend to be much slower than normal method calls. The mandatory import of `reflectiveSelectable` serves as a signpost that something inefficient is going on. - -**Note:** In Scala 2, Java reflection is the only mechanism available for structural types and it is automatically enabled without needing the -`reflectiveSelectable` conversion. However, to warn against inefficient -dispatch, Scala 2 requires a language import `import scala.language.reflectiveCalls`. - -Before resorting to structural calls with Java reflection one should consider alternatives. For instance, sometimes a more a modular _and_ efficient architecture can be obtained using type classes. - -## Extensibility - -New instances of `Selectable` can be defined to support means of -access other than Java reflection, which would enable usages such as -the database access example given at the beginning of this document. - -## Local Selectable Instances - -Local and anonymous classes that extend `Selectable` get more refined types -than other classes. Here is an example: - -```scala -trait Vehicle extends reflect.Selectable: - val wheels: Int - -val i3 = new Vehicle: // i3: Vehicle { val range: Int } - val wheels = 4 - val range = 240 - -i3.range -``` - -The type of `i3` in this example is `Vehicle { val range: Int }`. Hence, -`i3.range` is well-formed. Since the base class `Vehicle` does not define a `range` field or method, we need structural dispatch to access the `range` field of the anonymous class that initializes `id3`. Structural dispatch -is implemented by the base trait `reflect.Selectable` of `Vehicle`, which -defines the necessary `selectDynamic` member. - -`Vehicle` could also extend some other subclass of `scala.Selectable` that implements `selectDynamic` and `applyDynamic` differently. But if it does not extend a `Selectable` at all, the code would no longer typecheck: - -```scala -trait Vehicle: - val wheels: Int - -val i3 = new Vehicle: // i3: Vehicle - val wheels = 4 - val range = 240 - -i3.range // error: range is not a member of `Vehicle` -``` - -The difference is that the type of an anonymous class that does not extend `Selectable` is just formed from the parent type(s) of the class, without -adding any refinements. Hence, `i3` now has just type `Vehicle` and the selection `i3.range` gives a "member not found" error. - -Note that in Scala 2 all local and anonymous classes could produce values with refined types. But -members defined by such refinements could be selected only with the language import -`reflectiveCalls`. - -## Relation with `scala.Dynamic` - -There are clearly some connections with `scala.Dynamic` here, since -both select members programmatically. But there are also some -differences. - -- Fully dynamic selection is not typesafe, but structural selection - is, as long as the correspondence of the structural type with the - underlying value is as stated. - -- `Dynamic` is just a marker trait, which gives more leeway where and - how to define reflective access operations. By contrast - `Selectable` is a trait which declares the access operations. - -- Two access operations, `selectDynamic` and `applyDynamic` are shared - between both approaches. In `Selectable`, `applyDynamic` also may also take - `java.lang.Class` arguments indicating the method's formal parameter types. - `Dynamic` comes with `updateDynamic`. - -[More details](structural-types-spec.html) diff --git a/_scala3-reference/changed-features/type-checking.md b/_scala3-reference/changed-features/type-checking.md deleted file mode 100644 index 81ca345a6c..0000000000 --- a/_scala3-reference/changed-features/type-checking.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Changes in Type Checking" -type: section -num: 57 -previous-page: /scala3/reference/changed-features/imports -next-page: /scala3/reference/changed-features/type-inference ---- - -*** **TO BE FILLED IN** *** diff --git a/_scala3-reference/changed-features/type-inference.md b/_scala3-reference/changed-features/type-inference.md deleted file mode 100644 index 873c918312..0000000000 --- a/_scala3-reference/changed-features/type-inference.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Changes in Type Inference" -type: section -num: 58 -previous-page: /scala3/reference/changed-features/type-checking -next-page: /scala3/reference/changed-features/implicit-resolution ---- - -For more information, see the two presentations - -* [Scala 3, Type inference and You!](https://www.youtube.com/watch?v=lMvOykNQ4zs) by Guillaume Martres (September 2019) -* [GADTs in Dotty](https://www.youtube.com/watch?v=VV9lPg3fNl8) by Aleksander Boruch-Gruszecki (July 2019). diff --git a/_scala3-reference/changed-features/vararg-splices.md b/_scala3-reference/changed-features/vararg-splices.md deleted file mode 100644 index efa7f034ac..0000000000 --- a/_scala3-reference/changed-features/vararg-splices.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: "Vararg Splices" -type: section -num: 63 -previous-page: /scala3/reference/changed-features/match-syntax -next-page: /scala3/reference/changed-features/pattern-bindings ---- - -The syntax of vararg splices in patterns and function arguments has changed. The new syntax uses a postfix `*`, analogously to how a vararg parameter is declared. - -```scala -val arr = Array(0, 1, 2, 3) -val lst = List(arr*) // vararg splice argument -lst match - case List(0, 1, xs*) => println(xs) // binds xs to Seq(2, 3) - case List(1, _*) => // wildcard pattern -``` - -The old syntax for splice arguments will be phased out. - -```scala -/*!*/ val lst = List(arr: _*) // syntax error - lst match - case List(0, 1, xs @ _*) // ok, equivalent to `xs*` -``` - -## Syntax - -``` -ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ - | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’ - -ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ -``` - -## Compatibility considerations - -To enable cross compilation between Scala 2 and Scala 3, the compiler will -accept both the old and the new syntax. Under the `-source future` setting, an error -will be emitted when the old syntax is encountered. An automatic rewrite from old -to new syntax is offered under `-source future-migration`. diff --git a/_scala3-reference/changed-features/wildcards.md b/_scala3-reference/changed-features/wildcards.md deleted file mode 100644 index fdf69fdcd9..0000000000 --- a/_scala3-reference/changed-features/wildcards.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Wildcard Arguments in Types -type: section -num: 55 -previous-page: /scala3/reference/changed-features/operators -next-page: /scala3/reference/changed-features/imports ---- - -The syntax of wildcard arguments in types has changed from `_` to `?`. Example: -```scala -List[?] -Map[? <: AnyRef, ? >: Null] -``` - -### Motivation - -We would like to use the underscore syntax `_` to stand for an anonymous type parameter, aligning it with its meaning in -value parameter lists. So, just as `f(_)` is a shorthand for the lambda `x => f(x)`, in the future `C[_]` will be a shorthand -for the type lambda `[X] =>> C[X]`. This makes higher-kinded types easier to use. It also removes the wart that, used as a type -parameter, `F[_]` means `F` is a type constructor whereas used as a type, `F[_]` means it is a wildcard (i.e. existential) type. -In the future, `F[_]` will mean the same thing, no matter where it is used. - -We pick `?` as a replacement syntax for wildcard types, since it aligns with -[Java's syntax](https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html). - -### Migration Strategy - -The migration to the new scheme is complicated, in particular since the [kind projector](https://github.com/typelevel/kind-projector) -compiler plugin still uses the reverse convention, with `?` meaning parameter placeholder instead of wildcard. Fortunately, kind projector has added `*` as an alternative syntax for `?`. - -A step-by-step migration is made possible with the following measures: - - 1. In Scala 3.0, both `_` and `?` are legal names for wildcards. - 2. In Scala 3.1, `_` is deprecated in favor of `?` as a name for a wildcard. A `-rewrite` option is - available to rewrite one to the other. - 3. In Scala 3.2, the meaning of `_` changes from wildcard to placeholder for type parameter. - 4. The Scala 3.1 behavior is already available today under the `-source future` setting. - -To smooth the transition for codebases that use kind-projector, we adopt the following measures under the command line -option `-Ykind-projector`: - - 1. In Scala 3.0, `*` is available as a type parameter placeholder. - 2. In Scala 3.2, `*` is deprecated in favor of `_`. A `-rewrite` option is - available to rewrite one to the other. - 3. In Scala 3.3, `*` is removed again, and all type parameter placeholders will be expressed with `_`. - -These rules make it possible to cross build between Scala 2 using the kind projector plugin and Scala 3.0 - 3.2 using the compiler option `-Ykind-projector`. - -There is also a migration path for users that want a one-time transition to syntax with `_` as a type parameter placeholder. -With option `-Ykind-projector:underscores` Scala 3 will regard `_` as a type parameter placeholder, leaving `?` as the only syntax for wildcards. - -To cross-compile with old Scala 2 sources, while using `_` a placeholder, you must use options `-Xsource:3 -P:kind-projector:underscore-placeholders` together with a recent version of kind-projector (`0.13` and higher) and most recent versions of Scala 2 (`2.13.5` and higher and `2.12.14` and higher) diff --git a/_scala3-reference/contextual.md b/_scala3-reference/contextual.md deleted file mode 100644 index 1dfc5e6baa..0000000000 --- a/_scala3-reference/contextual.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: "Contextual Abstractions" -type: chapter -num: 13 -previous-page: /scala3/reference/enums/desugarEnums -next-page: /scala3/reference/contextual/givens ---- - -### Critique of the Status Quo - -Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a unified paradigm with a great variety of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them. - -Following Haskell, Scala was the second popular language to have some form of implicits. Other languages have followed suit. E.g [Rust's traits](https://doc.rust-lang.org/rust-by-example/trait.html) or [Swift's protocol extensions](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID521). Design proposals are also on the table for Kotlin as [compile time dependency resolution](https://github.com/Kotlin/KEEP/blob/e863b25f8b3f2e9b9aaac361c6ee52be31453ee0/proposals/compile-time-dependency-resolution.md), for C# as [Shapes and Extensions](https://github.com/dotnet/csharplang/issues/164) -or for F# as [Traits](https://github.com/MattWindsor91/visualfsharp/blob/hackathon-vs/examples/fsconcepts.md). Implicits are also a common feature of theorem provers such as [Coq](https://coq.inria.fr/refman/language/extensions/implicit-arguments.html) or [Agda](https://agda.readthedocs.io/en/latest/language/implicit-arguments.html). - -Even though these designs use widely different terminology, they are all variants of the core idea of _term inference_. Given a type, the compiler synthesizes a "canonical" term that has that type. Scala embodies the idea in a purer form than most other languages: An implicit parameter directly leads to an inferred argument term that could also be written down explicitly. By contrast, type class based designs are less direct since they hide term inference behind some form of type classification and do not offer the option of writing the inferred quantities (typically, dictionaries) explicitly. - -Given that term inference is where the industry is heading, and given that Scala has it in a very pure form, how come implicits are not more popular? In fact, it's fair to say that implicits are at the same time Scala's most distinguished and most controversial feature. I believe this is due to a number of aspects that together make implicits harder to learn than necessary and also make it harder to prevent abuses. - -Particular criticisms are: - -1. Being very powerful, implicits are easily over-used and mis-used. This observation holds in almost all cases when we talk about _implicit conversions_, which, even though conceptually different, share the same syntax with other implicit definitions. For instance, regarding the two definitions - - ```scala - implicit def i1(implicit x: T): C[T] = ... - implicit def i2(x: T): C[T] = ... - ``` - - the first of these is a conditional implicit _value_, the second an implicit _conversion_. Conditional implicit values are a cornerstone for expressing type classes, whereas most applications of implicit conversions have turned out to be of dubious value. The problem is that many newcomers to the language start with defining implicit conversions since they are easy to understand and seem powerful and convenient. Scala 3 will put under a language flag both definitions and applications of "undisciplined" implicit conversions between types defined elsewhere. This is a useful step to push back against overuse of implicit conversions. But the problem remains that syntactically, conversions and values just look too similar for comfort. - -2. Another widespread abuse is over-reliance on implicit imports. This often leads to inscrutable type errors that go away with the right import incantation, leaving a feeling of frustration. Conversely, it is hard to see what implicits a program uses since implicits can hide anywhere in a long list of imports. - -3. The syntax of implicit definitions is too minimal. It consists of a single modifier, `implicit`, that can be attached to a large number of language constructs. A problem with this for newcomers is that it conveys mechanism instead of intent. For instance, a type class instance is an implicit object or val if unconditional and an implicit def with implicit parameters referring to some class if conditional. This describes precisely what the implicit definitions translate to -- just drop the `implicit` modifier, and that's it! But the cues that define intent are rather indirect and can be easily misread, as demonstrated by the definitions of `i1` and `i2` above. - -4. The syntax of implicit parameters also has shortcomings. While implicit _parameters_ are designated specifically, arguments are not. Passing an argument to an implicit parameter looks like a regular application `f(arg)`. This is problematic because it means there can be confusion regarding what parameter gets instantiated in a call. For instance, in - - ```scala - def currentMap(implicit ctx: Context): Map[String, Int] - ``` - - one cannot write `currentMap("abc")` since the string `"abc"` is taken as explicit argument to the implicit `ctx` parameter. One has to write `currentMap.apply("abc")` instead, which is awkward and irregular. For the same reason, a method definition can only have one implicit parameter section and it must always come last. This restriction not only reduces orthogonality, but also prevents some useful program constructs, such as a method with a regular parameter whose type depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in many cases that name is never referenced. - -5. Implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but tools like [Scaladoc](https://docs.scala-lang.org/overviews/scaladoc/overview.html) that are based on static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deeply recursive implicit search has failed. Note that the Scala 3 compiler has already made a lot of progress in the error diagnostics area. If a recursive search fails some levels down, it shows what was constructed and what is missing. Also, it suggests imports that can bring missing implicits in scope. - -None of the shortcomings is fatal, after all implicits are very widely used, and many libraries and applications rely on them. But together, they make code using implicits a lot more cumbersome and less clear than it could be. - -Historically, many of these shortcomings come from the way implicits were gradually "discovered" in Scala. Scala originally had only implicit conversions with the intended use case of "extending" a class or trait after it was defined, i.e. what is expressed by implicit classes in later versions of Scala. Implicit parameters and instance definitions came later in 2006 and we picked similar syntax since it seemed convenient. For the same reason, no effort was made to distinguish implicit imports or arguments from normal ones. - -Existing Scala programmers by and large have gotten used to the status quo and see little need for change. But for newcomers this status quo presents a big hurdle. I believe if we want to overcome that hurdle, we should take a step back and allow ourselves to consider a radically new design. - -### The New Design - -The following pages introduce a redesign of contextual abstractions in Scala. They introduce four fundamental changes: - -1. [Given Instances](./contextual/givens.html) are a new way to define basic terms that can be synthesized. They replace implicit definitions. The core principle of the proposal is that, rather than mixing the `implicit` modifier with a large number of features, we have a single way to define terms that can be synthesized for types. - -2. [Using Clauses](./contextual/using-clauses.html) are a new syntax for implicit _parameters_ and their _arguments_. It unambiguously aligns parameters and arguments, solving a number of language warts. It also allows us to have several `using` clauses in a definition. - -3. ["Given" Imports](./contextual/given-imports.html) are a new class of import selectors that specifically import - givens and nothing else. - -4. [Implicit Conversions](./contextual/conversions.html) are now expressed as given instances of a standard `Conversion` class. All other forms of implicit conversions will be phased out. - -This section also contains pages describing other language features that are related to context abstraction. These are: - -- [Context Bounds](./contextual/context-bounds.html), which carry over unchanged. -- [Extension Methods](./contextual/extension-methods.html) replace implicit classes in a way that integrates better with type classes. -- [Implementing Type Classes](./contextual/type-classes.html) demonstrates how some common type classes can be implemented using the new constructs. -- [Type Class Derivation](./contextual/derivation.html) introduces constructs to automatically derive type class instances for ADTs. -- [Multiversal Equality](./contextual/multiversal-equality.html) introduces a special type class to support type safe equality. -- [Context Functions](./contextual/context-functions.html) provide a way to abstract over context parameters. -- [By-Name Context Parameters](./contextual/by-name-context-parameters.html) are an essential tool to define recursive synthesized values without looping. -- [Relationship with Scala 2 Implicits](./contextual/relationship-implicits.html) discusses the relationship between old-style implicits and new-style givens and how to migrate from one to the other. - -Overall, the new design achieves a better separation of term inference from the rest of the language: There is a single way to define givens instead of a multitude of forms all taking an `implicit` modifier. There is a single way to introduce implicit parameters and arguments instead of conflating implicit with normal arguments. There is a separate way to import givens that does not allow them to hide in a sea of normal imports. And there is a single way to define an implicit conversion which is clearly marked as such and does not require special syntax. - -This design thus avoids feature interactions and makes the language more consistent and orthogonal. It will make implicits easier to learn and harder to abuse. It will greatly improve the clarity of the 95% of Scala programs that use implicits. It has thus the potential to fulfil the promise of term inference in a principled way that is also accessible and friendly. - -Could we achieve the same goals by tweaking existing implicits? After having tried for a long time, I believe now that this is impossible. - -- First, some of the problems are clearly syntactic and require different syntax to solve them. -- Second, there is the problem how to migrate. We cannot change the rules in mid-flight. At some stage of language evolution we need to accommodate both the new and the old rules. With a syntax change, this is easy: Introduce the new syntax with new rules, support the old syntax for a while to facilitate cross compilation, deprecate and phase out the old syntax at some later time. Keeping the same syntax does not offer this path, and in fact does not seem to offer any viable path for evolution -- Third, even if we would somehow succeed with migration, we still have the problem - how to teach this. We cannot make existing tutorials go away. Almost all existing tutorials start with implicit conversions, which will go away; they use normal imports, which will go away, and they explain calls to methods with implicit parameters by expanding them to plain applications, which will also go away. This means that we'd have - to add modifications and qualifications to all existing literature and courseware, likely causing more confusion with beginners instead of less. By contrast, with a new syntax there is a clear criterion: Any book or courseware that mentions `implicit` is outdated and should be updated. diff --git a/_scala3-reference/contextual/by-name-context-parameters.md b/_scala3-reference/contextual/by-name-context-parameters.md deleted file mode 100644 index c1777b6259..0000000000 --- a/_scala3-reference/contextual/by-name-context-parameters.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "By-Name Context Parameters" -type: section -num: 25 -previous-page: /scala3/reference/contextual/conversions -next-page: /scala3/reference/contextual/relationship-implicits ---- - -Context parameters can be declared by-name to avoid a divergent inferred expansion. Example: - -```scala -trait Codec[T]: - def write(x: T): Unit - -given intCodec: Codec[Int] = ??? - -given optionCodec[T](using ev: => Codec[T]): Codec[Option[T]] with - def write(xo: Option[T]) = xo match - case Some(x) => ev.write(x) - case None => - -val s = summon[Codec[Option[Int]]] - -s.write(Some(33)) -s.write(None) -``` -As is the case for a normal by-name parameter, the argument for the context parameter `ev` -is evaluated on demand. In the example above, if the option value `x` is `None`, it is -not evaluated at all. - -The synthesized argument for a context parameter is backed by a local val -if this is necessary to prevent an otherwise diverging expansion. - -The precise steps for synthesizing an argument for a by-name context parameter of type `=> T` are as follows. - - 1. Create a new given of type `T`: - - ```scala - given lv: T = ??? - ``` - - where `lv` is an arbitrary fresh name. - - 1. This given is not immediately available as candidate for argument inference (making it immediately available could result in a loop in the synthesized computation). But it becomes available in all nested contexts that look again for an argument to a by-name context parameter. - - 1. If this search succeeds with expression `E`, and `E` contains references to `lv`, replace `E` by - - ```scala - { given lv: T = E; lv } - ``` - - Otherwise, return `E` unchanged. - -In the example above, the definition of `s` would be expanded as follows. - -```scala -val s = summon[Test.Codec[Option[Int]]]( - optionCodec[Int](using intCodec) -) -``` - -No local given instance was generated because the synthesized argument is not recursive. - -### Reference - -For more information, see [Issue #1998](https://github.com/lampepfl/dotty/issues/1998) -and the associated [Scala SIP](https://docs.scala-lang.org/sips/byname-implicits.html). diff --git a/_scala3-reference/contextual/context-bounds.md b/_scala3-reference/contextual/context-bounds.md deleted file mode 100644 index 5c7ea5cd59..0000000000 --- a/_scala3-reference/contextual/context-bounds.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Context Bounds" -type: section -num: 17 -previous-page: /scala3/reference/contextual/using-clauses -next-page: /scala3/reference/contextual/given-imports ---- - -A context bound is a shorthand for expressing the common pattern of a context parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this: - -```scala -def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) -``` - -A bound like `: Ord` on a type parameter `T` of a method or class indicates a context parameter `using Ord[T]`. The context parameter(s) generated from context bounds come last in the definition of the containing method or class. For instance, - -```scala -def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R -``` - -would expand to - -```scala -def f[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]): R -``` - -Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g. - -```scala -def g[T <: B : C](x: T): R = ... -``` - -## Migration - -To ease migration, context bounds in Dotty map in Scala 3.0 to old-style implicit parameters -for which arguments can be passed either with a `(using ...)` clause or with a normal application. From Scala 3.1 on, they will map to context parameters instead, as is described above. - -If the source version is `future-migration`, any pairing of an evidence -context parameter stemming from a context bound with a normal argument will give a migration -warning. The warning indicates that a `(using ...)` clause is needed instead. The rewrite can be -done automatically under `-rewrite`. - -## Syntax - -``` -TypeParamBounds ::= [SubtypeBounds] {ContextBound} -ContextBound ::= ‘:’ Type -``` diff --git a/_scala3-reference/contextual/context-functions-spec.md b/_scala3-reference/contextual/context-functions-spec.md deleted file mode 100644 index 9267eea23f..0000000000 --- a/_scala3-reference/contextual/context-functions-spec.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Context Functions - More Details" ---- - -## Syntax - -``` -Type ::= ... - | FunArgTypes ‘?=>’ Type -Expr ::= ... - | FunParams ‘?=>’ Expr -``` - -Context function types associate to the right, e.g. -`S ?=> T ?=> U` is the same as `S ?=> (T ?=> U)`. - -## Implementation - -Context function types are shorthands for class types that define `apply` -methods with context parameters. Specifically, the `N`-ary function type - -`T1, ..., TN ?=> R` is a shorthand for the class type -`ContextFunctionN[T1, ..., TN, R]`. Such class types are assumed to have the following definitions, for any value of `N >= 1`: - -```scala -package scala -trait ContextFunctionN[-T1, ..., -TN, +R]: - def apply(using x1: T1, ..., xN: TN): R -``` - -Context function types erase to normal function types, so these classes are -generated on the fly for typechecking, but not realized in actual code. - -Context function literals `(x1: T1, ..., xn: Tn) ?=> e` map -context parameters `xi` of types `Ti` to the result of evaluating the expression `e`. -The scope of each context parameter `xi` is `e`. The parameters must have pairwise distinct names. - -If the expected type of the context function literal is of the form -`scala.ContextFunctionN[S1, ..., Sn, R]`, the expected type of `e` is `R` and -the type `Ti` of any of the parameters `xi` can be omitted, in which case `Ti -= Si` is assumed. If the expected type of the context function literal is -some other type, all context parameter types must be explicitly given, and the expected type of `e` is undefined. -The type of the context function literal is `scala.ContextFunctionN[S1, ...,Sn, T]`, where `T` is the widened -type of `e`. `T` must be equivalent to a type which does not refer to any of -the context parameters `xi`. - -The context function literal is evaluated as the instance creation expression - -```scala -new scala.ContextFunctionN[T1, ..., Tn, T]: - def apply(using x1: T1, ..., xn: Tn): T = e -``` - -A context parameter may also be a wildcard represented by an underscore `_`. In that case, a fresh name for the parameter is chosen arbitrarily. - -**Note:** The closing paragraph of the -[Anonymous Functions section](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#anonymous-functions) -of Scala 2.13 is subsumed by context function types and should be removed. - -Context function literals `(x1: T1, ..., xn: Tn) ?=> e` are -automatically created for any expression `e` whose expected type is -`scala.ContextFunctionN[T1, ..., Tn, R]`, unless `e` is -itself a context function literal. This is analogous to the automatic -insertion of `scala.Function0` around expressions in by-name argument position. - -Context function types generalize to `N > 22` in the same way that function types do, see [the corresponding -documentation](../dropped-features/limit22.html). - -## Examples - -See the section on Expressiveness from [Simplicitly: foundations and -applications of implicit function -types](https://dl.acm.org/citation.cfm?id=3158130). - -### Type Checking - -After desugaring no additional typing rules are required for context function types. diff --git a/_scala3-reference/contextual/context-functions.md b/_scala3-reference/contextual/context-functions.md deleted file mode 100644 index 1b930b3c8d..0000000000 --- a/_scala3-reference/contextual/context-functions.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: "Context Functions" -type: section -num: 23 -previous-page: /scala3/reference/contextual/multiversal-equality -next-page: /scala3/reference/contextual/conversions ---- - -_Context functions_ are functions with (only) context parameters. -Their types are _context function types_. Here is an example of a context function type: - -```scala -type Executable[T] = ExecutionContext ?=> T -``` -Context functions are written using `?=>` as the "arrow" sign. -They are applied to synthesized arguments, in -the same way methods with context parameters are applied. For instance: -```scala - given ec: ExecutionContext = ... - - def f(x: Int): ExecutionContext ?=> Int = ... - - // could be written as follows with the type alias from above - // def f(x: Int): Executable[Int] = ... - - f(2)(using ec) // explicit argument - f(2) // argument is inferred -``` -Conversely, if the expected type of an expression `E` is a context function type -`(T_1, ..., T_n) ?=> U` and `E` is not already an -context function literal, `E` is converted to a context function literal by rewriting it to -```scala - (x_1: T1, ..., x_n: Tn) ?=> E -``` -where the names `x_1`, ..., `x_n` are arbitrary. This expansion is performed -before the expression `E` is typechecked, which means that `x_1`, ..., `x_n` -are available as givens in `E`. - -Like their types, context function literals are written using `?=>` as the arrow between parameters and results. They differ from normal function literals in that their types are context function types. - -For example, continuing with the previous definitions, -```scala - def g(arg: Executable[Int]) = ... - - g(22) // is expanded to g((ev: ExecutionContext) ?=> 22) - - g(f(2)) // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev)) - - g((ctx: ExecutionContext) ?=> f(3)) // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx)) - g((ctx: ExecutionContext) ?=> f(3)(using ctx)) // is left as it is -``` - -### Example: Builder Pattern - -Context function types have considerable expressive power. For -instance, here is how they can support the "builder pattern", where -the aim is to construct tables like this: -```scala - table { - row { - cell("top left") - cell("top right") - } - row { - cell("bottom left") - cell("bottom right") - } - } -``` -The idea is to define classes for `Table` and `Row` that allow the -addition of elements via `add`: -```scala - class Table: - val rows = new ArrayBuffer[Row] - def add(r: Row): Unit = rows += r - override def toString = rows.mkString("Table(", ", ", ")") - - class Row: - val cells = new ArrayBuffer[Cell] - def add(c: Cell): Unit = cells += c - override def toString = cells.mkString("Row(", ", ", ")") - - case class Cell(elem: String) -``` -Then, the `table`, `row` and `cell` constructor methods can be defined -with context function types as parameters to avoid the plumbing boilerplate -that would otherwise be necessary. -```scala - def table(init: Table ?=> Unit) = - given t: Table = Table() - init - t - - def row(init: Row ?=> Unit)(using t: Table) = - given r: Row = Row() - init - t.add(r) - - def cell(str: String)(using r: Row) = - r.add(new Cell(str)) -``` -With that setup, the table construction code above compiles and expands to: -```scala - table { ($t: Table) ?=> - - row { ($r: Row) ?=> - cell("top left")(using $r) - cell("top right")(using $r) - }(using $t) - - row { ($r: Row) ?=> - cell("bottom left")(using $r) - cell("bottom right")(using $r) - }(using $t) - } -``` -### Example: Postconditions - -As a larger example, here is a way to define constructs for checking arbitrary postconditions using an extension method `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque type aliases, context function types, and extension methods to provide a zero-overhead abstraction. - -```scala -object PostConditions: - opaque type WrappedResult[T] = T - - def result[T](using r: WrappedResult[T]): T = r - - extension [T](x: T) - def ensuring(condition: WrappedResult[T] ?=> Boolean): T = - assert(condition(using x)) - x -end PostConditions -import PostConditions.{ensuring, result} - -val s = List(1, 2, 3).sum.ensuring(result == 6) -``` -**Explanations**: We use a context function type `WrappedResult[T] ?=> Boolean` -as the type of the condition of `ensuring`. An argument to `ensuring` such as -`(result == 6)` will therefore have a given of type `WrappedResult[T]` in -scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure -that we do not get unwanted givens in scope (this is good practice in all cases -where context parameters are involved). Since `WrappedResult` is an opaque type alias, its -values need not be boxed, and since `ensuring` is added as an extension method, its argument -does not need boxing either. Hence, the implementation of `ensuring` is close in efficiency to the best possible code one could write by hand: - -```scala -val s = - val result = List(1, 2, 3).sum - assert(result == 6) - result -``` -### Reference - -For more information, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html), -(which uses a different syntax that has been superseded). - -[More details](./context-functions-spec.html) diff --git a/_scala3-reference/contextual/conversions.md b/_scala3-reference/contextual/conversions.md deleted file mode 100644 index 5a222515ad..0000000000 --- a/_scala3-reference/contextual/conversions.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: "Implicit Conversions" -type: section -num: 24 -previous-page: /scala3/reference/contextual/context-functions -next-page: /scala3/reference/contextual/by-name-context-parameters ---- - -Implicit conversions are defined by given instances of the `scala.Conversion` class. -This class is defined in package `scala` as follows: -```scala -abstract class Conversion[-T, +U] extends (T => U): - def apply (x: T): U -``` -For example, here is an implicit conversion from `String` to `Token`: -```scala -given Conversion[String, Token] with - def apply(str: String): Token = new KeyWord(str) -``` -Using an alias this can be expressed more concisely as: -```scala -given Conversion[String, Token] = new KeyWord(_) -``` -An implicit conversion is applied automatically by the compiler in three situations: - -1. If an expression `e` has type `T`, and `T` does not conform to the expression's expected type `S`. -2. In a selection `e.m` with `e` of type `T`, but `T` defines no member `m`. -3. In an application `e.m(args)` with `e` of type `T`, if `T` does define - some member(s) named `m`, but none of these members can be applied to the arguments `args`. - -In the first case, the compiler looks for a given `scala.Conversion` instance that maps -an argument of type `T` to type `S`. In the second and third -case, it looks for a given `scala.Conversion` instance that maps an argument of type `T` -to a type that defines a member `m` which can be applied to `args` if present. -If such an instance `C` is found, the expression `e` is replaced by `C.apply(e)`. - -## Examples - -1. The `Predef` package contains "auto-boxing" conversions that map -primitive number types to subclasses of `java.lang.Number`. For instance, the -conversion from `Int` to `java.lang.Integer` can be defined as follows: - ```scala - given int2Integer: Conversion[Int, java.lang.Integer] = - java.lang.Integer.valueOf(_) - ``` - -2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. Example: - ```scala - object Completions: - - // The argument "magnet" type - enum CompletionArg: - case Error(s: String) - case Response(f: Future[HttpResponse]) - case Status(code: Future[StatusCode]) - - object CompletionArg: - - // conversions defining the possible arguments to pass to `complete` - // these always come with CompletionArg - // They can be invoked explicitly, e.g. - // - // CompletionArg.fromStatusCode(statusCode) - - given fromString : Conversion[String, CompletionArg] = Error(_) - given fromFuture : Conversion[Future[HttpResponse], CompletionArg] = Response(_) - given fromStatusCode: Conversion[Future[StatusCode], CompletionArg] = Status(_) - end CompletionArg - import CompletionArg.* - - def complete[T](arg: CompletionArg) = arg match - case Error(s) => ... - case Response(f) => ... - case Status(code) => ... - - end Completions - ``` -This setup is more complicated than simple overloading of `complete`, but it can still be useful if normal overloading is not available (as in the case above, since we cannot have two overloaded methods that take `Future[...]` arguments), or if normal overloading would lead to a combinatorial explosion of variants. diff --git a/_scala3-reference/contextual/derivation-macro.md b/_scala3-reference/contextual/derivation-macro.md deleted file mode 100644 index c9c657d780..0000000000 --- a/_scala3-reference/contextual/derivation-macro.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "How to write a type class `derived` method using macros" ---- - -In the main [derivation](./derivation.html) documentation page, we explained the -details behind `Mirror`s and type class derivation. Here we demonstrate how to -implement a type class `derived` method using macros only. We follow the same -example of deriving `Eq` instances and for simplicity we support a `Product` -type e.g., a case class `Person`. The low-level method we will use to implement -the `derived` method exploits quotes, splices of both expressions and types and -the `scala.quoted.Expr.summon` method which is the equivalent of -`summonFrom`. The former is suitable for use in a quote context, used within -macros. - -As in the original code, the type class definition is the same: - -```scala -trait Eq[T]: - def eqv(x: T, y: T): Boolean -``` - -we need to implement a method `Eq.derived` on the companion object of `Eq` that -produces a quoted instance for `Eq[T]`. Here is a possible signature, - -```scala -given derived[T: Type](using Quotes): Expr[Eq[T]] -``` - -and for comparison reasons we give the same signature we had with `inline`: - -```scala -inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = ??? -``` - -Note, that since a type is used in a subsequent stage it will need to be lifted -to a `Type` by using the corresponding context bound. Also, not that we can -summon the quoted `Mirror` inside the body of the `derived` this we can omit it -from the signature. The body of the `derived` method is shown below: - - -```scala -given derived[T: Type](using Quotes): Expr[Eq[T]] = - import quotes.reflect.* - - val ev: Expr[Mirror.Of[T]] = Expr.summon[Mirror.Of[T]].get - - ev match - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = elementTypes }} => - val elemInstances = summonAll[elementTypes] - val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => - elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { - case (acc, (elem, index)) => - val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} - val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } - } - - '{ eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) } - - // case for Mirror.ProductOf[T] - // ... -``` - -Note, that in the `inline` case we can merely write -`summonAll[m.MirroredElemTypes]` inside the inline method but here, since -`Expr.summon` is required, we can extract the element types in a macro fashion. -Being inside a macro, our first reaction would be to write the code below. Since -the path inside the type argument is not stable this cannot be used: - -```scala -'{ - summonAll[$m.MirroredElemTypes] -} -``` - -Instead we extract the tuple-type for element types using pattern matching over -quotes and more specifically of the refined type: - -```scala - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = elementTypes }} => ... -``` - -Shown below is the implementation of `summonAll` as a macro. We assume that -given instances for our primitive types exist. - -```scala -def summonAll[T: Type](using Quotes): List[Expr[Eq[_]]] = - Type.of[T] match - case '[String *: tpes] => '{ summon[Eq[String]] } :: summonAll[tpes] - case '[Int *: tpes] => '{ summon[Eq[Int]] } :: summonAll[tpes] - case '[tpe *: tpes] => derived[tpe] :: summonAll[tpes] - case '[EmptyTuple] => Nil -``` - -One additional difference with the body of `derived` here as opposed to the one -with `inline` is that with macros we need to synthesize the body of the code during the -macro-expansion time. That is the rationale behind the `eqProductBody` function. -Assuming that we calculate the equality of two `Person`s defined with a case -class that holds a name of type [`String`](https://scala-lang.org/api/3.x/scala/Predef$.html#String-0) -and an age of type `Int`, the equality check we want to generate is the following: - -```scala - true - && Eq[String].eqv(x.productElement(0),y.productElement(0)) - && Eq[Int].eqv(x.productElement(1), y.productElement(1)) -``` - -## Calling the derived method inside the macro - -Following the rules in [Macros](../metaprogramming.html) we create two methods. -One that hosts the top-level splice `eqv` and one that is the implementation. -Alternatively and what is shown below is that we can call the `eqv` method -directly. The `eqGen` can trigger the derivation. - -```scala -extension [T](inline x: T) - inline def === (inline y: T)(using eq: Eq[T]): Boolean = eq.eqv(x, y) - -inline given eqGen[T]: Eq[T] = ${ Eq.derived[T] } -``` - -Note, that we use inline method syntax and we can compare instance such as -`Sm(Person("Test", 23)) === Sm(Person("Test", 24))` for e.g., the following two -types: - -```scala -case class Person(name: String, age: Int) - -enum Opt[+T]: - case Sm(t: T) - case Nn -``` - -The full code is shown below: - -```scala -import scala.deriving.* -import scala.quoted.* - - -trait Eq[T]: - def eqv(x: T, y: T): Boolean - -object Eq: - given Eq[String] with - def eqv(x: String, y: String) = x == y - - given Eq[Int] with - def eqv(x: Int, y: Int) = x == y - - def eqProduct[T](body: (T, T) => Boolean): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = body(x, y) - - def eqSum[T](body: (T, T) => Boolean): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = body(x, y) - - def summonAll[T: Type](using Quotes): List[Expr[Eq[_]]] = - Type.of[T] match - case '[String *: tpes] => '{ summon[Eq[String]] } :: summonAll[tpes] - case '[Int *: tpes] => '{ summon[Eq[Int]] } :: summonAll[tpes] - case '[tpe *: tpes] => derived[tpe] :: summonAll[tpes] - case '[EmptyTuple] => Nil - - given derived[T: Type](using q: Quotes): Expr[Eq[T]] = - import quotes.reflect.* - - val ev: Expr[Mirror.Of[T]] = Expr.summon[Mirror.Of[T]].get - - ev match - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = elementTypes }} => - val elemInstances = summonAll[elementTypes] - val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => - elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { - case (acc, (elem, index)) => - val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} - val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - - '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } - } - '{ eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) } - - case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = elementTypes }} => - val elemInstances = summonAll[elementTypes] - val eqSumBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => - val ordx = '{ $m.ordinal($x) } - val ordy = '{ $m.ordinal($y) } - - val elements = Expr.ofList(elemInstances) - '{ $ordx == $ordy && $elements($ordx).asInstanceOf[Eq[Any]].eqv($x, $y) } - - '{ eqSum((x: T, y: T) => ${eqSumBody('x, 'y)}) } - end derived -end Eq - -object Macro3: - extension [T](inline x: T) - inline def === (inline y: T)(using eq: Eq[T]): Boolean = eq.eqv(x, y) - - inline given eqGen[T]: Eq[T] = ${ Eq.derived[T] } -``` diff --git a/_scala3-reference/contextual/derivation.md b/_scala3-reference/contextual/derivation.md deleted file mode 100644 index 8039cfdc1a..0000000000 --- a/_scala3-reference/contextual/derivation.md +++ /dev/null @@ -1,405 +0,0 @@ ---- -title: "Type Class Derivation" -type: section -num: 21 -previous-page: /scala3/reference/contextual/type-classes -next-page: /scala3/reference/contextual/multiversal-equality ---- - -Type class derivation is a way to automatically generate given instances for type classes which satisfy some simple -conditions. A type class in this sense is any trait or class with a type parameter determining the type being operated -on. Common examples are `Eq`, `Ordering`, or `Show`. For example, given the following `Tree` algebraic data type -(ADT), - -```scala -enum Tree[T] derives Eq, Ordering, Show: - case Branch(left: Tree[T], right: Tree[T]) - case Leaf(elem: T) -``` - -The `derives` clause generates the following given instances for the `Eq`, `Ordering` and `Show` type classes in the -companion object of `Tree`, - -```scala -given [T: Eq] : Eq[Tree[T]] = Eq.derived -given [T: Ordering] : Ordering[Tree] = Ordering.derived -given [T: Show] : Show[Tree] = Show.derived -``` - -We say that `Tree` is the _deriving type_ and that the `Eq`, `Ordering` and `Show` instances are _derived instances_. - -### Types supporting `derives` clauses - -All data types can have a `derives` clause. This document focuses primarily on data types which also have a given instance -of the `Mirror` type class available. Instances of the `Mirror` type class are generated automatically by the compiler -for, - -+ enums and enum cases -+ case classes and case objects -+ sealed classes or traits that have only case classes and case objects as children - -`Mirror` type class instances provide information at the type level about the components and labelling of the type. -They also provide minimal term level infrastructure to allow higher level libraries to provide comprehensive -derivation support. - -```scala -sealed trait Mirror: - - /** the type being mirrored */ - type MirroredType - - /** the type of the elements of the mirrored type */ - type MirroredElemTypes - - /** The mirrored *-type */ - type MirroredMonoType - - /** The name of the type */ - type MirroredLabel <: String - - /** The names of the elements of the type */ - type MirroredElemLabels <: Tuple - -object Mirror: - - /** The Mirror for a product type */ - trait Product extends Mirror: - - /** Create a new instance of type `T` with elements - * taken from product `p`. - */ - def fromProduct(p: scala.Product): MirroredMonoType - - trait Sum extends Mirror: - - /** The ordinal number of the case class of `x`. - * For enums, `ordinal(x) == x.ordinal` - */ - def ordinal(x: MirroredMonoType): Int - -end Mirror -``` - -Product types (i.e. case classes and objects, and enum cases) have mirrors which are subtypes of `Mirror.Product`. Sum -types (i.e. sealed class or traits with product children, and enums) have mirrors which are subtypes of `Mirror.Sum`. - -For the `Tree` ADT from above the following `Mirror` instances will be automatically provided by the compiler, - -```scala -// Mirror for Tree -new Mirror.Sum: - type MirroredType = Tree - type MirroredElemTypes[T] = (Branch[T], Leaf[T]) - type MirroredMonoType = Tree[_] - type MirroredLabel = "Tree" - type MirroredElemLabels = ("Branch", "Leaf") - - def ordinal(x: MirroredMonoType): Int = x match - case _: Branch[_] => 0 - case _: Leaf[_] => 1 - -// Mirror for Branch -new Mirror.Product: - type MirroredType = Branch - type MirroredElemTypes[T] = (Tree[T], Tree[T]) - type MirroredMonoType = Branch[_] - type MirroredLabel = "Branch" - type MirroredElemLabels = ("left", "right") - - def fromProduct(p: Product): MirroredMonoType = - new Branch(...) - -// Mirror for Leaf -new Mirror.Product: - type MirroredType = Leaf - type MirroredElemTypes[T] = Tuple1[T] - type MirroredMonoType = Leaf[_] - type MirroredLabel = "Leaf" - type MirroredElemLabels = Tuple1["elem"] - - def fromProduct(p: Product): MirroredMonoType = - new Leaf(...) -``` - -Note the following properties of `Mirror` types, - -+ Properties are encoded using types rather than terms. This means that they have no runtime footprint unless used and - also that they are a compile time feature for use with Scala 3's metaprogramming facilities. -+ The kinds of `MirroredType` and `MirroredElemTypes` match the kind of the data type the mirror is an instance for. - This allows `Mirror`s to support ADTs of all kinds. -+ There is no distinct representation type for sums or products (ie. there is no `HList` or `Coproduct` type as in - Scala 2 versions of Shapeless). Instead the collection of child types of a data type is represented by an ordinary, - possibly parameterized, tuple type. Scala 3's metaprogramming facilities can be used to work with these tuple types - as-is, and higher level libraries can be built on top of them. -+ For both product and sum types, the elements of `MirroredElemTypes` are arranged in definition order (i.e. `Branch[T]` - precedes `Leaf[T]` in `MirroredElemTypes` for `Tree` because `Branch` is defined before `Leaf` in the source file). - This means that `Mirror.Sum` differs in this respect from Shapeless's generic representation for ADTs in Scala 2, - where the constructors are ordered alphabetically by name. -+ The methods `ordinal` and `fromProduct` are defined in terms of `MirroredMonoType` which is the type of kind-`*` - which is obtained from `MirroredType` by wildcarding its type parameters. - -### Type classes supporting automatic deriving - -A trait or class can appear in a `derives` clause if its companion object defines a method named `derived`. The -signature and implementation of a `derived` method for a type class `TC[_]` are arbitrary but it is typically of the -following form, - -```scala -import scala.deriving.Mirror - -def derived[T](using Mirror.Of[T]): TC[T] = ... -``` - -That is, the `derived` method takes a context parameter of (some subtype of) type `Mirror` which defines the shape of -the deriving type `T`, and computes the type class implementation according to that shape. This is all that the -provider of an ADT with a `derives` clause has to know about the derivation of a type class instance. - -Note that `derived` methods may have context `Mirror` parameters indirectly (e.g. by having a context argument which in turn -has a context `Mirror` parameter, or not at all (e.g. they might use some completely different user-provided mechanism, for -instance using Scala 3 macros or runtime reflection). We expect that (direct or indirect) `Mirror` based implementations -will be the most common and that is what this document emphasises. - -Type class authors will most likely use higher level derivation or generic programming libraries to implement -`derived` methods. An example of how a `derived` method might be implemented using _only_ the low level facilities -described above and Scala 3's general metaprogramming features is provided below. It is not anticipated that type class -authors would normally implement a `derived` method in this way, however this walkthrough can be taken as a guide for -authors of the higher level derivation libraries that we expect typical type class authors will use (for a fully -worked out example of such a library, see [Shapeless 3](https://github.com/milessabin/shapeless/tree/shapeless-3)). - -#### How to write a type class `derived` method using low level mechanisms - -The low-level method we will use to implement a type class `derived` method in this example exploits three new -type-level constructs in Scala 3: inline methods, inline matches, and implicit searches via `summonInline` or `summonFrom`. Given this definition of the -`Eq` type class, - -```scala -trait Eq[T]: - def eqv(x: T, y: T): Boolean -``` - -we need to implement a method `Eq.derived` on the companion object of `Eq` that produces a given instance for `Eq[T]` given -a `Mirror[T]`. Here is a possible implementation, - -```scala -import scala.deriving.Mirror - -inline given derived[T](using m: Mirror.Of[T]): Eq[T] = - val elemInstances = summonAll[m.MirroredElemTypes] // (1) - inline m match // (2) - case s: Mirror.SumOf[T] => eqSum(s, elemInstances) - case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) -``` - -Note that `derived` is defined as an `inline` given. This means that the method will be expanded at -call sites (for instance the compiler generated instance definitions in the companion objects of ADTs which have a -`derived Eq` clause), and also that it can be used recursively if necessary, to compute instances for children. - -The body of this method (1) first materializes the `Eq` instances for all the child types of type the instance is -being derived for. This is either all the branches of a sum type or all the fields of a product type. The -implementation of `summonAll` is `inline` and uses Scala 3's `summonInline` construct to collect the instances as a -`List`, - -```scala -inline def summonAll[T <: Tuple]: List[Eq[_]] = - inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts] -``` - -with the instances for children in hand the `derived` method uses an `inline match` to dispatch to methods which can -construct instances for either sums or products (2). Note that because `derived` is `inline` the match will be -resolved at compile-time and only the left-hand side of the matching case will be inlined into the generated code with -types refined as revealed by the match. - -In the sum case, `eqSum`, we use the runtime `ordinal` values of the arguments to `eqv` to first check if the two -values are of the same subtype of the ADT (3) and then, if they are, to further test for equality based on the `Eq` -instance for the appropriate ADT subtype using the auxiliary method `check` (4). - -```scala -import scala.deriving.Mirror - -def eqSum[T](s: Mirror.SumOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = - val ordx = s.ordinal(x) // (3) - (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) // (4) -``` - -In the product case, `eqProduct` we test the runtime values of the arguments to `eqv` for equality as products based -on the `Eq` instances for the fields of the data type (5), - -```scala -import scala.deriving.Mirror - -def eqProduct[T](p: Mirror.ProductOf[T], elems: List[Eq[_]]): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = - iterator(x).zip(iterator(y)).zip(elems.iterator).forall { // (5) - case ((x, y), elem) => check(elem)(x, y) - } -``` - -Pulling this all together we have the following complete implementation, - -```scala -import scala.deriving.* -import scala.compiletime.{erasedValue, summonInline} - -inline def summonAll[T <: Tuple]: List[Eq[_]] = - inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts] - -trait Eq[T]: - def eqv(x: T, y: T): Boolean - -object Eq: - given Eq[Int] with - def eqv(x: Int, y: Int) = x == y - - def check(elem: Eq[_])(x: Any, y: Any): Boolean = - elem.asInstanceOf[Eq[Any]].eqv(x, y) - - def iterator[T](p: T) = p.asInstanceOf[Product].productIterator - - def eqSum[T](s: Mirror.SumOf[T], elems: => List[Eq[_]]): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = - val ordx = s.ordinal(x) - (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) - - def eqProduct[T](p: Mirror.ProductOf[T], elems: => List[Eq[_]]): Eq[T] = - new Eq[T]: - def eqv(x: T, y: T): Boolean = - iterator(x).zip(iterator(y)).zip(elems.iterator).forall { - case ((x, y), elem) => check(elem)(x, y) - } - - inline given derived[T](using m: Mirror.Of[T]): Eq[T] = - lazy val elemInstances = summonAll[m.MirroredElemTypes] - inline m match - case s: Mirror.SumOf[T] => eqSum(s, elemInstances) - case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) -end Eq -``` - -we can test this relative to a simple ADT like so, - -```scala -enum Opt[+T] derives Eq: - case Sm(t: T) - case Nn - -@main def test(): Unit = - import Opt.* - val eqoi = summon[Eq[Opt[Int]]] - assert(eqoi.eqv(Sm(23), Sm(23))) - assert(!eqoi.eqv(Sm(23), Sm(13))) - assert(!eqoi.eqv(Sm(23), Nn)) -``` - -In this case the code that is generated by the inline expansion for the derived `Eq` instance for `Opt` looks like the -following, after a little polishing, - -```scala -given derived$Eq[T](using eqT: Eq[T]): Eq[Opt[T]] = - eqSum( - summon[Mirror[Opt[T]]], - List( - eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]])), - eqProduct(summon[Mirror[Nn.type]], Nil) - ) - ) -``` - -Alternative approaches can be taken to the way that `derived` methods can be defined. For example, more aggressively -inlined variants using Scala 3 macros, whilst being more involved for type class authors to write than the example -above, can produce code for type classes like `Eq` which eliminate all the abstraction artefacts (eg. the `Lists` of -child instances in the above) and generate code which is indistinguishable from what a programmer might write by hand. -As a third example, using a higher level library such as Shapeless the type class author could define an equivalent -`derived` method as, - -```scala -given eqSum[A](using inst: => K0.CoproductInstances[Eq, A]): Eq[A] with - def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)( - [t] => (eqt: Eq[t], t0: t, t1: t) => eqt.eqv(t0, t1) - ) - -given eqProduct[A](using inst: K0.ProductInstances[Eq, A]): Eq[A] with - def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)( - [t] => (acc: Boolean, eqt: Eq[t], t0: t, t1: t) => - Complete(!eqt.eqv(t0, t1))(false)(true) - ) - -inline def derived[A](using gen: K0.Generic[A]): Eq[A] = - gen.derive(eqProduct, eqSum) -``` - -The framework described here enables all three of these approaches without mandating any of them. - -For a brief discussion on how to use macros to write a type class `derived` -method please read more at [How to write a type class `derived` method using macros](./derivation-macro.html). - -### Deriving instances elsewhere - -Sometimes one would like to derive a type class instance for an ADT after the ADT is defined, without being able to -change the code of the ADT itself. To do this, simply define an instance using the `derived` method of the type class -as right-hand side. E.g, to implement `Ordering` for `Option` define, - -```scala -given [T: Ordering]: Ordering[Option[T]] = Ordering.derived -``` - -Assuming the `Ordering.derived` method has a context parameter of type `Mirror[T]` it will be satisfied by the -compiler generated `Mirror` instance for `Option` and the derivation of the instance will be expanded on the right -hand side of this definition in the same way as an instance defined in ADT companion objects. - -### Syntax - -``` -Template ::= InheritClauses [TemplateBody] -EnumDef ::= id ClassConstr InheritClauses EnumBody -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] -ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} -``` - -**Note:** To align `extends` clauses and `derives` clauses, Scala 3 also allows multiple -extended types to be separated by commas. So the following is now legal: - -```scala -class A extends B, C { ... } -``` - -It is equivalent to the old form - -```scala -class A extends B with C { ... } -``` - -### Discussion - -This type class derivation framework is intentionally very small and low-level. There are essentially two pieces of -infrastructure in compiler-generated `Mirror` instances, - -+ type members encoding properties of the mirrored types. -+ a minimal value level mechanism for working generically with terms of the mirrored types. - -The `Mirror` infrastructure can be seen as an extension of the existing `Product` infrastructure for case classes: -typically `Mirror` types will be implemented by the ADTs companion object, hence the type members and the `ordinal` or -`fromProduct` methods will be members of that object. The primary motivation for this design decision, and the -decision to encode properties via types rather than terms was to keep the bytecode and runtime footprint of the -feature small enough to make it possible to provide `Mirror` instances _unconditionally_. - -Whilst `Mirrors` encode properties precisely via type members, the value level `ordinal` and `fromProduct` are -somewhat weakly typed (because they are defined in terms of `MirroredMonoType`) just like the members of `Product`. -This means that code for generic type classes has to ensure that type exploration and value selection proceed in -lockstep and it has to assert this conformance in some places using casts. If generic type classes are correctly -written these casts will never fail. - -As mentioned, however, the compiler-provided mechanism is intentionally very low level and it is anticipated that -higher level type class derivation and generic programming libraries will build on this and Scala 3's other -metaprogramming facilities to hide these low-level details from type class authors and general users. Type class -derivation in the style of both Shapeless and Magnolia are possible (a prototype of Shapeless 3, which combines -aspects of both Shapeless 2 and Magnolia has been developed alongside this language feature) as is a more aggressively -inlined style, supported by Scala 3's new quote/splice macro and inlining facilities. diff --git a/_scala3-reference/contextual/extension-methods.md b/_scala3-reference/contextual/extension-methods.md deleted file mode 100644 index 06364baa50..0000000000 --- a/_scala3-reference/contextual/extension-methods.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: "Extension Methods" -type: section -num: 19 -previous-page: /scala3/reference/contextual/given-imports -next-page: /scala3/reference/contextual/type-classes ---- - -Extension methods allow one to add methods to a type after the type is defined. Example: - -```scala -case class Circle(x: Double, y: Double, radius: Double) - -extension (c: Circle) - def circumference: Double = c.radius * math.Pi * 2 -``` - -Like regular methods, extension methods can be invoked with infix `.`: - -```scala -val circle = Circle(0, 0, 1) -circle.circumference -``` - -### Translation of Extension Methods - -An extension method translates to a specially labelled method that takes the leading parameter section as its first argument list. The label, expressed -as `` here, is compiler-internal. So, the definition of `circumference` above translates to the following method, and can also be invoked as such: - -``` - def circumference(c: Circle): Double = c.radius * math.Pi * 2 - -assert(circle.circumference == circumference(circle)) -``` - -### Operators - -The extension method syntax can also be used to define operators. Examples: - -```scala -extension (x: String) - def < (y: String): Boolean = ... -extension (x: Elem) - def +: (xs: Seq[Elem]): Seq[Elem] = ... -extension (x: Number) - infix def min (y: Number): Number = ... - -"ab" < "c" -1 +: List(2, 3) -x min 3 -``` - -The three definitions above translate to - -``` - def < (x: String)(y: String): Boolean = ... - def +: (xs: Seq[Elem])(x: Elem): Seq[Elem] = ... - infix def min(x: Number)(y: Number): Number = ... -``` - -Note the swap of the two parameters `x` and `xs` when translating -the right-associative operator `+:` to an extension method. This is analogous -to the implementation of right binding operators as normal methods. The Scala -compiler preprocesses an infix operation `x +: xs` to `xs.+:(x)`, so the extension -method ends up being applied to the sequence as first argument (in other words, the -two swaps cancel each other out). See [here for details](./right-associative-extension-methods.html). - -### Generic Extensions - -It is also possible to extend generic types by adding type parameters to an extension. For instance: - -```scala -extension [T](xs: List[T]) - def second = xs.tail.head - -extension [T: Numeric](x: T) - def + (y: T): T = summon[Numeric[T]].plus(x, y) -``` - -Type parameters on extensions can also be combined with type parameters on the methods -themselves: - -```scala -extension [T](xs: List[T]) - def sumBy[U: Numeric](f: T => U): U = ... -``` - -Type arguments matching method type parameters are passed as usual: - -```scala -List("a", "bb", "ccc").sumBy[Int](_.length) -``` - -By contrast, type arguments matching type parameters following `extension` can be passed -only if the method is referenced as a non-extension method: - -```scala -sumBy[String](List("a", "bb", "ccc"))(_.length) -``` - -Or, when passing both type arguments: - -```scala -sumBy[String](List("a", "bb", "ccc"))[Int](_.length) -``` - -Extensions can also take using clauses. For instance, the `+` extension above could equivalently be written with a using clause: - -```scala -extension [T](x: T)(using n: Numeric[T]) - def + (y: T): T = n.plus(x, y) -``` - -### Collective Extensions - -Sometimes, one wants to define several extension methods that share the same -left-hand parameter type. In this case one can "pull out" the common parameters into -a single extension and enclose all methods in braces or an indented region. -Example: - -```scala -extension (ss: Seq[String]) - - def longestStrings: Seq[String] = - val maxLength = ss.map(_.length).max - ss.filter(_.length == maxLength) - - def longestString: String = longestStrings.head -``` - -The same can be written with braces as follows (note that indented regions can still be used inside braces): - -```scala -extension (ss: Seq[String]) { - - def longestStrings: Seq[String] = { - val maxLength = ss.map(_.length).max - ss.filter(_.length == maxLength) - } - - def longestString: String = longestStrings.head -} -``` - -Note the right-hand side of `longestString`: it calls `longestStrings` directly, implicitly -assuming the common extended value `ss` as receiver. - -Collective extensions like these are a shorthand for individual extensions -where each method is defined separately. For instance, the first extension above expands to: - -```scala -extension (ss: Seq[String]) - def longestStrings: Seq[String] = - val maxLength = ss.map(_.length).max - ss.filter(_.length == maxLength) - -extension (ss: Seq[String]) - def longestString: String = ss.longestStrings.head -``` - -Collective extensions also can take type parameters and have using clauses. Example: - -```scala -extension [T](xs: List[T])(using Ordering[T]) - def smallest(n: Int): List[T] = xs.sorted.take(n) - def smallestIndices(n: Int): List[Int] = - val limit = smallest(n).max - xs.zipWithIndex.collect { case (x, i) if x <= limit => i } -``` - -### Translation of Calls to Extension Methods - -To convert a reference to an extension method, the compiler has to know about the extension -method. We say in this case that the extension method is _applicable_ at the point of reference. -There are four possible ways for an extension method to be applicable: - - 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference. - 2. The extension method is a member of some given - instance that is visible at the point of the reference. - 3. The reference is of the form `r.m` and the extension method - is defined in the implicit scope of the type of `r`. - 4. The reference is of the form `r.m` and the extension method - is defined in some given instance in the implicit scope of the type of `r`. - -Here is an example for the first rule: - -```scala -trait IntOps: - extension (i: Int) def isZero: Boolean = i == 0 - - extension (i: Int) def safeMod(x: Int): Option[Int] = - // extension method defined in same scope IntOps - if x.isZero then None - else Some(i % x) - -object IntOpsEx extends IntOps: - extension (i: Int) def safeDiv(x: Int): Option[Int] = - // extension method brought into scope via inheritance from IntOps - if x.isZero then None - else Some(i / x) - -trait SafeDiv: - import IntOpsEx.* // brings safeDiv and safeMod into scope - - extension (i: Int) def divide(d: Int): Option[(Int, Int)] = - // extension methods imported and thus in scope - (i.safeDiv(d), i.safeMod(d)) match - case (Some(d), Some(r)) => Some((d, r)) - case _ => None -``` - -By the second rule, an extension method can be made available by defining a given instance containing it, like this: - -```scala -given ops1: IntOps() // brings safeMod into scope - -1.safeMod(2) -``` - -By the third and fourth rule, an extension method is available if it is in the implicit scope of the receiver type or in a given instance in that scope. Example: - -```scala -class List[T]: - ... -object List: - ... - extension [T](xs: List[List[T]]) - def flatten: List[T] = xs.foldLeft(List.empty[T])(_ ++ _) - - given [T: Ordering]: Ordering[List[T]] with - extension (xs: List[T]) - def < (ys: List[T]): Boolean = ... -end List - -// extension method available since it is in the implicit scope -// of List[List[Int]] -List(List(1, 2), List(3, 4)).flatten - -// extension method available since it is in the given Ordering[List[T]], -// which is itself in the implicit scope of List[Int] -List(1, 2) < List(3) -``` - -The precise rules for resolving a selection to an extension method are as follows. - -Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, and where `T` is the expected type. -The following two rewritings are tried in order: - - 1. The selection is rewritten to `m[Ts](e)`. - 2. If the first rewriting does not typecheck with expected type `T`, - and there is an extension method `m` in some eligible object `o`, the selection is rewritten to `o.m[Ts](e)`. An object `o` is _eligible_ if - - - `o` forms part of the implicit scope of `T`, or - - `o` is a given instance that is visible at the point of the application, or - - `o` is a given instance in the implicit scope of `T`. - - This second rewriting is attempted at the time where the compiler also tries an implicit conversion - from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. - -An extension method can also be referenced using a simple identifier without a preceding expression. If an identifier `g` appears in the body of an extension method `f` and refers to an extension method `g` that is defined in the same collective extension - -```scala -extension (x: T) - def f ... = ... g ... - def g ... -``` - -the identifier is rewritten to `x.g`. This is also the case if `f` and `g` are the same method. Example: - -```scala -extension (s: String) - def position(ch: Char, n: Int): Int = - if n < s.length && s(n) != ch then position(ch, n + 1) - else n -``` - -The recursive call `position(ch, n + 1)` expands to `s.position(ch, n + 1)` in this case. The whole extension method rewrites to - -```scala -def position(s: String)(ch: Char, n: Int): Int = - if n < s.length && s(n) != ch then position(s)(ch, n + 1) - else n -``` - -### Syntax - -Here are the syntax changes for extension methods and collective extensions relative -to the [current syntax](../syntax.html). - -``` -BlockStat ::= ... | Extension -TemplateStat ::= ... | Extension -TopStat ::= ... | Extension -Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef -``` - -In the above the notation `<<< ts >>>` in the production rule `ExtMethods` is defined as follows : - -``` -<<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent -``` - -`extension` is a soft keyword. It is recognized as a keyword only if it appears -at the start of a statement and is followed by `[` or `(`. In all other cases -it is treated as an identifier. diff --git a/_scala3-reference/contextual/given-imports.md b/_scala3-reference/contextual/given-imports.md deleted file mode 100644 index 7e49841588..0000000000 --- a/_scala3-reference/contextual/given-imports.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: "Importing Givens" -type: section -num: 18 -previous-page: /scala3/reference/contextual/context-bounds -next-page: /scala3/reference/contextual/extension-methods ---- - -A special form of import wildcard selector is used to import given instances. Example: - -```scala -object A: - class TC - given tc: TC = ??? - def f(using TC) = ??? - -object B: - import A.* - import A.given - ... -``` - -In the code above, the `import A.*` clause in object `B` imports all members -of `A` _except_ the given instance `tc`. Conversely, the second import `import A.given` will import _only_ that given instance. -The two import clauses can also be merged into one: - -```scala -object B: - import A.{given, *} - ... -``` - -Generally, a normal wildcard selector `*` brings all definitions other than givens or extensions into scope -whereas a `given` selector brings all givens (including those resulting from extensions) into scope. - -There are two main benefits arising from these rules: - -- It is made clearer where givens in scope are coming from. - In particular, it is not possible to hide imported givens in a long list of regular wildcard imports. -- It enables importing all givens - without importing anything else. This is particularly important since givens - can be anonymous, so the usual recourse of using named imports is not - practical. - -### Importing By Type - -Since givens can be anonymous it is not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it clearer what is imported. Example: - -```scala -import A.given TC -``` - -This imports any given in `A` that has a type which conforms to `TC`. Importing givens of several types `T1,...,Tn` -is expressed by multiple `given` selectors. - -```scala -import A.{given T1, ..., given Tn} -``` - -Importing all given instances of a parameterized type is expressed by wildcard arguments. -For instance, assuming the object - -```scala -object Instances: - given intOrd: Ordering[Int] = ... - given listOrd[T: Ordering]: Ordering[List[T]] = ... - given ec: ExecutionContext = ... - given im: Monoid[Int] = ... -``` - -the import clause - -```scala -import Instances.{given Ordering[?], given ExecutionContext} -``` - -would import the `intOrd`, `listOrd`, and `ec` instances but leave out the `im` instance, since it fits none of the specified bounds. - -By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, the import clause - -```scala -import Instances.{im, given Ordering[?]} -``` - -would import `im`, `intOrd`, and `listOrd` but leave out `ec`. - -### Migration - -The rules for imports stated above have the consequence that a library -would have to migrate in lockstep with all its users from old style implicits and -normal imports to givens and given imports. - -The following modifications avoid this hurdle to migration. - - 1. A `given` import selector also brings old style implicits into scope. So, in Scala 3.0 - an old-style implicit definition can be brought into scope either by a `*` or a `given` wildcard selector. - - 2. In Scala 3.1, old-style implicits accessed through a `*` wildcard import will give a deprecation warning. - - 3. In some version after 3.1, old-style implicits accessed through a `*` wildcard import will give a compiler error. - -These rules mean that library users can use `given` selectors to access old-style implicits in Scala 3.0, -and will be gently nudged and then forced to do so in later versions. Libraries can then switch to -given instances once their user base has migrated. - -### Syntax - -``` -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec -ImportSpec ::= NamedSelector - | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ -NamedSelector ::= id [‘as’ (id | ‘_’)] -WildCardSelector ::= ‘*' | ‘given’ [InfixType] -ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} -``` diff --git a/_scala3-reference/contextual/givens.md b/_scala3-reference/contextual/givens.md deleted file mode 100644 index 0ca25c068c..0000000000 --- a/_scala3-reference/contextual/givens.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: "Given Instances" -type: section -num: 15 -previous-page: /scala3/reference/contextual -next-page: /scala3/reference/contextual/using-clauses ---- - -Given instances (or, simply, "givens") define "canonical" values of certain types -that serve for synthesizing arguments to [context parameters](./using-clauses.html). Example: - -```scala -trait Ord[T]: - def compare(x: T, y: T): Int - extension (x: T) def < (y: T) = compare(x, y) < 0 - extension (x: T) def > (y: T) = compare(x, y) > 0 - -given intOrd: Ord[Int] with - def compare(x: Int, y: Int) = - if x < y then -1 else if x > y then +1 else 0 - -given listOrd[T](using ord: Ord[T]): Ord[List[T]] with - - def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = ord.compare(x, y) - if fst != 0 then fst else compare(xs1, ys1) - -``` - -This code defines a trait `Ord` with two given instances. `intOrd` defines -a given for the type `Ord[Int]` whereas `listOrd[T]` defines givens -for `Ord[List[T]]` for all types `T` that come with a given instance for `Ord[T]` -themselves. The `using` clause in `listOrd` defines a condition: There must be a -given of type `Ord[T]` for a given of type `Ord[List[T]]` to exist. -Such conditions are expanded by the compiler to [context parameters](./using-clauses.html). - -## Anonymous Givens - -The name of a given can be left out. So the definitions -of the last section can also be expressed like this: - -```scala -given Ord[Int] with - ... -given [T](using Ord[T]): Ord[List[T]] with - ... -``` - -If the name of a given is missing, the compiler will synthesize a name from -the implemented type(s). - -**Note** The name synthesized by the compiler is chosen to be readable and reasonably concise. For instance, the two instances above would get the names: - -```scala -given_Ord_Int -given_Ord_List_T -``` - -The precise rules for synthesizing names are found [here](./relationship-implicits.html#anonymous-given-instances). These rules do not guarantee absence of name conflicts between -given instances of types that are "too similar". To avoid conflicts one can -use named instances. - -**Note** To ensure robust binary compatibility, publicly available libraries should prefer named instances. - -## Alias Givens - -An alias can be used to define a given instance that is equal to some expression. Example: - -```scala -given global: ExecutionContext = ForkJoinPool() -``` - -This creates a given `global` of type `ExecutionContext` that resolves to the right -hand side `ForkJoinPool()`. -The first time `global` is accessed, a new `ForkJoinPool` is created, which is then -returned for this and all subsequent accesses to `global`. This operation is thread-safe. - -Alias givens can be anonymous as well, e.g. - -```scala -given Position = enclosingTree.position -given (using config: Config): Factory = MemoizingFactory(config) -``` - -An alias given can have type parameters and context parameters just like any other given, -but it can only implement a single type. - -## Given Macros - -Given aliases can have the `inline` and `transparent` modifiers. -Example: - -```scala -transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${ - // code producing a value of a subtype of Annotations -} -``` - -Since `mkAnnotations` is `transparent`, the type of an application is the type of its right-hand side, which can be a proper subtype of the declared result type `Annotations[A, T]`. - -## Pattern-Bound Given Instances - -Given instances can also appear in patterns. Example: - -```scala -for given Context <- applicationContexts do - -pair match - case (ctx @ given Context, y) => ... -``` - -In the first fragment above, anonymous given instances for class `Context` are established by enumerating over `applicationContexts`. In the second fragment, a given `Context` -instance named `ctx` is established by matching against the first half of the `pair` selector. - -In each case, a pattern-bound given instance consists of `given` and a type `T`. The pattern matches exactly the same selectors as the type ascription pattern `_: T`. - -## Negated Givens - -Scala 2's somewhat puzzling behavior with respect to ambiguity has been exploited to implement the analogue of a "negated" search in implicit resolution, -where a query Q1 fails if some other query Q2 succeeds and Q1 succeeds if Q2 fails. With the new cleaned up behavior these techniques no longer work. -But the new special type `scala.util.NotGiven` now implements negation directly. - -For any query type `Q`, `NotGiven[Q]` succeeds if and only if the implicit -search for `Q` fails, for example: - -```scala -import scala.util.NotGiven - -trait Tagged[A] - -case class Foo[A](value: Boolean) -object Foo: - given fooTagged[A](using Tagged[A]): Foo[A] = Foo(true) - given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo(false) - -@main def test(): Unit = - given Tagged[Int]() - assert(summon[Foo[Int]].value) // fooTagged is found - assert(!summon[Foo[String]].value) // fooNotTagged is found -``` - -## Given Instance Initialization - -A given instance without type or context parameters is initialized on-demand, the first -time it is accessed. If a given has type or context parameters, a fresh instance -is created for each reference. - -## Syntax - -Here is the syntax for given instances: - -``` -TmplDef ::= ... - | ‘given’ GivenDef -GivenDef ::= [GivenSig] StructuralInstance - | [GivenSig] AnnotType ‘=’ Expr - | [GivenSig] AnnotType -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody -``` - -A given instance starts with the reserved word `given` and an optional _signature_. The signature -defines a name and/or parameters for the instance. It is followed by `:`. There are three kinds -of given instances: - -- A _structural instance_ contains one or more types or constructor applications, - followed by `with` and a template body that contains member definitions of the instance. -- An _alias instance_ contains a type, followed by `=` and a right-hand side expression. -- An _abstract instance_ contains just the type, which is not followed by anything. diff --git a/_scala3-reference/contextual/multiversal-equality.md b/_scala3-reference/contextual/multiversal-equality.md deleted file mode 100644 index 19b44c6544..0000000000 --- a/_scala3-reference/contextual/multiversal-equality.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -title: "Multiversal Equality" -type: section -num: 22 -previous-page: /scala3/reference/contextual/derivation -next-page: /scala3/reference/contextual/context-functions ---- - -Previously, Scala had universal equality: Two values of any types -could be compared with each other with `==` and `!=`. This came from -the fact that `==` and `!=` are implemented in terms of Java's -`equals` method, which can also compare values of any two reference -types. - -Universal equality is convenient. But it is also dangerous since it -undermines type safety. For instance, let's assume one is left after some refactoring -with an erroneous program where a value `y` has type `S` instead of the correct type `T`. - -```scala -val x = ... // of type T -val y = ... // of type S, but should be T -x == y // typechecks, will always yield false -``` - -If `y` gets compared to other values of type `T`, -the program will still typecheck, since values of all types can be compared with each other. -But it will probably give unexpected results and fail at runtime. - -Multiversal equality is an opt-in way to make universal equality safer. -It uses a binary type class [`scala.CanEqual`](https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala) -to indicate that values of two given types can be compared with each other. -The example above would not typecheck if `S` or `T` was a class -that derives `CanEqual`, e.g. - -```scala -class T derives CanEqual -``` - -Alternatively, one can also provide a `CanEqual` given instance directly, like this: - -```scala -given CanEqual[T, T] = CanEqual.derived -``` - -This definition effectively says that values of type `T` can (only) be -compared to other values of type `T` when using `==` or `!=`. The definition -affects type checking but it has no significance for runtime -behavior, since `==` always maps to `equals` and `!=` always maps to -the negation of `equals`. The right-hand side `CanEqual.derived` of the definition -is a value that has any `CanEqual` instance as its type. Here is the definition of class -`CanEqual` and its companion object: - -```scala -package scala -import annotation.implicitNotFound - -@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") -sealed trait CanEqual[-L, -R] - -object CanEqual: - object derived extends CanEqual[Any, Any] -``` - -One can have several `CanEqual` given instances for a type. For example, the four -definitions below make values of type `A` and type `B` comparable with -each other, but not comparable to anything else: - -```scala -given CanEqual[A, A] = CanEqual.derived -given CanEqual[B, B] = CanEqual.derived -given CanEqual[A, B] = CanEqual.derived -given CanEqual[B, A] = CanEqual.derived -``` - -The [`scala.CanEqual`](https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala) -object defines a number of `CanEqual` given instances that together -define a rule book for what standard types can be compared (more details below). - -There is also a "fallback" instance named `canEqualAny` that allows comparisons -over all types that do not themselves have a `CanEqual` given. `canEqualAny` is defined as follows: - -```scala -def canEqualAny[L, R]: CanEqual[L, R] = CanEqual.derived -``` - -Even though `canEqualAny` is not declared as `given`, the compiler will still -construct an `canEqualAny` instance as answer to an implicit search for the -type `CanEqual[L, R]`, unless `L` or `R` have `CanEqual` instances -defined on them, or the language feature `strictEquality` is enabled. - -The primary motivation for having `canEqualAny` is backwards compatibility. -If this is of no concern, one can disable `canEqualAny` by enabling the language -feature `strictEquality`. As for all language features this can be either -done with an import - -```scala -import scala.language.strictEquality -``` -or with a command line option `-language:strictEquality`. - -## Deriving CanEqual Instances - -Instead of defining `CanEqual` instances directly, it is often more convenient to derive them. Example: - -```scala -class Box[T](x: T) derives CanEqual -``` - -By the usual rules of [type class derivation](./derivation.html), -this generates the following `CanEqual` instance in the companion object of `Box`: - -```scala -given [T, U](using CanEqual[T, U]): CanEqual[Box[T], Box[U]] = - CanEqual.derived -``` - -That is, two boxes are comparable with `==` or `!=` if their elements are. Examples: - -```scala -new Box(1) == new Box(1L) // ok since there is an instance for `CanEqual[Int, Long]` -new Box(1) == new Box("a") // error: can't compare -new Box(1) == 1 // error: can't compare -``` - -## Precise Rules for Equality Checking - -The precise rules for equality checking are as follows. - -If the `strictEquality` feature is enabled then -a comparison using `x == y` or `x != y` between values `x: T` and `y: U` -is legal if there is a `given` of type `CanEqual[T, U]`. - -In the default case where the `strictEquality` feature is not enabled the comparison is -also legal if - - 1. `T` and `U` are the same, or - 2. one of `T`, `U` is a subtype of the _lifted_ version of the other type, or - 3. neither `T` nor `U` have a _reflexive_ `CanEqual` instance. - -Explanations: - - - _lifting_ a type `S` means replacing all references to abstract types - in covariant positions of `S` by their upper bound, and replacing - all refinement types in covariant positions of `S` by their parent. - - a type `T` has a _reflexive_ `CanEqual` instance if the implicit search for `CanEqual[T, T]` - succeeds. - -## Predefined CanEqual Instances - -The `CanEqual` object defines instances for comparing - - the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`, - - `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`, - - `scala.collection.Seq`, and `scala.collection.Set`. - -Instances are defined so that every one of these types has a _reflexive_ `CanEqual` instance, and the following holds: - - - Primitive numeric types can be compared with each other. - - Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_). - - `Boolean` can be compared with `java.lang.Boolean` (and _vice versa_). - - `Char` can be compared with `java.lang.Character` (and _vice versa_). - - Two sequences (of arbitrary subtypes of `scala.collection.Seq`) can be compared - with each other if their element types can be compared. The two sequence types - need not be the same. - - Two sets (of arbitrary subtypes of `scala.collection.Set`) can be compared - with each other if their element types can be compared. The two set types - need not be the same. - - Any subtype of `AnyRef` can be compared with `Null` (and _vice versa_). - -## Why Two Type Parameters? - -One particular feature of the `CanEqual` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional -implementations of an equality type class take only a single type parameter which represents the common type of _both_ operands. -One type parameter is simpler than two, so why go through the additional complication? The reason has to do with the fact that, rather than coming up with a type class where no operation existed before, -we are dealing with a refinement of pre-existing, universal equality. It is best illustrated through an example. - -Say you want to come up with a safe version of the `contains` method on `List[T]`. The original definition of `contains` in the standard library was: -```scala -class List[+T]: - ... - def contains(x: Any): Boolean -``` -That uses universal equality in an unsafe way since it permits arguments of any type to be compared with the list's elements. The "obvious" alternative definition -```scala - def contains(x: T): Boolean -``` -does not work, since it refers to the covariant parameter `T` in a nonvariant context. The only variance-correct way to use the type parameter `T` in `contains` is as a lower bound: -```scala - def contains[U >: T](x: U): Boolean -``` -This generic version of `contains` is the one used in the current (Scala 2.13) version of `List`. -It looks different but it admits exactly the same applications as the `contains(x: Any)` definition we started with. -However, we can make it more useful (i.e. restrictive) by adding a `CanEqual` parameter: -```scala - def contains[U >: T](x: U)(using CanEqual[T, U]): Boolean // (1) -``` -This version of `contains` is equality-safe! More precisely, given -`x: T`, `xs: List[T]` and `y: U`, then `xs.contains(y)` is type-correct if and only if -`x == y` is type-correct. - -Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `CanEqual1[T]` type class: -```scala - def contains[U >: T](x: U)(using CanEqual1[U]): Boolean // (2) -``` -This version could be applied just as widely as the original `contains(x: Any)` method, -since the `CanEqual1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `CanEqual[A, B]` is available only if neither `A` nor `B` have a reflexive `CanEqual` instance. That rule simply cannot be expressed if there is a single type parameter for `CanEqual`. - -The situation is different under `-language:strictEquality`. In that case, -the `CanEqual[Any, Any]` or `CanEqual1[Any]` instances would never be available, and the -single and two-parameter versions would indeed coincide for most practical purposes. - -But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `CanEqual` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones. -So it can be done almost at any time, modulo binary compatibility concerns. -On the other hand, parameterizing `contains` with `CanEqual1` as in (2) would make `contains` -unusable for all types that have not yet declared a `CanEqual1` instance, including all -types coming from Java. This is clearly unacceptable. It would lead to a situation where, -rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `CanEqual1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease. - -For these reasons, it looks like a two-parameter type class is the only way forward because it can take the existing ecosystem where it is and migrate it towards a future where more and more code uses safe equality. - -In applications where `-language:strictEquality` is the default one could also introduce a one-parameter type alias such as -```scala -type Eq[-T] = CanEqual[T, T] -``` -Operations needing safe equality could then use this alias instead of the two-parameter `CanEqual` class. But it would only -work under `-language:strictEquality`, since otherwise the universal `Eq[Any]` instance would be available everywhere. - - -More on multiversal equality is found in a [blog post](http://www.scala-lang.org/blog/2016/05/06/multiversal-equality.html) -and a [GitHub issue](https://github.com/lampepfl/dotty/issues/1247). diff --git a/_scala3-reference/contextual/relationship-implicits.md b/_scala3-reference/contextual/relationship-implicits.md deleted file mode 100644 index 9be698affd..0000000000 --- a/_scala3-reference/contextual/relationship-implicits.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: "Relationship with Scala 2 Implicits" -type: section -num: 26 -previous-page: /scala3/reference/contextual/by-name-context-parameters -next-page: /scala3/reference/metaprogramming ---- - -Many, but not all, of the new contextual abstraction features in Scala 3 can be mapped to Scala 2's implicits. This page gives a rundown on the relationships between new and old features. - -## Simulating Scala 3 Contextual Abstraction Concepts with Scala 2 Implicits - -### Given Instances - -Given instances can be mapped to combinations of implicit objects, classes and implicit methods. - - 1. Given instances without parameters are mapped to implicit objects. For instance, - - ```scala - given intOrd: Ord[Int] with { ... } - ``` - - maps to - - ```scala - implicit object intOrd extends Ord[Int] { ... } - ``` - - 2. Parameterized givens are mapped to combinations of classes and implicit methods. For instance, - - ```scala - given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { ... } - ``` - - maps to - - ```scala - class listOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... } - final implicit def listOrd[T](implicit ord: Ord[T]): listOrd[T] = - new listOrd[T] - ``` - - 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type nor context parameters, - it is treated as a lazy val, unless the right-hand side is a simple reference, in which case we can use a forwarder to - that reference without caching it. - -Examples: - -```scala -given global: ExecutionContext = new ForkJoinContext() - -val ctx: Context -given Context = ctx -``` - -would map to - -```scala -final implicit lazy val global: ExecutionContext = new ForkJoinContext() -final implicit def given_Context = ctx -``` - -### Anonymous Given Instances - -Anonymous given instances get compiler synthesized names, which are generated in a reproducible way from the implemented type(s). For example, if the names of the `IntOrd` and `ListOrd` givens above were left out, the following names would be synthesized instead: - -```scala -given given_Ord_Int: Ord[Int] with { ... } -given given_Ord_List_T[T](using ord: Ord[T]): Ord[List[T]] with { ... } -``` - -The synthesized type names are formed from - -1. the prefix `given_`, -2. the simple name(s) of the implemented type(s), leaving out any prefixes, -3. the simple name(s) of the top-level argument type constructors to these types. - -Tuples are treated as transparent, i.e. a type `F[(X, Y)]` would get the synthesized name -`F_X_Y`. Directly implemented function types `A => B` are represented as `A_to_B`. Function types used as arguments to other type constructors are represented as `Function`. - -### Using Clauses - -Using clauses correspond largely to Scala 2's implicit parameter clauses. E.g. - -```scala -def max[T](x: T, y: T)(using ord: Ord[T]): T -``` - -would be written - -```scala -def max[T](x: T, y: T)(implicit ord: Ord[T]): T -``` - -in Scala 2. The main difference concerns applications of such parameters. -Explicit arguments to parameters of using clauses _must_ be written using `(using ...)`, -mirroring the definition syntax. E.g, `max(2, 3)(using IntOrd)`. -Scala 2 uses normal applications `max(2, 3)(IntOrd)` instead. The Scala 2 syntax has some inherent ambiguities and restrictions which are overcome by the new syntax. For instance, multiple implicit parameter lists are not available in the old syntax, even though they can be simulated using auxiliary objects in the "Aux" pattern. - -The `summon` method corresponds to `implicitly` in Scala 2. -It is precisely the same as the `the` method in [Shapeless](https://github.com/milessabin/shapeless). -The difference between `summon` (or `the`) and `implicitly` is -that `summon` can return a more precise type than the type that was -asked for. - -### Context Bounds - -Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. - -**Note:** To ease migration, context bounds in Scala 3 map for a limited time to old-style implicit parameters for which arguments can be passed either in a using clause or -in a normal argument list. Once old-style implicits are deprecated, context bounds -will map to using clauses instead. - -### Extension Methods - -Extension methods have no direct counterpart in Scala 2, but they can be simulated with implicit classes. For instance, the extension method - -```scala -extension (c: Circle) - def circumference: Double = c.radius * math.Pi * 2 -``` - -could be simulated to some degree by - -```scala -implicit class CircleDecorator(c: Circle) extends AnyVal { - def circumference: Double = c.radius * math.Pi * 2 -} -``` - -Abstract extension methods in traits that are implemented in given instances have no direct counterpart in Scala 2. The only way to simulate these is to make implicit classes available through imports. The Simulacrum macro library can automate this process in some cases. - -### Type Class Derivation - -Type class derivation has no direct counterpart in the Scala 2 language. Comparable functionality can be achieved by macro-based libraries such as [Shapeless](https://github.com/milessabin/shapeless), [Magnolia](https://propensive.com/opensource/magnolia), or [scalaz-deriving](https://github.com/scalaz/scalaz-deriving). - -### Context Function Types - -Context function types have no analogue in Scala 2. - -### Implicit By-Name Parameters - -Implicit by-name parameters are not supported in Scala 2, but can be emulated to some degree by the `Lazy` type in Shapeless. - -## Simulating Scala 2 Implicits in Scala 3 - -### Implicit Conversions - -Implicit conversion methods in Scala 2 can be expressed as given instances of the `scala.Conversion` class in Scala 3. For instance, instead of - -```scala -implicit def stringToToken(str: String): Token = new Keyword(str) -``` - -one can write - -```scala -given stringToToken: Conversion[String, Token] with - def apply(str: String): Token = KeyWord(str) -``` - -or - -```scala -given stringToToken: Conversion[String, Token] = KeyWord(_) -``` - -### Implicit Classes - -Implicit classes in Scala 2 are often used to define extension methods, which are directly supported in Scala 3. Other uses of implicit classes can be simulated by a pair of a regular class and a given `Conversion` instance. - -### Implicit Values - -Implicit `val` definitions in Scala 2 can be expressed in Scala 3 using a regular `val` definition and an alias given. -For instance, Scala 2's - -```scala -lazy implicit val pos: Position = tree.sourcePos -``` - -can be expressed in Scala 3 as - -```scala -lazy val pos: Position = tree.sourcePos -given Position = pos -``` - -### Abstract Implicits - -An abstract implicit `val` or `def` in Scala 2 can be expressed in Scala 3 using a regular abstract definition and an alias given. For instance, Scala 2's - -```scala -implicit def symDecorator: SymDecorator -``` - -can be expressed in Scala 3 as - -```scala -def symDecorator: SymDecorator -given SymDecorator = symDecorator -``` - -## Implementation Status and Timeline - -The Scala 3 implementation implements both Scala 2's implicits and the new abstractions. In fact, support for Scala 2's implicits is an essential part of the common language subset between 2.13 and Scala 3. -Migration to the new abstractions will be supported by making automatic rewritings available. - -Depending on adoption patterns, old style implicits might start to be deprecated in a version following Scala 3.0. diff --git a/_scala3-reference/contextual/right-associative-extension-methods.md b/_scala3-reference/contextual/right-associative-extension-methods.md deleted file mode 100644 index b77e6dccdd..0000000000 --- a/_scala3-reference/contextual/right-associative-extension-methods.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Right-Associative Extension Methods: Details" ---- - -The most general form of leading parameters of an extension method is as follows: - - - A possibly empty list of using clauses `leadingUsing` - - A single parameter `extensionParam` - - A possibly empty list of using clauses `trailingUsing` - -This is then followed by `def`, the method name, and possibly further parameters -`otherParams`. An example is: - -```scala - extension (using a: A, b: B)(using c: C) // <-- leadingUsing - (x: X) // <-- extensionParam - (using d: D) // <-- trailingUsing - def +:: (y: Y)(using e: E)(z: Z) // <-- otherParams -``` - -An extension method is treated as a right-associative operator -(as in [SLS §6.12.3](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#infix-operations)) -if it has a name ending in `:` and is immediately followed by a -single parameter. In the example above, that parameter is `(y: Y)`. - -The Scala compiler pre-processes a right-associative infix operation such as `x +: xs` -to `xs.+:(x)` if `x` is a pure expression or a call-by-name parameter and to `val y = x; xs.+:(y)` otherwise. This is necessary since a regular right-associative infix method -is defined in the class of its right operand. To make up for this swap, -the expansion of right-associative extension methods performs an analogous parameter swap. More precisely, if `otherParams` consists of a single parameter -`rightParam` followed by `remaining`, the total parameter sequence -of the extension method's expansion is: - -``` - leadingUsing rightParam trailingUsing extensionParam remaining -``` - -For instance, the `+::` method above would become - -```scala - def +:: (using a: A, b: B)(using c: C) - (y: Y) - (using d: D) - (x: X) - (using e: E)(z: Z) -``` - -This expansion has to be kept in mind when writing right-associative extension -methods with inter-parameter dependencies. - -An overall simpler design could be obtained if right-associative operators could _only_ be defined as extension methods, and would be disallowed as normal methods. In that case neither arguments nor parameters would have to be swapped. Future versions of Scala should strive to achieve this simplification. diff --git a/_scala3-reference/contextual/type-classes.md b/_scala3-reference/contextual/type-classes.md deleted file mode 100644 index 4c80e7b53b..0000000000 --- a/_scala3-reference/contextual/type-classes.md +++ /dev/null @@ -1,284 +0,0 @@ ---- -title: "Implementing Type classes" -type: section -num: 20 -previous-page: /scala3/reference/contextual/extension-methods -next-page: /scala3/reference/contextual/derivation ---- - -A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example: - -* expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior -* expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance) - -Therefore in Scala 3, _type classes_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**. -Here are some examples of common type classes: - -### Semigroups and monoids - -Here's the `Monoid` type class definition: - -```scala -trait SemiGroup[T]: - extension (x: T) def combine (y: T): T - -trait Monoid[T] extends SemiGroup[T]: - def unit: T -``` - -An implementation of this `Monoid` type class for the type `String` can be the following: - -```scala -given Monoid[String] with - extension (x: String) def combine (y: String): String = x.concat(y) - def unit: String = "" -``` - -Whereas for the type `Int` one could write the following: - -```scala -given Monoid[Int] with - extension (x: Int) def combine (y: Int): Int = x + y - def unit: Int = 0 -``` - -This monoid can now be used as _context bound_ in the following `combineAll` method: - -```scala -def combineAll[T: Monoid](xs: List[T]): T = - xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) -``` - -To get rid of the `summon[...]` we can define a `Monoid` object as follows: - -```scala -object Monoid: - def apply[T](using m: Monoid[T]) = m -``` - -Which would allow to re-write the `combineAll` method this way: - -```scala -def combineAll[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_.combine(_)) -``` - -### Functors - -A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements. -We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument. -Therefore we write it `F[_]`, hinting that the type `F` takes another type as argument. -The definition of a generic `Functor` would thus be written as: - -```scala -trait Functor[F[_]]: - def map[A, B](x: F[A], f: A => B): F[B] -``` - -Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of function `f` with type `A => B`". We call the `Functor` definition here a _type class_. -This way, we could define an instance of `Functor` for the `List` type: - -```scala -given Functor[List] with - def map[A, B](x: List[A], f: A => B): List[B] = - x.map(f) // List already has a `map` method -``` - -With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used. - -For instance, we may write such a testing method: - -```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = - assert(expected == summon[Functor[F]].map(original, mapping)) -``` - -And use it this way, for example: - -```scala -assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") -``` - -That's a first step, but in practice we probably would like the `map` function to be a method directly accessible on the type `F`. So that we can call `map` directly on instances of `F`, and get rid of the `summon[Functor[F]]` part. -As in the previous example of Monoids, [`extension` methods](extension-methods.html) help achieving that. Let's re-define the `Functor` type class with extension methods. - -```scala -trait Functor[F[_]]: - extension [A](x: F[A]) - def map[B](f: A => B): F[B] -``` - -The instance of `Functor` for `List` now becomes: - -```scala -given Functor[List] with - extension [A](xs: List[A]) - def map[B](f: A => B): List[B] = - xs.map(f) // List already has a `map` method - -``` - -It simplifies the `assertTransformation` method: - -```scala -def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = - assert(expected == original.map(mapping)) -``` - -The `map` method is now directly used on `original`. It is available as an extension method -since `original`'s type is `F[A]` and a given instance for `Functor[F[A]]` which defines `map` -is in scope. - -### Monads - -Applying `map` in `Functor[List]` to a mapping function of type `A => B` results in a `List[B]`. So applying it to a mapping function of type `A => List[B]` results in a `List[List[B]]`. To avoid managing lists of lists, we may want to "flatten" the values in a single list. - -That's where `Monad` comes in. A `Monad` for type `F[_]` is a `Functor[F]` with two more operations: - -* `flatMap`, which turns an `F[A]` into an `F[B]` when given a function of type `A => F[B]`, -* `pure`, which creates an `F[A]` from a single value `A`. - -Here is the translation of this definition in Scala 3: - -```scala -trait Monad[F[_]] extends Functor[F]: - - /** The unit value for a monad */ - def pure[A](x: A): F[A] - - extension [A](x: F[A]) - /** The fundamental composition operation */ - def flatMap[B](f: A => F[B]): F[B] - - /** The `map` operation can now be defined in terms of `flatMap` */ - def map[B](f: A => B) = x.flatMap(f.andThen(pure)) - -end Monad -``` - -#### List - -A `List` can be turned into a monad via this `given` instance: - -```scala -given listMonad: Monad[List] with - def pure[A](x: A): List[A] = - List(x) - extension [A](xs: List[A]) - def flatMap[B](f: A => List[B]): List[B] = - xs.flatMap(f) // rely on the existing `flatMap` method of `List` -``` - -Since `Monad` is a subtype of `Functor`, `List` is also a functor. The Functor's `map` -operation is already provided by the `Monad` trait, so the instance does not need to define -it explicitly. - -#### Option - -`Option` is an other type having the same kind of behaviour: - -```scala -given optionMonad: Monad[Option] with - def pure[A](x: A): Option[A] = - Option(x) - extension [A](xo: Option[A]) - def flatMap[B](f: A => Option[B]): Option[B] = xo match - case Some(x) => f(x) - case None => None -``` - -#### Reader - -Another example of a `Monad` is the _Reader_ Monad, which acts on functions instead of -data types like `List` or `Option`. It can be used to combine multiple functions -that all need the same parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc. - -Let's define a `Config` type, and two functions using it: - -```scala -trait Config -// ... -def compute(i: Int)(config: Config): String = ??? -def show(str: String)(config: Config): Unit = ??? -``` - -We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation, and we'd like to use -a monad to avoid passing the parameter explicitly multiple times. So postulating -the right `flatMap` operation, we could write: - -```scala -def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show) -``` - -instead of - -```scala -show(compute(i)(config))(config) -``` - -Let's define this m then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`. - -```scala -type ConfigDependent[Result] = Config => Result -``` - -The monad instance will look like this: - -```scala -given configDependentMonad: Monad[ConfigDependent] with - - def pure[A](x: A): ConfigDependent[A] = - config => x - - extension [A](x: ConfigDependent[A]) - def flatMap[B](f: A => ConfigDependent[B]): ConfigDependent[B] = - config => f(x(config))(config) - -end configDependentMonad -``` - -The type `ConfigDependent` can be written using [type lambdas](../new-types/type-lambdas.html): - -```scala -type ConfigDependent = [Result] =>> Config => Result -``` - -Using this syntax would turn the previous `configDependentMonad` into: - -```scala -given configDependentMonad: Monad[[Result] =>> Config => Result] with - - def pure[A](x: A): Config => A = - config => x - - extension [A](x: Config => A) - def flatMap[B](f: A => Config => B): Config => B = - config => f(x(config))(config) - -end configDependentMonad -``` - -It is likely that we would like to use this pattern with other kinds of environments than our `Config` trait. The Reader monad allows us to abstract away `Config` as a type _parameter_, named `Ctx` in the following definition: - -```scala -given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] with - - def pure[A](x: A): Ctx => A = - ctx => x - - extension [A](x: Ctx => A) - def flatMap[B](f: A => Ctx => B): Ctx => B = - ctx => f(x(ctx))(ctx) - -end readerMonad -``` - -### Summary - -The definition of a _type class_ is expressed with a parameterised type with abstract members, such as a `trait`. -The main difference between subtype polymorphism and ad-hoc polymorphism with _type classes_ is how the definition of the _type class_ is implemented, in relation to the type it acts upon. -In the case of a _type class_, its implementation for a concrete type is expressed through a `given` instance definition, which is supplied as an implicit argument alongside the value it acts upon. With subtype polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. The type class solution -takes more effort to set up, but is more extensible: Adding a new interface to a -class requires changing the source code of that class. But contrast, instances for type classes can be defined anywhere. - -To conclude, we have seen that traits and given instances, combined with other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _type classes_. diff --git a/_scala3-reference/contextual/using-clauses.md b/_scala3-reference/contextual/using-clauses.md deleted file mode 100644 index b233c84667..0000000000 --- a/_scala3-reference/contextual/using-clauses.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: "Using Clauses" -type: section -num: 16 -previous-page: /scala3/reference/contextual/givens -next-page: /scala3/reference/contextual/context-bounds ---- - -Functional programming tends to express most dependencies as simple function parameterization. -This is clean and powerful, but it sometimes leads to functions that take many parameters where the same value is passed over and over again in long call chains to many -functions. Context parameters can help here since they enable the compiler to synthesize -repetitive arguments instead of the programmer having to write them explicitly. - -For example, with the [given instances](./givens.html) defined previously, -a `max` function that works for any arguments for which an ordering exists can be defined as follows: - -```scala -def max[T](x: T, y: T)(using ord: Ord[T]): T = - if ord.compare(x, y) < 0 then y else x -``` - -Here, `ord` is a _context parameter_ introduced with a `using` clause. -The `max` function can be applied as follows: - -```scala -max(2, 3)(using intOrd) -``` - -The `(using intOrd)` part passes `intOrd` as an argument for the `ord` parameter. But the point of context parameters is that this argument can also be left out (and it usually is). So the following applications are equally valid: - -```scala -max(2, 3) -max(List(1, 2, 3), Nil) -``` - -## Anonymous Context Parameters - -In many situations, the name of a context parameter need not be -mentioned explicitly at all, since it is used only in synthesized arguments for -other context parameters. In that case one can avoid defining a parameter name -and just provide its type. Example: - -```scala -def maximum[T](xs: List[T])(using Ord[T]): T = - xs.reduceLeft(max) -``` - -`maximum` takes a context parameter of type `Ord[T]` only to pass it on as an -inferred argument to `max`. The name of the parameter is left out. - -Generally, context parameters may be defined either as a full parameter list `(p_1: T_1, ..., p_n: T_n)` or just as a sequence of types `T_1, ..., T_n`. Vararg parameters are not supported in `using` clauses. - -## Class Context Parameters - -If a class context parameter is made a member by adding a `val` or `var` modifier, -then that member is available as a given instance. - -Compare the following examples, where the attempt to supply an explicit `given` member induces an ambiguity: - -```scala -class GivenIntBox(using val givenInt: Int): - def n = summon[Int] - -class GivenIntBox2(using givenInt: Int): - given Int = givenInt - //def n = summon[Int] // ambiguous -``` - -The `given` member is importable as explained in the section on [importing `given`s](./given-imports.html): - -```scala -val b = GivenIntBox(using 23) -import b.given -summon[Int] // 23 - -import b.* -//givenInt // Not found -``` - -## Inferring Complex Arguments - -Here are two other methods that have a context parameter of type `Ord[T]`: - -```scala -def descending[T](using asc: Ord[T]): Ord[T] = new Ord[T]: - def compare(x: T, y: T) = asc.compare(y, x) - -def minimum[T](xs: List[T])(using Ord[T]) = - maximum(xs)(using descending) -``` - -The `minimum` method's right-hand side passes `descending` as an explicit argument to `maximum(xs)`. -With this setup, the following calls are all well-formed, and they all normalize to the last one: - -```scala -minimum(xs) -maximum(xs)(using descending) -maximum(xs)(using descending(using listOrd)) -maximum(xs)(using descending(using listOrd(using intOrd))) -``` - -## Multiple `using` Clauses - -There can be several `using` clauses in a definition and `using` clauses can be freely mixed with normal parameter clauses. Example: - -```scala -def f(u: Universe)(using ctx: u.Context)(using s: ctx.Symbol, k: ctx.Kind) = ... -``` - -Multiple `using` clauses are matched left-to-right in applications. Example: - -```scala -object global extends Universe { type Context = ... } -given ctx : global.Context with { type Symbol = ...; type Kind = ... } -given sym : ctx.Symbol -given kind: ctx.Kind - -``` -Then the following calls are all valid (and normalize to the last one) - -```scala -f(global) -f(global)(using ctx) -f(global)(using ctx)(using sym, kind) -``` - -But `f(global)(using sym, kind)` would give a type error. - - -## Summoning Instances - -The method `summon` in `Predef` returns the given of a specific type. For example, -the given instance for `Ord[List[Int]]` is produced by - -```scala -summon[Ord[List[Int]]] // reduces to listOrd(using intOrd) -``` - -The `summon` method is simply defined as the (non-widening) identity function over a context parameter. - -```scala -def summon[T](using x: T): x.type = x -``` - -## Syntax - -Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../syntax.html). `using` is a soft keyword, recognized only at the start of a parameter or argument list. It can be used as a normal identifier everywhere else. - -``` -ClsParamClause ::= ... | UsingClsParamClause -DefParamClauses ::= ... | UsingParamClause -UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ -UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ -ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ -``` diff --git a/_scala3-reference/dropped-features.md b/_scala3-reference/dropped-features.md deleted file mode 100644 index fdffc69542..0000000000 --- a/_scala3-reference/dropped-features.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Dropped Features -type: chapter -num: 70 -previous-page: /scala3/reference/changed-features/main-functions -next-page: /scala3/reference/dropped-features/delayed-init ---- - -The following pages document the features of Scala 2 that have been dropped in Scala 3. diff --git a/_scala3-reference/dropped-features/auto-apply.md b/_scala3-reference/dropped-features/auto-apply.md deleted file mode 100644 index 8670850f79..0000000000 --- a/_scala3-reference/dropped-features/auto-apply.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: "Dropped: Auto-Application" -type: section -num: 83 -previous-page: /scala3/reference/dropped-features/symlits -next-page: /scala3/reference/dropped-features/weak-conformance ---- - -Previously an empty argument list `()` was implicitly inserted when -calling a nullary method without arguments. Example: - -```scala -def next(): T = ... -next // is expanded to next() -``` - -In Scala 3, this idiom is an error. - -```scala -next -^ -missing arguments for method next -``` - -In Scala 3, the application syntax has to follow exactly the parameter -syntax. Excluded from this rule are methods that are defined in Java -or that override methods defined in Java. The reason for being more -lenient with such methods is that otherwise everyone would have to -write - -```scala -xs.toString().length() -``` - -instead of - -```scala -xs.toString.length -``` - -The latter is idiomatic Scala because it conforms to the _uniform -access principle_. This principle states that one should be able to -change an object member from a field to a non-side-effecting method -and back without affecting clients that access the -member. Consequently, Scala encourages to define such "property" -methods without a `()` parameter list whereas side-effecting methods -should be defined with it. Methods defined in Java cannot make this -distinction; for them a `()` is always mandatory. So Scala fixes the -problem on the client side, by allowing the parameterless references. -But where Scala allows that freedom for all method references, Scala 3 -restricts it to references of external methods that are not defined -themselves in Scala 3. - -For reasons of backwards compatibility, Scala 3 for the moment also -auto-inserts `()` for nullary methods that are defined in Scala 2, or -that override a method defined in Scala 2. It turns out that, because -the correspondence between definition and call was not enforced in -Scala so far, there are quite a few method definitions in Scala 2 -libraries that use `()` in an inconsistent way. For instance, we -find in `scala.math.Numeric` - -```scala -def toInt(): Int -``` - -whereas `toInt` is written without parameters everywhere -else. Enforcing strict parameter correspondence for references to -such methods would project the inconsistencies to client code, which -is undesirable. So Scala 3 opts for more leniency when type-checking -references to such methods until most core libraries in Scala 2 have -been cleaned up. - -Stricter conformance rules also apply to overriding of nullary -methods. It is no longer allowed to override a parameterless method -by a nullary method or _vice versa_. Instead, both methods must agree -exactly in their parameter lists. - -```scala -class A: - def next(): Int - -class B extends A: - def next: Int // overriding error: incompatible type -``` - -Methods overriding Java or Scala 2 methods are again exempted from this -requirement. - -### Migrating code - -Existing Scala code with inconsistent parameters can still be compiled -in Scala 3 under `-source 3.0-migration`. When paired with the `-rewrite` -option, the code will be automatically rewritten to conform to Scala 3's -stricter checking. - -### Reference - -For more information, see [Issue #2570](https://github.com/lampepfl/dotty/issues/2570) and [PR #2716](https://github.com/lampepfl/dotty/pull/2716). diff --git a/_scala3-reference/dropped-features/class-shadowing-spec.md b/_scala3-reference/dropped-features/class-shadowing-spec.md deleted file mode 100644 index d1d5d4b34b..0000000000 --- a/_scala3-reference/dropped-features/class-shadowing-spec.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Dropped: Class Shadowing - More Details" ---- - -Spec diff: in section [5.1.4 Overriding](https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#Overriding), add *M' must not be a class*. - -> Why do we want to make this change to the language? - -Class shadowing is irregular compared to other types of overrides. Indeed, inner classes are not actually overridden but simply shadowed. - - -> How much existing code is going to be affected? - -From all the code compiled so far with Scala 3 the only instance of this I could find is in the stdlib. Looking at [this commit](https://github.com/lampepfl/scala/commit/68f13bf39979b631ed211ec1751934306ceb5d6c#diff-7aa508b70e055b47c823764e3e5646b8) it seems like the usage of class shadowing was accidental. - - -> How exactly is existing code going to be affected? - -Code that relies on overridden inner classes will stop compiling. - - -> Is this change going to be migratable automatically? - -No. diff --git a/_scala3-reference/dropped-features/class-shadowing.md b/_scala3-reference/dropped-features/class-shadowing.md deleted file mode 100644 index b3a4ab2dc1..0000000000 --- a/_scala3-reference/dropped-features/class-shadowing.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: "Dropped: Class Shadowing" -type: section -num: 79 -previous-page: /scala3/reference/dropped-features/early-initializers -next-page: /scala3/reference/dropped-features/limit22 ---- - -Scala 2 so far allowed patterns like this: - -```scala -class Base { - class Ops { ... } -} - -class Sub extends Base { - class Ops { ... } -} -``` - -Scala 3 rejects this with the error message: - -```scala -6 | class Ops { } - | ^ - |class Ops cannot have the same name as class Ops in class Base - | -- class definitions cannot be overridden -``` - -The issue is that the two `Ops` classes _look_ like one overrides the -other, but classes in Scala 2 cannot be overridden. To keep things clean -(and its internal operations consistent) the Scala 3 compiler forces you -to rename the inner classes so that their names are different. - -[More details](./class-shadowing-spec.html) diff --git a/_scala3-reference/dropped-features/delayed-init.md b/_scala3-reference/dropped-features/delayed-init.md deleted file mode 100644 index 5673db5dfb..0000000000 --- a/_scala3-reference/dropped-features/delayed-init.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Dropped: DelayedInit" -type: section -num: 71 -previous-page: /scala3/reference/dropped-features -next-page: /scala3/reference/dropped-features/macros ---- - -The special handling of the `DelayedInit` trait is no longer supported. - -One consequence is that the `App` class, which used `DelayedInit` is -now partially broken. You can still use `App` as a simple way to set up a main program. Example: - -```scala -object HelloWorld extends App { - println("Hello, world!") -} -``` - -However, the code is now run in the initializer of the object, which on -some JVM's means that it will only be interpreted. So, better not use it -for benchmarking! Also, if you want to access the command line arguments, -you need to use an explicit `main` method for that. - -```scala -object Hello: - def main(args: Array[String]) = - println(s"Hello, ${args(0)}") -``` - -On the other hand, Scala 3 offers a convenient alternative to such "program" objects -with [`@main` methods](../changed-features/main-functions.html). diff --git a/_scala3-reference/dropped-features/do-while.md b/_scala3-reference/dropped-features/do-while.md deleted file mode 100644 index 3fd59430ac..0000000000 --- a/_scala3-reference/dropped-features/do-while.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Dropped: Do-While" -type: section -num: 75 -previous-page: /scala3/reference/dropped-features/type-projection -next-page: /scala3/reference/dropped-features/procedure-syntax ---- - -The syntax construct -```scala -do while -``` -is no longer supported. Instead, it is recommended to use the equivalent `while` loop -below: -```scala -while ({ ; }) () -``` -For instance, instead of -```scala -do - i += 1 -while (f(i) == 0) -``` -one writes -```scala -while - i += 1 - f(i) == 0 -do () -``` -The idea to use a block as the condition of a while also gives a solution -to the "loop-and-a-half" problem. Here is another example: -```scala -while - val x: Int = iterator.next - x >= 0 -do print(".") -``` - -### Why Drop The Construct? - - - `do-while` is used relatively rarely and it can be expressed faithfully using just `while`. So there seems to be little point in having it as a separate syntax construct. - - Under the [new syntax rules](../other-new-features/control-syntax.html) `do` is used as a statement continuation, which would clash with its meaning as a statement introduction. diff --git a/_scala3-reference/dropped-features/early-initializers.md b/_scala3-reference/dropped-features/early-initializers.md deleted file mode 100644 index 18d97640b1..0000000000 --- a/_scala3-reference/dropped-features/early-initializers.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "Dropped: Early Initializers" -type: section -num: 78 -previous-page: /scala3/reference/dropped-features/package-objects -next-page: /scala3/reference/dropped-features/class-shadowing ---- - -Early initializers of the form - -```scala -class C extends { ... } with SuperClass ... -``` - -have been dropped. They were rarely used, and mostly to compensate for the lack of -[trait parameters](../other-new-features/trait-parameters.html), which are now directly supported in Scala 3. - -For more information, see [SLS §5.1.6](https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#early-definitions). diff --git a/_scala3-reference/dropped-features/existential-types.md b/_scala3-reference/dropped-features/existential-types.md deleted file mode 100644 index df624cbd07..0000000000 --- a/_scala3-reference/dropped-features/existential-types.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "Dropped: Existential Types" -type: section -num: 73 -previous-page: /scala3/reference/dropped-features/macros -next-page: /scala3/reference/dropped-features/type-projection ---- - -Existential types using `forSome` (as in -[SLS §3.2.12](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#existential-types)) -have been dropped. The reasons for dropping them are: - - - Existential types violate a type soundness principle on which DOT - and Scala 3 are constructed. That principle says that every - prefix (`p`, respectvely `S`) of a type selection `p.T` or `S#T` - must either come from a value constructed at runtime or refer to a - type that is known to have only good bounds. - - - Existential types create many difficult feature interactions - with other Scala constructs. - - - Existential types largely overlap with path-dependent types, - so the gain of having them is relatively minor. - -Existential types that can be expressed using only wildcards (but not -`forSome`) are still supported, but are treated as refined types. -For instance, the type -```scala -Map[_ <: AnyRef, Int] -``` -is treated as the type `Map`, where the first type parameter -is upper-bounded by `AnyRef` and the second type parameter is an alias -of `Int`. - -When reading class files compiled with Scala 2, Scala 3 will do a best -effort to approximate existential types with its own types. It will -issue a warning that a precise emulation is not possible. diff --git a/_scala3-reference/dropped-features/limit22.md b/_scala3-reference/dropped-features/limit22.md deleted file mode 100644 index 2e21efec58..0000000000 --- a/_scala3-reference/dropped-features/limit22.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Dropped: Limit 22" -type: section -num: 80 -previous-page: /scala3/reference/dropped-features/class-shadowing -next-page: /scala3/reference/dropped-features/xml ---- - -The limits of 22 for the maximal number of parameters of function types and the -maximal number of fields in tuple types have been dropped. - -* Functions can now have an arbitrary number of parameters. Functions beyond - [`scala.Function22`](https://www.scala-lang.org/api/current/scala/Function22.html) are erased to a new trait [`scala.runtime.FunctionXXL`](https://scala-lang.org/api/3.x/scala/runtime/FunctionXXL.html). - -* Tuples can also have an arbitrary number of fields. Tuples beyond [`scala.Tuple22`](https://www.scala-lang.org/api/current/scala/Tuple22.html) - are erased to a new class [`scala.runtime.TupleXXL`](https://scala-lang.org/api/3.x/scala/runtime/TupleXXL.html) (which extends the trait [`scala.Product`](https://scala-lang.org/api/3.x/scala/Product.html)). Furthermore, they support generic - operation such as concatenation and indexing. - -Both of these are implemented using arrays. diff --git a/_scala3-reference/dropped-features/macros.md b/_scala3-reference/dropped-features/macros.md deleted file mode 100644 index f7a23edb00..0000000000 --- a/_scala3-reference/dropped-features/macros.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "Dropped: Scala 2 Macros" -type: section -num: 72 -previous-page: /scala3/reference/dropped-features/delayed-init -next-page: /scala3/reference/dropped-features/existential-types ---- - -The previous, experimental macro system has been dropped. - -Instead, there is a cleaner, more restricted system based on two complementary concepts: `inline` and `'{ ... }`/`${ ... }` code generation. -`'{ ... }` delays the compilation of the code and produces an object containing the code, dually `${ ... }` evaluates an expression which produces code and inserts it in the surrounding `${ ... }`. -In this setting, a definition marked as inlined containing a `${ ... }` is a macro, the code inside the `${ ... }` is executed at compile-time and produces code in the form of `'{ ... }`. -Additionally, the contents of code can be inspected and created with a more complex reflection API as an extension of `'{ ... }`/`${ ... }` framework. - -* `inline` has been [implemented](../metaprogramming/inline.html) in Scala 3. -* Quotes `'{ ... }` and splices `${ ... }` has been [implemented](../metaprogramming/macros.html) in Scala 3. -* [TASTy reflect](../metaprogramming/reflection.html) provides more complex tree based APIs to inspect or create quoted code. diff --git a/_scala3-reference/dropped-features/nonlocal-returns.md b/_scala3-reference/dropped-features/nonlocal-returns.md deleted file mode 100644 index 89b244f122..0000000000 --- a/_scala3-reference/dropped-features/nonlocal-returns.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Deprecated: Nonlocal Returns" ---- - -Returning from nested anonymous functions has been deprecated. - -Nonlocal returns are implemented by throwing and catching `scala.runtime.NonLocalReturnException`-s. This is rarely what is intended by the programmer. It can be problematic because of the hidden performance cost of throwing and catching exceptions. Furthermore, it is a leaky implementation: a catch-all exception handler can intercept a `NonLocalReturnException`. - -A drop-in library replacement is provided in [`scala.util.control.NonLocalReturns`](https://scala-lang.org/api/3.x/scala/util/control/NonLocalReturns$.html). Example: - -```scala -import scala.util.control.NonLocalReturns.* - -extension [T](xs: List[T]) - def has(elem: T): Boolean = returning { - for x <- xs do - if x == elem then throwReturn(true) - false - } - -@main def test(): Unit = - val xs = List(1, 2, 3, 4, 5) - assert(xs.has(2) == xs.contains(2)) -``` diff --git a/_scala3-reference/dropped-features/package-objects.md b/_scala3-reference/dropped-features/package-objects.md deleted file mode 100644 index 35e21d56a2..0000000000 --- a/_scala3-reference/dropped-features/package-objects.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Dropped: Package Objects" -type: section -num: 77 -previous-page: /scala3/reference/dropped-features/procedure-syntax -next-page: /scala3/reference/dropped-features/early-initializers ---- - -Package objects -```scala -package object p { - val a = ... - def b = ... -} -``` -will be dropped. They are still available in Scala 3.0, but will be deprecated and removed afterwards. - -Package objects are no longer needed since all kinds of definitions can now be written at the top-level. Example: -```scala -package p -type Labelled[T] = (String, T) -val a: Labelled[Int] = ("count", 1) -def b = a._2 - -case class C() - -extension (x: C) def pair(y: C) = (x, y) -``` -There may be several source files in a package containing such top-level definitions, and source files can freely mix top-level value, method, and type definitions with classes and objects. - -The compiler generates synthetic objects that wrap top-level definitions falling into one of the following categories: - - - all pattern, value, method, and type definitions, - - implicit classes and objects, - - companion objects of opaque type aliases. - -If a source file `src.scala` contains such top-level definitions, they will be put in a synthetic object named `src$package`. The wrapping is transparent, however. The definitions in `src` can still be accessed as members of the enclosing package. - -**Note:** This means that -1. The name of a source file containing wrapped top-level definitions is relevant for binary compatibility. If the name changes, so does the name of the generated object and its class. - -2. A top-level main method `def main(args: Array[String]): Unit = ...` is wrapped as any other method. If it appears -in a source file `src.scala`, it could be invoked from the command line using a command like `scala src$package`. Since the -"program name" is mangled it is recommended to always put `main` methods in explicitly named objects. - -3. The notion of `private` is independent of whether a definition is wrapped or not. A `private` top-level definition is always visible from everywhere in the enclosing package. - -4. If several top-level definitions are overloaded variants with the same name, -they must all come from the same source file. diff --git a/_scala3-reference/dropped-features/procedure-syntax.md b/_scala3-reference/dropped-features/procedure-syntax.md deleted file mode 100644 index f3bf8cc9d4..0000000000 --- a/_scala3-reference/dropped-features/procedure-syntax.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: "Dropped: Procedure Syntax" -type: section -num: 76 -previous-page: /scala3/reference/dropped-features/do-while -next-page: /scala3/reference/dropped-features/package-objects ---- - -Procedure syntax -```scala -def f() { ... } -``` -has been dropped. You need to write one of the following instead: -```scala -def f() = { ... } -def f(): Unit = { ... } -``` -Scala 3 accepts the old syntax under the `-source:3.0-migration` option. -If the `-migration` option is set, it can even rewrite old syntax to new. -The [Scalafix](https://scalacenter.github.io/scalafix/) tool also -can rewrite procedure syntax to make it Scala 3 compatible. diff --git a/_scala3-reference/dropped-features/symlits.md b/_scala3-reference/dropped-features/symlits.md deleted file mode 100644 index 3cb7243af9..0000000000 --- a/_scala3-reference/dropped-features/symlits.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Dropped: Symbol Literals" -type: section -num: 82 -previous-page: /scala3/reference/dropped-features/xml -next-page: /scala3/reference/dropped-features/auto-apply ---- - -Symbol literals are no longer supported. - -The `scala.Symbol` class still exists, so a -literal translation of the symbol literal `'xyz` is `Symbol("xyz")`. However, it is recommended to use a plain string literal `"xyz"` instead. (The `Symbol` class will be deprecated and removed in the future). diff --git a/_scala3-reference/dropped-features/this-qualifier.md b/_scala3-reference/dropped-features/this-qualifier.md deleted file mode 100644 index c25f896415..0000000000 --- a/_scala3-reference/dropped-features/this-qualifier.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: "Dropped: private[this] and protected[this]" -type: section -num: 85 -previous-page: /scala3/reference/dropped-features/weak-conformance -next-page: /scala3/reference/dropped-features/wildcard-init ---- - -The `private[this]` and `protected[this]` access modifiers are deprecated and will be phased out. - -Previously, these modifiers were needed for - - - avoiding the generation of getters and setters - - excluding code under a `private[this]` from variance checks. (Scala 2 also excludes `protected[this]` but this was found to be unsound and was therefore removed). - -The compiler now infers for `private` members the fact that they are only accessed via `this`. Such members are treated as if they had been declared `private[this]`. `protected[this]` is dropped without a replacement. - diff --git a/_scala3-reference/dropped-features/type-projection.md b/_scala3-reference/dropped-features/type-projection.md deleted file mode 100644 index fb87ef79be..0000000000 --- a/_scala3-reference/dropped-features/type-projection.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: "Dropped: General Type Projection" -type: section -num: 74 -previous-page: /scala3/reference/dropped-features/existential-types -next-page: /scala3/reference/dropped-features/do-while ---- - -Scala so far allowed general type projection `T#A` where `T` is an arbitrary type -and `A` names a type member of `T`. - -Scala 3 disallows this if `T` is an abstract type (class types and type aliases -are fine). This change was made because unrestricted type projection -is [unsound](https://github.com/lampepfl/dotty/issues/1050). - -This restriction rules out the [type-level encoding of a combinator -calculus](https://michid.wordpress.com/2010/01/29/scala-type-level-encoding-of-the-ski-calculus/). - -To rewrite code using type projections on abstract types, consider using -path-dependent types or implicit parameters. diff --git a/_scala3-reference/dropped-features/weak-conformance-spec.md b/_scala3-reference/dropped-features/weak-conformance-spec.md deleted file mode 100644 index 147298f557..0000000000 --- a/_scala3-reference/dropped-features/weak-conformance-spec.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Dropped: Weak Conformance - More Details" ---- - -To simplify the underlying type theory, Scala 3 drops the notion of -[*weak conformance*](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#weak-conformance) -altogether. Instead, it provides more flexibility when -assigning a type to a constant expression. The new rule is: - - - *If* a list of expressions `Es` appears as one of - - - the elements of a vararg parameter, or - - the alternatives of an if-then-else or match expression, or - - the body and catch results of a try expression, - -- *and* all expressions have primitive numeric types, but they do not - all have the same type, - -- *then* the following is attempted: - - - the expressions `Es` are partitioned into `Int` constants on the - one hand, and all other expressions on the other hand, - - if all the other expressions have the same numeric type `T` - (which can be one of `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, - `Double`), possibly after widening, and if none of the `Int` - literals would incur a loss of precision when converted to `T`, - then they are thus converted (the other expressions are left - unchanged regardless), - - otherwise, the expressions `Es` are used unchanged. - - A loss of precision occurs for - - an `Int -> Float` conversion of a constant - `c` if `c.toFloat.toInt != c` - - an `Int -> Byte` conversion of a constant - `c` if `c.toByte.toInt != c`, - - an `Int -> Short` conversion of a constant - `c` if `c.toShort.toInt != c`. - -### Examples - -```scala -inline val b = 33 -def f(): Int = b + 1 -Array(b, 33, 5.5) : Array[Double] // b is an inline val -Array(f(), 33, 5.5) : Array[AnyVal] // f() is not a constant -Array(5, 11L) : Array[Long] -Array(5, 11L, 5.5) : Array[AnyVal] // Long and Double found -Array(1.0f, 2) : Array[Float] -Array(1.0f, 1234567890): Array[AnyVal] // loss of precision -Array(b, 33, 'a') : Array[Char] -Array(5.toByte, 11) : Array[Byte] -``` diff --git a/_scala3-reference/dropped-features/weak-conformance.md b/_scala3-reference/dropped-features/weak-conformance.md deleted file mode 100644 index deca70b607..0000000000 --- a/_scala3-reference/dropped-features/weak-conformance.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Dropped: Weak Conformance" -type: section -num: 84 -previous-page: /scala3/reference/dropped-features/auto-apply -next-page: /scala3/reference/dropped-features/this-qualifier ---- - -In some situations, Scala used a _weak conformance_ relation when -testing type compatibility or computing the least upper bound of a set -of types. The principal motivation behind weak conformance was to -make an expression like this have type `List[Double]`: - -```scala -List(1.0, math.sqrt(3.0), 0, -3.3) // : List[Double] -``` - -It's "obvious" that this should be a `List[Double]`. However, without -some special provision, the least upper bound of the lists's element -types `(Double, Double, Int, Double)` would be `AnyVal`, hence the list -expression would be given type `List[AnyVal]`. - -A less obvious example is the following one, which was also typed as a -`List[Double]`, using the weak conformance relation. - -```scala -val n: Int = 3 -val c: Char = 'X' -val d: Double = math.sqrt(3.0) -List(n, c, d) // used to be: List[Double], now: List[AnyVal] -``` - -Here, it is less clear why the type should be widened to -`List[Double]`, a `List[AnyVal]` seems to be an equally valid -- and -more principled -- choice. - -Weak conformance applies to all "numeric" types (including `Char`), and -independently of whether the expressions are literals or not. However, -in hindsight, the only intended use case is for *integer literals* to -be adapted to the type of the other expressions. Other types of numerics -have an explicit type annotation embedded in their syntax (`f`, `d`, -`.`, `L` or `'` for `Char`s) which ensures that their author really -meant them to have that specific type). - -Therefore, Scala 3 drops the general notion of weak conformance, and -instead keeps one rule: `Int` literals are adapted to other numeric -types if necessary. - -[More details](weak-conformance-spec.html) diff --git a/_scala3-reference/dropped-features/wildcard-init.md b/_scala3-reference/dropped-features/wildcard-init.md deleted file mode 100644 index be633fb1ca..0000000000 --- a/_scala3-reference/dropped-features/wildcard-init.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Dropped: Wildcard Initializer" -type: section -num: 86 -previous-page: /scala3/reference/dropped-features/this-qualifier -next-page: /scala3/reference/syntax ---- - -The syntax - -```scala - var x: A = _ -``` - -that was used to indicate an uninitialized field, has been dropped. -At its place there is a special value `uninitialized` in the `scala.compiletime` package. -To get an uninitialized field, you now write - -```scala -import scala.compiletime.uninitialized - -var x: A = uninitialized -``` - -To enable cross-compilation, `_` is still supported, but it will be dropped in a future 3.x version. diff --git a/_scala3-reference/dropped-features/xml.md b/_scala3-reference/dropped-features/xml.md deleted file mode 100644 index cab3d0663a..0000000000 --- a/_scala3-reference/dropped-features/xml.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Dropped: XML Literals" -type: section -num: 81 -previous-page: /scala3/reference/dropped-features/limit22 -next-page: /scala3/reference/dropped-features/symlits ---- - -XML Literals are still supported, but will be dropped in the near future, to -be replaced with [XML string interpolation](https://github.com/lampepfl/xml-interpolator): - -```scala -import dotty.xml.interpolator.* - -case class Person(name: String) { override def toString = name } - -@main def test: Unit = - val bill = Person("Bill") - val john = Person("John") - val mike = Person("Mike") - val todoList = List( - (bill, john, "Meeting", "Room 203, 11:00am"), - (john, mike, "Holiday", "March 22-24") - ) - // XML literals (to be dropped) - val mails1 = for (from, to, heading, body) <- todoList yield - - {from}{to} - {heading}{body} - - println(mails1) - // XML string interpolation - val mails2 = for (from, to, heading, body) <- todoList yield xml""" - - ${from}${to} - ${heading}${body} - """ - println(mails2) -``` - -For more information, see the semester project [XML String Interpolator for Dotty](https://infoscience.epfl.ch/record/267527) by Yassin Kammoun (2019). diff --git a/_scala3-reference/enums.md b/_scala3-reference/enums.md deleted file mode 100644 index e8c63c2377..0000000000 --- a/_scala3-reference/enums.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Enums" -type: chapter -num: 9 -previous-page: /scala3/reference/new-types/polymorphic-function-types -next-page: /scala3/reference/enums/enums ---- - -This chapter documents enums in Scala 3. diff --git a/_scala3-reference/enums/adts.md b/_scala3-reference/enums/adts.md deleted file mode 100644 index 10558707b5..0000000000 --- a/_scala3-reference/enums/adts.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: "Algebraic Data Types" -type: section -num: 11 -previous-page: /scala3/reference/enums/enums -next-page: /scala3/reference/enums/desugarEnums ---- - -The [`enum` concept](./enums.html) is general enough to also support algebraic data -types (ADTs) and their generalized version (GADTs). Here is an example -how an `Option` type can be represented as an ADT: - -```scala -enum Option[+T]: - case Some(x: T) - case None -``` - -This example introduces an `Option` enum with a covariant type -parameter `T` consisting of two cases, `Some` and `None`. `Some` is -parameterized with a value parameter `x`. It is a shorthand for writing a -case class that extends `Option`. Since `None` is not parameterized, it -is treated as a normal enum value. - -The `extends` clauses that were omitted in the example above can also -be given explicitly: - -```scala -enum Option[+T]: - case Some(x: T) extends Option[T] - case None extends Option[Nothing] -``` - -Note that the parent type of the `None` value is inferred as -`Option[Nothing]`. Generally, all covariant type parameters of the enum -class are minimized in a compiler-generated `extends` clause whereas all -contravariant type parameters are maximized. If `Option` was non-variant, -you would need to give the extends clause of `None` explicitly. - -As for normal enum values, the cases of an `enum` are all defined in -the `enum`s companion object. So it's `Option.Some` and `Option.None` -unless the definitions are "pulled out" with an import: - -```scala -scala> Option.Some("hello") -val res1: t2.Option[String] = Some(hello) - -scala> Option.None -val res2: t2.Option[Nothing] = None -``` - -Note that the type of the expressions above is always `Option`. Generally, the type of a enum case constructor application will be widened to the underlying enum type, unless a more specific type is expected. This is a subtle difference with respect to normal case classes. The classes making up the cases do exist, and can be unveiled, either by constructing them directly with a `new`, or by explicitly providing an expected type. - -```scala -scala> new Option.Some(2) -val res3: Option.Some[Int] = Some(2) -scala> val x: Option.Some[Int] = Option.Some(3) -val res4: Option.Some[Int] = Some(3) -``` - -As all other enums, ADTs can define methods. For instance, here is `Option` again, with an -`isDefined` method and an `Option(...)` constructor in its companion object. - -```scala -enum Option[+T]: - case Some(x: T) - case None - - def isDefined: Boolean = this match - case None => false - case _ => true - -object Option: - - def apply[T >: Null](x: T): Option[T] = - if x == null then None else Some(x) - -end Option -``` - -Enumerations and ADTs have been presented as two different -concepts. But since they share the same syntactic construct, they can -be seen simply as two ends of a spectrum and it is perfectly possible -to construct hybrids. For instance, the code below gives an -implementation of `Color` either with three enum values or with a -parameterized case that takes an RGB value. - -```scala -enum Color(val rgb: Int): - case Red extends Color(0xFF0000) - case Green extends Color(0x00FF00) - case Blue extends Color(0x0000FF) - case Mix(mix: Int) extends Color(mix) -``` - -### Parameter Variance of Enums - -By default, parameterized cases of enums with type parameters will copy the type parameters of their parent, along -with any variance notations. As usual, it is important to use type parameters carefully when they are variant, as shown -below: - -The following `View` enum has a contravariant type parameter `T` and a single case `Refl`, representing a function -mapping a type `T` to itself: - -```scala -enum View[-T]: - case Refl(f: T => T) -``` - -The definition of `Refl` is incorrect, as it uses contravariant type `T` in the covariant result position of a -function type, leading to the following error: - -```scala --- Error: View.scala:2:12 -------- -2 | case Refl(f: T => T) - | ^^^^^^^^^ - |contravariant type T occurs in covariant position in type T => T of value f - |enum case Refl requires explicit declaration of type T to resolve this issue. -``` - -Because `Refl` does not declare explicit parameters, it looks to the compiler like the following: - -```scala -enum View[-T]: - case Refl[/*synthetic*/-T1](f: T1 => T1) extends View[T1] -``` - -The compiler has inferred for `Refl` the contravariant type parameter `T1`, following `T` in `View`. -We can now clearly see that `Refl` needs to declare its own non-variant type parameter to correctly type `f`, -and can remedy the error by the following change to `Refl`: - -```diff -enum View[-T]: -- case Refl(f: T => T) -+ case Refl[R](f: R => R) extends View[R] -``` - -Above, type `R` is chosen as the parameter for `Refl` to highlight that it has a different meaning to -type `T` in `View`, but any name will do. - -After some further changes, a more complete implementation of `View` can be given as follows and be used -as the function type `T => U`: - -```scala -enum View[-T, +U] extends (T => U): - case Refl[R](f: R => R) extends View[R, R] - - final def apply(t: T): U = this match - case refl: Refl[r] => refl.f(t) -``` - -### Syntax of Enums - -Changes to the syntax fall in two categories: enum definitions and cases inside enums. -The changes are specified below as deltas with respect to the Scala syntax given [here](../syntax.html) - - 1. Enum definitions are defined as follows: - - ``` - TmplDef ::= `enum' EnumDef - EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody - EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ - EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase - ``` - - 2. Cases of enums are defined as follows: - - ``` - EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) - ``` - -### Reference - -For more information, see [Issue #1970](https://github.com/lampepfl/dotty/issues/1970). diff --git a/_scala3-reference/enums/desugarEnums.md b/_scala3-reference/enums/desugarEnums.md deleted file mode 100644 index efcc41d5b2..0000000000 --- a/_scala3-reference/enums/desugarEnums.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -title: "Translation of Enums and ADTs" -type: section -num: 12 -previous-page: /scala3/reference/enums/adts -next-page: /scala3/reference/contextual ---- - -The compiler expands enums and their cases to code that only uses -Scala's other language features. As such, enums in Scala are -convenient _syntactic sugar_, but they are not essential to understand -Scala's core. - -We now explain the expansion of enums in detail. First, -some terminology and notational conventions: - - - We use `E` as a name of an enum, and `C` as a name of a case that appears in `E`. - - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance, - `` represents one or more parameter lists `(...)` or nothing at all. - - - Enum cases fall into three categories: - - - _Class cases_ are those cases that are parameterized, either with a type parameter section `[...]` or with one or more (possibly empty) parameter sections `(...)`. - - _Simple cases_ are cases of a non-generic enum that have neither parameters nor an extends clause or body. That is, they consist of a name only. - - _Value cases_ are all cases that do not have a parameter section but that do have a (possibly generated) `extends` clause and/or a body. - - Simple cases and value cases are collectively called _singleton cases_. - -The desugaring rules imply that class cases are mapped to case classes, and singleton cases are mapped to `val` definitions. - -There are nine desugaring rules. Rule (1) desugars enum definitions. Rules -(2) and (3) desugar simple cases. Rules (4) to (6) define `extends` clauses for cases that -are missing them. Rules (7) to (9) define how such cases with `extends` clauses -map into `case class`es or `val`s. - -1. An `enum` definition - ```scala - enum E ... { } - ``` - expands to a `sealed abstract` class that extends the `scala.reflect.Enum` trait and - an associated companion object that contains the defined cases, expanded according - to rules (2 - 8). The enum class starts with a compiler-generated import that imports - the names `` of all cases so that they can be used without prefix in the class. - ```scala - sealed abstract class E ... extends with scala.reflect.Enum { - import E.{ } - - } - object E { } - ``` - -2. A simple case consisting of a comma-separated list of enum names - ```scala - case C_1, ..., C_n - ``` - expands to - ```scala - case C_1; ...; case C_n - ``` - Any modifiers or annotations on the original case extend to all expanded - cases. - -3. A simple case - ```scala - case C - ``` - of an enum `E` that does not take type parameters expands to - ```scala - val C = $new(n, "C") - ``` - Here, `$new` is a private method that creates an instance of `E` (see - below). - -4. If `E` is an enum with type parameters - ```scala - V1 T1 >: L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) - ``` - where each of the variances `Vi` is either `'+'` or `'-'`, then a simple case - ```scala - case C - ``` - expands to - ```scala - case C extends E[B1, ..., Bn] - ``` - where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. This result is then further - rewritten with rule (8). Simple cases of enums with non-variant type - parameters are not permitted (however value cases with explicit `extends` clause are) - -5. A class case without an extends clause - ```scala - case C - ``` - of an enum `E` that does not take type parameters expands to - ```scala - case C extends E - ``` - This result is then further rewritten with rule (9). - -6. If `E` is an enum with type parameters `Ts`, a class case with neither type parameters nor an extends clause - ```scala - case C - ``` - expands to - ```scala - case C[Ts] extends E[Ts] - ``` - This result is then further rewritten with rule (9). For class cases that have type parameters themselves, an extends clause needs to be given explicitly. - -7. If `E` is an enum with type parameters `Ts`, a class case without type parameters but with an extends clause - ```scala - case C extends - ``` - expands to - ```scala - case C[Ts] extends - ``` - provided at least one of the parameters `Ts` is mentioned in a parameter type in - `` or in a type argument in ``. - -8. A value case - ```scala - case C extends - ``` - expands to a value definition in `E`'s companion object: - ```scala - val C = new { ; def ordinal = n } - ``` - where `n` is the ordinal number of the case in the companion object, - starting from 0. The anonymous class also - implements the abstract `Product` methods that it inherits from `Enum`. - - It is an error if a value case refers to a type parameter of the enclosing `enum` - in a type argument of ``. - -9. A class case - ```scala - case C extends - ``` - expands analogous to a final case class in `E`'s companion object: - ```scala - final case class C extends - ``` - The enum case defines an `ordinal` method of the form - ```scala - def ordinal = n - ``` - where `n` is the ordinal number of the case in the companion object, - starting from 0. - - It is an error if a value case refers to a type parameter of the enclosing `enum` - in a parameter type in `` or in a type argument of ``, unless that parameter is already - a type parameter of the case, i.e. the parameter name is defined in ``. - - The compiler-generated `apply` and `copy` methods of an enum case - ```scala - case C(ps) extends P1, ..., Pn - ``` - are treated specially. A call `C(ts)` of the apply method is ascribed the underlying type - `P1 & ... & Pn` (dropping any [transparent traits](../other-new-features/transparent-traits.html)) - as long as that type is still compatible with the expected type at the point of application. - A call `t.copy(ts)` of `C`'s `copy` method is treated in the same way. - -### Translation of Enums with Singleton Cases - -An enum `E` (possibly generic) that defines one or more singleton cases -will define the following additional synthetic members in its companion object (where `E'` denotes `E` with -any type parameters replaced by wildcards): - - - A method `valueOf(name: String): E'`. It returns the singleton case value whose identifier is `name`. - - A method `values` which returns an `Array[E']` of all singleton case - values defined by `E`, in the order of their definitions. - -If `E` contains at least one simple case, its companion object will define in addition: - - - A private method `$new` which defines a new simple case value with given - ordinal number and name. This method can be thought as being defined as - follows. - - ```scala - private def $new(_$ordinal: Int, $name: String) = - new E with runtime.EnumValue: - def ordinal = _$ordinal - override def productPrefix = $name // if not overridden in `E` - override def toString = $name // if not overridden in `E` - ``` - -The anonymous class also implements the abstract `Product` methods that it inherits from `Enum`. -The `ordinal` method is only generated if the enum does not extend from `java.lang.Enum` (as Scala enums do not extend -`java.lang.Enum`s unless explicitly specified). In case it does, there is no need to generate `ordinal` as -`java.lang.Enum` defines it. Similarly there is no need to override `toString` as that is defined in terms of `name` in -`java.lang.Enum`. Finally, `productPrefix` will call `this.name` when `E` extends `java.lang.Enum`. - -### Scopes for Enum Cases - -A case in an `enum` is treated similarly to a secondary constructor. It can access neither the enclosing `enum` using `this`, nor its value parameters or instance members using simple -identifiers. - -Even though translated enum cases are located in the enum's companion object, referencing -this object or its members via `this` or a simple identifier is also illegal. The compiler typechecks enum cases in the scope of the enclosing companion object but flags any such illegal accesses as errors. - -### Translation of Java-compatible enums - -A Java-compatible enum is an enum that extends `java.lang.Enum`. The translation rules are the same as above, with the reservations defined in this section. - -It is a compile-time error for a Java-compatible enum to have class cases. - -Cases such as `case C` expand to a `@static val` as opposed to a `val`. This allows them to be generated as static fields of the enum type, thus ensuring they are represented the same way as Java enums. - -### Other Rules - -- A normal case class which is not produced from an enum case is not allowed to extend - `scala.reflect.Enum`. This ensures that the only cases of an enum are the ones that are - explicitly declared in it. - -- If an enum case has an `extends` clause, the enum class must be one of the - classes that's extended. diff --git a/_scala3-reference/enums/enums.md b/_scala3-reference/enums/enums.md deleted file mode 100644 index 96d11e8742..0000000000 --- a/_scala3-reference/enums/enums.md +++ /dev/null @@ -1,196 +0,0 @@ ---- -title: "Enumerations" -type: section -num: 10 -previous-page: /scala3/reference/enums -next-page: /scala3/reference/enums/adts ---- - -An enumeration is used to define a type consisting of a set of named values. - -```scala -enum Color: - case Red, Green, Blue -``` - -This defines a new `sealed` class, `Color`, with three values, `Color.Red`, -`Color.Green`, `Color.Blue`. The color values are members of `Color`s -companion object. - -### Parameterized enums - -Enums can be parameterized. - -```scala -enum Color(val rgb: Int): - case Red extends Color(0xFF0000) - case Green extends Color(0x00FF00) - case Blue extends Color(0x0000FF) -``` - -As the example shows, you can define the parameter value by using an -explicit extends clause. - -### Methods defined for enums - -The values of an enum correspond to unique integers. The integer -associated with an enum value is returned by its `ordinal` method: - -```scala -scala> val red = Color.Red -val red: Color = Red -scala> red.ordinal -val res0: Int = 0 -``` - -The companion object of an enum also defines three utility methods. -The `valueOf` method obtains an enum value -by its name. The `values` method returns all enum values -defined in an enumeration in an `Array`. The `fromOrdinal` -method obtains an enum value from its ordinal (`Int`) value. - -```scala -scala> Color.valueOf("Blue") -val res0: Color = Blue -scala> Color.values -val res1: Array[Color] = Array(Red, Green, Blue) -scala> Color.fromOrdinal(0) -val res2: Color = Red -``` - -### User-defined members of enums - -It is possible to add your own definitions to an enum. Example: - -```scala -enum Planet(mass: Double, radius: Double): - private final val G = 6.67300E-11 - def surfaceGravity = G * mass / (radius * radius) - def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity - - case Mercury extends Planet(3.303e+23, 2.4397e6) - case Venus extends Planet(4.869e+24, 6.0518e6) - case Earth extends Planet(5.976e+24, 6.37814e6) - case Mars extends Planet(6.421e+23, 3.3972e6) - case Jupiter extends Planet(1.9e+27, 7.1492e7) - case Saturn extends Planet(5.688e+26, 6.0268e7) - case Uranus extends Planet(8.686e+25, 2.5559e7) - case Neptune extends Planet(1.024e+26, 2.4746e7) -end Planet -``` - -It is also possible to define an explicit companion object for an enum: - -```scala -object Planet: - def main(args: Array[String]) = - val earthWeight = args(0).toDouble - val mass = earthWeight / Earth.surfaceGravity - for p <- values do - println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -end Planet -``` - -### Deprecation of Enum Cases - -As a library author, you may want to signal that an enum case is no longer intended for use. However you could still want to gracefully handle the removal of a case from your public API, such as special casing deprecated cases. - -To illustrate, say that the `Planet` enum originally had an additional case: - -```diff - enum Planet(mass: Double, radius: Double): - ... - case Neptune extends Planet(1.024e+26, 2.4746e7) -+ case Pluto extends Planet(1.309e+22, 1.1883e3) - end Planet -``` - -We now want to deprecate the `Pluto` case. First we add the `scala.deprecated` annotation to `Pluto`: - -```diff - enum Planet(mass: Double, radius: Double): - ... - case Neptune extends Planet(1.024e+26, 2.4746e7) -- case Pluto extends Planet(1.309e+22, 1.1883e3) -+ -+ @deprecated("refer to IAU definition of planet") -+ case Pluto extends Planet(1.309e+22, 1.1883e3) - end Planet -``` - -Outside the lexical scopes of `enum Planet` or `object Planet`, references to `Planet.Pluto` will produce a deprecation warning, but within those scopes we can still reference it to implement introspection over the deprecated cases: - -```scala -trait Deprecations[T <: reflect.Enum] { - extension (t: T) def isDeprecatedCase: Boolean -} - -object Planet { - given Deprecations[Planet] with { - extension (p: Planet) - def isDeprecatedCase = p == Pluto - } -} -``` - -We could imagine that a library may use [type class derivation](../contextual/derivation.html) to automatically provide an instance for `Deprecations`. - -### Compatibility with Java Enums - -If you want to use the Scala-defined enums as [Java enums](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html), you can do so by extending -the class `java.lang.Enum`, which is imported by default, as follows: - -```scala -enum Color extends Enum[Color] { case Red, Green, Blue } -``` - -The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should be the same as the type of the enum. -There is no need to provide constructor arguments (as defined in the Java API docs) to `java.lang.Enum` when extending it – the compiler will generate them automatically. - -After defining `Color` like that, you can use it like you would a Java enum: - -```scala -scala> Color.Red.compareTo(Color.Green) -val res15: Int = -1 -``` - -For a more in-depth example of using Scala 3 enums from Java, see [this test](https://github.com/lampepfl/dotty/tree/master/tests/run/enum-java). In the test, the enums are defined in the `MainScala.scala` file and used from a Java source, `Test.java`. - -### Implementation - -Enums are represented as `sealed` classes that extend the `scala.reflect.Enum` trait. -This trait defines a single public method, `ordinal`: - -```scala -package scala.reflect - -/** A base trait of all Scala enum definitions */ -transparent trait Enum extends Any, Product, Serializable: - - /** A number uniquely identifying a case of an enum */ - def ordinal: Int -``` - -Enum values with `extends` clauses get expanded to anonymous class instances. -For instance, the `Venus` value above would be defined like this: - -```scala -val Venus: Planet = new Planet(4.869E24, 6051800.0): - def ordinal: Int = 1 - override def productPrefix: String = "Venus" - override def toString: String = "Venus" -``` - -Enum values without `extends` clauses all share a single implementation -that can be instantiated using a private method that takes a tag and a name as arguments. -For instance, the first -definition of value `Color.Red` above would expand to: - -```scala -val Red: Color = $new(0, "Red") -``` - -### Reference - -For more information, see [Issue #1970](https://github.com/lampepfl/dotty/issues/1970) and -[PR #4003](https://github.com/lampepfl/dotty/pull/4003). diff --git a/_scala3-reference/experimental/canthrow.md b/_scala3-reference/experimental/canthrow.md deleted file mode 100644 index f25a869467..0000000000 --- a/_scala3-reference/experimental/canthrow.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: CanThrow Abilities -author: Martin Odersky ---- - -This page describes experimental support for exception checking in Scala 3. It is enabled by the language import -```scala -import language.experimental.saferExceptions -``` -The reason for publishing this extension now is to get feedback on its usability. We are working on more advanced type systems that build on the general ideas put forward in the extension. Those type systems have application areas beyond checked exceptions. Exception checking is a useful starting point since exceptions are familiar to all Scala programmers and their current treatment leaves room for improvement. - -## Why Exceptions? - -Exceptions are an ideal mechanism for error handling in many situations. They serve the intended purpose of propagating error conditions with a minimum of boilerplate. They cause zero overhead for the "happy path", which means they are very efficient as long as errors arise infrequently. Exceptions are also debug friendly, since they produce stack traces that can be inspected at the handler site. So one never has to guess where an erroneous condition originated. - -## Why Not Exceptions? - -However, exceptions in current Scala and many other languages are not reflected in the type system. This means that an essential part of the contract of a function - i.e. what exceptions can it produce? - is not statically checked. Most people acknowledge that this is a problem, but that so far the alternative of checked exceptions was just too painful to be considered. A good example are Java checked exceptions, which do the right thing in principle, but are widely regarded as a mistake since they are so difficult to deal with. So far, none of the successor languages that are modeled after Java or that build on the JVM has copied this feature. See for example Anders Hejlsberg's [statement on why C# does not have checked exceptions](https://www.artima.com/articles/the-trouble-with-checked-exceptions). - -## The Problem With Java's Checked Exceptions - -The main problem with Java's checked exception model is its inflexibility, which is due to lack of polymorphism. Consider for instance the `map` function which is declared on `List[A]` like this: -```scala - def map[B](f: A => B): List[B] -``` -In the Java model, function `f` is not allowed to throw a checked exception. So the following call would be invalid: -```scala - xs.map(x => if x < limit then x * x else throw LimitExceeded()) -``` -The only way around this would be to wrap the checked exception `LimitExceeded` in an unchecked `RuntimeException` that is caught at the callsite and unwrapped again. Something like this: -```scala - try - xs.map(x => if x < limit then x * x else throw Wrapper(LimitExceeded())) - catch case Wrapper(ex) => throw ex -``` -Ugh! No wonder checked exceptions in Java are not very popular. - -## Monadic Effects - -So the dilemma is that exceptions are easy to use only as long as we forgo static type checking. This has caused many people working with Scala to abandon exceptions altogether and to use an error monad like `Either` instead. This can work in many situations but is not without its downsides either. It makes code a lot more complicated and harder to refactor. It means one is quickly confronted with the problem how to work with several monads. In general, dealing with one monad at a time in Scala is straightforward but dealing with several monads together is much less pleasant since monads don't compose. A great number of techniques have been proposed, implemented, and promoted to deal with this, from monad transformers, to free monads, to tagless final. But none of these techniques is universally liked; each introduces a complicated DSL that's hard to understand for non-experts, introduces runtime overheads, and makes debugging difficult. In the end, quite a few developers prefer to work instead with a single "super-monad" like ZIO that has error propagation built in alongside other aspects. This one-size fits all approach can work very nicely, even though (or is it because?) it represents an all-encompassing framework. - -However, a programming language is not a framework; it has to cater also for those applications that do not fit the framework's use cases. So there's still a strong motivation for getting exception checking right. - -## From Effects To Abilities - -Why does `map` work so poorly with Java's checked exception model? It's because -`map`'s signature limits function arguments to not throw checked exceptions. We could try to come up with a more polymorphic formulation of `map`. For instance, it could look like this: -```scala - def map[B, E](f: A => B canThrow E): List[B] canThrow E -``` -This assumes a type `A canThrow E` to indicate computations of type `A` that can throw an exception of type `E`. But in practice the overhead of the additional type parameters makes this approach unappealing as well. Note in particular that we'd have to parameterize _every method_ that takes a function argument that way, so the added overhead of declaring all these exception types looks just like a sort of ceremony we would like to avoid. - -But there is a way to avoid the ceremony. Instead of concentrating on possible _effects_ such as "this code might throw an exception", concentrate on _capabilities_ such as "this code needs the capability to throw an exception". From a standpoint of expressiveness this is quite similar. But capabilities can be expressed as parameters whereas traditionally effects are expressed as some addition to result values. It turns out that this can make a big difference! - -Going to the root of the word _capability_, it means "being _able_ to do something", so the "cap" prefix is really just a filler. Following Conor McBride, we will use the name _ability_ from now on. - -## The CanThrow Ability - -In the _effects as abilities_ model, an effect is expressed as an (implicit) parameter of a certain type. For exceptions we would expect parameters of type -`CanThrow[E]` where `E` stands for the exception that can be thrown. Here is the definition of `CanThrow`: -```scala -erased class CanThrow[-E <: Exception] -``` -This shows another experimental Scala feature: [erased definitions]({% link _scala3-reference/experimental/erased-defs.md %}). Roughly speaking, values of an erased class do not generate runtime code; they are erased before code generation. This means that all `CanThrow` abilities are compile-time only artifacts; they do not have a runtime footprint. - -Now, if the compiler sees a `throw Exc()` construct where `Exc` is a checked exception, it will check that there is an ability of type `CanThrow[Exc]` that can be summoned as a given. It's a compile-time error if that's not the case. - -How can the ability be produced? There are several possibilities: - -Most often, the ability is produced by having a using clause `(using CanThrow[Exc])` in some enclosing scope. This roughly corresponds to a `throws` clause -in Java. The analogy is even stronger since alongside `CanThrow` there is also the following type alias defined in the `scala` package: -```scala -infix type canThrow[R, +E <: Exception] = CanThrow[E] ?=> R -``` -That is, `R canThrow E` is a context function type that takes an implicit `CanThrow[E]` parameter and that returns a value of type `R`. Therefore, a method written like this: -```scala -def m(x: T)(using CanThrow[E]): U -``` -can alternatively be expressed like this: -```scala -def m(x: T): U canThrow E -``` -_Aside_: If we rename `canThrow` to `throws` we would have a perfect analogy with Java but unfortunately `throws` is already taken in Scala 2.13. - -The `CanThrow`/`canThrow` combo essentially propagates the `CanThrow` requirement outwards. But where are these abilities created in the first place? That's in the `try` expression. Given a `try` like this: - -```scala -try - body -catch - case ex1: Ex1 => handler1 - ... - case exN: ExN => handlerN -``` -the compiler generates abilities for `CanThrow[Ex1]`, ..., `CanThrow[ExN]` that are in scope as givens in `body`. It does this by augmenting the `try` roughly as follows: -```scala -try - erased given CanThrow[Ex1] = ??? - ... - erased given CanThrow[ExN] = ??? - body -catch ... -``` -Note that the right-hand side of all givens is `???` (undefined). This is OK since -these givens are erased; they will not be executed at runtime. - -## An Example - -That's it. Let's see it in action in an example. First, add an import -```scala -import language.experimental.saferExceptions -``` -to enable exception checking. Now, define an exception `LimitExceeded` and -a function `f` like this: -```scala -val limit = 10e9 -class LimitExceeded extends Exception -def f(x: Double): Double = - if x < limit then x * x else throw LimitExceeded() -``` -You'll get this error message: -``` -9 | if x < limit then x * x else throw LimitExceeded() - | ^^^^^^^^^^^^^^^^^^^^^ - |The ability to throw exception LimitExceeded is missing. - |The ability can be provided by one of the following: - | - A using clause `(using CanThrow[LimitExceeded])` - | - A `canThrow` clause in a result type such as `X canThrow LimitExceeded` - | - an enclosing `try` that catches LimitExceeded - | - |The following import might fix the problem: - | - | import unsafeExceptions.canThrowAny -``` -As the error message implies, you have to declare that `f` needs the ability to throw a `LimitExceeded` exception. The most concise way to do so is to add a `canThrow` clause: -```scala -def f(x: Double): Double canThrow LimitExceeded = - if x < limit then x * x else throw LimitExceeded() -``` -Now put a call to `f` in a `try` that catches `LimitExceeded`: -```scala -@main def test(xs: Double*) = - try println(xs.map(f).sum) - catch case ex: LimitExceeded => println("too large") -``` -Run the program with some inputs: -``` -> scala test 1 2 3 -14.0 -> scala test -0.0 -> scala test 1 2 3 100000000000 -too large -``` -Everything typechecks and works as expected. But wait - we have called `map` without any ceremony! How did that work? Here's how the compiler expands the `test` function: -```scala -// compiler-generated code -@main def test(xs: Double*) = - try - erased given ctl: CanThrow[LimitExceeded] = ??? - println(xs.map(x => f(x)(using ctl)).sum) - catch case ex: LimitExceeded => println("too large") -``` -The `CanThrow[LimitExceeded]` ability is passed in a synthesized `using` clause to `f`, since `f` requires it. Then the resulting closure is passed to `map`. The signature of `map` does not have to account for effects. It takes a closure as always, but that -closure may refer to abilities in its free variables. This means that `map` is -already effect polymorphic even though we did not change its signature at all. -So the takeaway is that the effects as abilities model naturally provides for effect polymorphism whereas this is something that other approaches struggle with. - -## Gradual Typing Via Imports - -Another advantage is that the model allows a gradual migration from current unchecked exceptions to safer exceptions. Imagine for a moment that `experimental.saferExceptions` is turned on everywhere. There would be lots of code that breaks since functions have not yet been properly annotated with `canThrow`. But it's easy to create an escape hatch that lets us ignore the breakages for a while: simply add the import -```scala -import scala.unsafeExceptions.canThrowAny -``` -This will provide the `CanThrow` ability for any exception, and thereby allow -all throws and all other calls, no matter what the current state of `canThrow` declarations is. Here's the -definition of `canThrowAny`: -```scala -package scala -object unsafeExceptions: - given canThrowAny: CanThrow[Exception] = ??? -``` -Of course, defining a global ability like this amounts to cheating. But the cheating is useful for gradual typing. The import could be used to migrate existing code, or to -enable more fluid explorations of code without regard for complete exception safety. At the end of these migrations or explorations the import should be removed. - -## Scope Of the Extension - -To summarize, the extension for safer exception checking consists of the following elements: - - - It adds to the standard library the class `scala.CanThrow`, the type `scala.canThrow`, and the `scala.unsafeExceptions` object, as they were described above. - - It augments the type checking of `throw` by _demanding_ a `CanThrow` ability or the thrown exception. - - It augments the type checking of `try` by _providing_ `CanThrow` abilities for every caught exception. - -That's all. It's quite remarkable that one can do exception checking in this way without any special additions to the type system. We just need regular givens and context functions. Any runtime overhead is eliminated using `erased`. - -## Caveats - -Our ability model allows to declare and check the thrown exceptions of first-order code. But as it stands, it does not give us enough mechanism to enforce the _absence_ of -abilities for arguments to higher-order functions. Consider a variant `pureMap` -of `map` that should enforce that its argument does not throw exceptions or have any other effects (maybe because wants to reorder computations transparently). Right now -we cannot enforce that since the function argument to `pureMap` can capture arbitrary -abilities in its free variables without them showing up in its type. One possible way to -address this would be to introduce a pure function type (maybe written `A -> B`). Pure functions are not allowed to close over abilities. Then `pureMap` could be written -like this: -``` - def pureMap(f: A -> B): List[B] -``` -Another area where the lack of purity requirements shows up is when abilities escape from bounded scopes. Consider the following function -```scala -def escaped(xs: Double*): () => Int = - try () => xs.map(f).sum - catch case ex: LimitExceeded => -1 -``` -With the system presented here, this function typechecks, with expansion -```scala -// compiler-generated code -def escaped(xs: Double*): () => Int = - try - given ctl: CanThrow[LimitExceeded] = ??? - () => xs.map(x => f(x)(using ctl)).sum - catch case ex: LimitExceeded => -1 -``` -But if you try to call `escaped` like this -```scala -val g = escaped(1, 2, 1000000000) -g() -``` -the result will be a `LimitExceeded` exception thrown at the second line where `g` is called. What's missing is that `try` should enforce that the abilities it generates do not escape as free variables in the result of its body. It makes sense to describe such scoped effects as _ephemeral abilities_ - they have lifetimes that cannot be extended to delayed code in a lambda. - - -# Outlook - -We are working on a new class of type system that supports ephemeral abilities by tracking the free variables of values. Once that research matures, it will hopefully be possible to augment the language so that we can enforce the missing properties. - -And it would have many other applications besides: Exceptions are a special case of _algebraic effects_, which has been a very active research area over the last 20 years and is finding its way into programming languages (e.g. Koka, Eff, Multicore OCaml, Unison). In fact, algebraic effects have been characterized as being equivalent to exceptions with an additional _resume_ operation. The techniques developed here for exceptions can probably be generalized to other classes of algebraic effects. - -But even without these additional mechanisms, exception checking is already useful as it is. It gives a clear path forward to make code that uses exceptions safer, better documented, and easier to refactor. The only loophole arises for scoped abilities - here we have to verify manually that these abilities do not escape. Specifically, a `try` always has to be placed in the same computation stage as the throws that it enables. - -Put another way: If the status quo is 0% static checking since 100% is too painful, then an alternative that gives you 95% static checking with great ergonomics looks like a win. And we might still get to 100% in the future. - diff --git a/_scala3-reference/experimental/erased-defs-spec.md b/_scala3-reference/experimental/erased-defs-spec.md deleted file mode 100644 index 7905e4776f..0000000000 --- a/_scala3-reference/experimental/erased-defs-spec.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Erased Definitions: More Details" ---- - -TODO: complete -## Rules - -1. `erased` is a soft modifier. It can appear: - * At the start of a parameter block of a method, function or class - * In a method definition - * In a `val` definition (but not `lazy val` or `var`) - * In a `class` or `trait` definition - - ```scala - erased val x = ... - erased def f = ... - - def g(erased x: Int) = ... - - (erased x: Int) => ... - def h(x: (erased Int) => Int) = ... - - class K(erased x: Int) { ... } - erased class E {} - ``` - - -2. A reference to an `erased` val or def can only be used - * Inside the expression of argument to an `erased` parameter - * Inside the body of an `erased` `val` or `def` - - -3. Functions - * `(erased x1: T1, x2: T2, ..., xN: TN) => y : (erased T1, T2, ..., TN) => R` - * `(given erased x1: T1, x2: T2, ..., xN: TN) => y: (given erased T1, T2, ..., TN) => R` - * `(given erased T1) => R <:< erased T1 => R` - * `(given erased T1, T2) => R <:< (erased T1, T2) => R` - * ... - - Note that there is no subtype relation between `(erased T) => R` and `T => R` (or `(given erased T) => R` and `(given T) => R`) - - -4. Eta expansion - - if `def f(erased x: T): U` then `f: (erased T) => U`. - - -5. Erasure semantics - * All `erased` parameters are removed from the function - * All argument to `erased` parameters are not passed to the function - * All `erased` definitions are removed - * All `(erased T1, T2, ..., TN) => R` and `(given erased T1, T2, ..., TN) => R` become `() => R` - - -6. Overloading - - Method with `erased` parameters will follow the normal overloading constraints after erasure. - - -7. Overriding - * Member definitions overriding each other must both be `erased` or not be `erased` - * `def foo(x: T): U` cannot be overridden by `def foo(erased x: T): U` and vice-versa - diff --git a/_scala3-reference/experimental/erased-defs.md b/_scala3-reference/experimental/erased-defs.md deleted file mode 100644 index 07191a819c..0000000000 --- a/_scala3-reference/experimental/erased-defs.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Erased Definitions" ---- - -`erased` is a modifier that expresses that some definition or expression is erased by the compiler instead of being represented in the compiled output. It is not yet part of the Scala language standard. To enable `erased`, turn on the language feature -`experimental.erasedDefinitions`. This can be done with a language import -```scala -import scala.language.experimental.erasedDefinitions -``` -or by setting the command line option `-language:experimental.erasedDefinitions`. -## Why erased terms? - -Let's describe the motivation behind erased terms with an example. In the -following we show a simple state machine which can be in a state `On` or `Off`. -The machine can change state from `Off` to `On` with `turnedOn` only if it is -currently `Off`. This last constraint is captured with the `IsOff[S]` contextual -evidence which only exists for `IsOff[Off]`. For example, not allowing calling -`turnedOn` on in an `On` state as we would require an evidence of type -`IsOff[On]` that will not be found. - -```scala -sealed trait State -final class On extends State -final class Off extends State - -@implicitNotFound("State must be Off") -class IsOff[S <: State] -object IsOff: - given isOff: IsOff[Off] = new IsOff[Off] - -class Machine[S <: State]: - def turnedOn(using IsOff[S]): Machine[On] = new Machine[On] - -val m = new Machine[Off] -m.turnedOn -m.turnedOn.turnedOn // ERROR -// ^ -// State must be Off -``` - -Note that in the code above the actual context arguments for `IsOff` are never -used at runtime; they serve only to establish the right constraints at compile -time. As these terms are never used at runtime there is not real need to have -them around, but they still need to be present in some form in the generated -code to be able to do separate compilation and retain binary compatibility. We -introduce _erased terms_ to overcome this limitation: we are able to enforce the -right constrains on terms at compile time. These terms have no run time -semantics and they are completely erased. - -## How to define erased terms? - -Parameters of methods and functions can be declared as erased, placing `erased` -in front of a parameter list (like `given`). - -```scala -def methodWithErasedEv(erased ev: Ev): Int = 42 - -val lambdaWithErasedEv: erased Ev => Int = - (erased ev: Ev) => 42 -``` - -`erased` parameters will not be usable for computations, though they can be used -as arguments to other `erased` parameters. - -```scala -def methodWithErasedInt1(erased i: Int): Int = - i + 42 // ERROR: can not use i - -def methodWithErasedInt2(erased i: Int): Int = - methodWithErasedInt1(i) // OK -``` - -Not only parameters can be marked as erased, `val` and `def` can also be marked -with `erased`. These will also only be usable as arguments to `erased` -parameters. - -```scala -erased val erasedEvidence: Ev = ... -methodWithErasedEv(erasedEvidence) -``` - -## What happens with erased values at runtime? - -As `erased` are guaranteed not to be used in computations, they can and will be -erased. - -```scala -// becomes def methodWithErasedEv(): Int at runtime -def methodWithErasedEv(erased ev: Ev): Int = ... - -def evidence1: Ev = ... -erased def erasedEvidence2: Ev = ... // does not exist at runtime -erased val erasedEvidence3: Ev = ... // does not exist at runtime - -// evidence1 is not evaluated and no value is passed to methodWithErasedEv -methodWithErasedEv(evidence1) -``` - -## State machine with erased evidence example - -The following example is an extended implementation of a simple state machine -which can be in a state `On` or `Off`. The machine can change state from `Off` -to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to -`Off` with `turnedOff` only if it is currently `On`. These last constraint are -captured with the `IsOff[S]` and `IsOn[S]` given evidence only exist for -`IsOff[Off]` and `IsOn[On]`. For example, not allowing calling `turnedOff` on in -an `Off` state as we would require an evidence `IsOn[Off]` that will not be -found. - -As the given evidences of `turnedOn` and `turnedOff` are not used in the -bodies of those functions we can mark them as `erased`. This will remove the -evidence parameters at runtime, but we would still evaluate the `isOn` and -`isOff` givens that were found as arguments. As `isOn` and `isOff` are not -used except as `erased` arguments, we can mark them as `erased`, hence removing -the evaluation of the `isOn` and `isOff` evidences. - -```scala -import scala.annotation.implicitNotFound - -sealed trait State -final class On extends State -final class Off extends State - -@implicitNotFound("State must be Off") -class IsOff[S <: State] -object IsOff: - // will not be called at runtime for turnedOn, the - // compiler will only require that this evidence exists - given IsOff[Off] = new IsOff[Off] - -@implicitNotFound("State must be On") -class IsOn[S <: State] -object IsOn: - // will not exist at runtime, the compiler will only - // require that this evidence exists at compile time - erased given IsOn[On] = new IsOn[On] - -class Machine[S <: State] private (): - // ev will disappear from both functions - def turnedOn(using erased ev: IsOff[S]): Machine[On] = new Machine[On] - def turnedOff(using erased ev: IsOn[S]): Machine[Off] = new Machine[Off] - -object Machine: - def newMachine(): Machine[Off] = new Machine[Off] - -@main def test = - val m = Machine.newMachine() - m.turnedOn - m.turnedOn.turnedOff - - // m.turnedOff - // ^ - // State must be On - - // m.turnedOn.turnedOn - // ^ - // State must be Off -``` - -Note that in [Inline](../metaprogramming/inline.html) we discussed `erasedValue` and inline -matches. `erasedValue` is implemented with `erased`, so the state machine above -can be encoded as follows: - -```scala -import scala.compiletime.* - -sealed trait State -final class On extends State -final class Off extends State - -class Machine[S <: State]: - transparent inline def turnOn(): Machine[On] = - inline erasedValue[S] match - case _: Off => new Machine[On] - case _: On => error("Turning on an already turned on machine") - - transparent inline def turnOff(): Machine[Off] = - inline erasedValue[S] match - case _: On => new Machine[Off] - case _: Off => error("Turning off an already turned off machine") - -object Machine: - def newMachine(): Machine[Off] = - println("newMachine") - new Machine[Off] -end Machine - -@main def test = - val m = Machine.newMachine() - m.turnOn() - m.turnOn().turnOff() - m.turnOn().turnOn() // error: Turning on an already turned on machine -``` - -## Erased Classes - -`erased` can also be used as a modifier for a class. An erased class is intended to be used only in erased definitions. If the type of a val definition or parameter is -a (possibly aliased, refined, or instantiated) erased class, the definition is assumed to be `erased` itself. Likewise, a method with an erased class return type is assumed to be `erased` itself. Since given instances expand to vals and defs, they are also assumed to be erased if the type they produce is an erased class. Finally -function types with erased classes as arguments turn into erased function types. - -Example: -```scala -erased class CanRead - -val x: CanRead = ... // `x` is turned into an erased val -val y: CanRead => Int = ... // the function is turned into an erased function -def f(x: CanRead) = ... // `f` takes an erased parameter -def g(): CanRead = ... // `g` is turned into an erased def -given CanRead = ... // the anonymous given is assumed to be erased -``` -The code above expands to -```scala -erased class CanRead - -erased val x: CanRead = ... -val y: (erased CanRead) => Int = ... -def f(erased x: CanRead) = ... -erased def g(): CanRead = ... -erased given CanRead = ... -``` -After erasure, it is checked that no references to values of erased classes remain and that no instances of erased classes are created. So the following would be an error: -```scala -val err: Any = CanRead() // error: illegal reference to erased class CanRead -``` -Here, the type of `err` is `Any`, so `err` is not considered erased. Yet its initializing value is a reference to the erased class `CanRead`. - -[More Details](./erased-defs-spec.html) diff --git a/_scala3-reference/features-classification.md b/_scala3-reference/features-classification.md deleted file mode 100644 index 528b2ffccd..0000000000 --- a/_scala3-reference/features-classification.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "A Classification of Proposed Language Features" -date: April 6, 2019 -author: Martin Odersky ---- - -This document provides an overview of the constructs proposed for Scala 3 with the aim to facilitate the discussion what to include and when to include it. It classifies features into eight groups: (1) essential foundations, (2) simplifications, (3) restrictions, (4) dropped features, (5) changed features, (6) new features, (7) features oriented towards metaprogramming with the aim to replace existing macros, and (8) changes to type checking and inference. - -Each group contains sections classifying the status (i.e. relative importance to be a part of Scala 3, and relative urgency when to decide this) and the migration cost -of the constructs in it. - -The current document reflects the state of things as of April, 2019. It will be updated to reflect any future changes in that status. - -## Essential Foundations - -These new constructs directly model core features of [DOT](https://www.scala-lang.org/blog/2016/02/03/essence-of-scala.html), higher-kinded types, and the [SI calculus for implicit resolution](https://infoscience.epfl.ch/record/229878/files/simplicitly_1.pdf). - - - [Intersection types](new-types/intersection-types.html), replacing compound types, - - [Union types](new-types/union-types.html), - - [Type lambdas](new-types/type-lambdas.html), - replacing encodings using structural types and type projection. - - [Context functions](contextual/context-functions.html) offering abstraction over given parameters. - -**Status: essential** - -These are essential core features of Scala 3. Without them, Scala 3 would be a completely different language, with different foundations. - -**Migration cost: none to low** - -Since these are additions, there's generally no migration cost for old code. An exception are intersection types which replace compound types with slightly cleaned-up semantics. But few programs would be affected by this change. - -## Simplifications - -These constructs replace existing constructs with the aim of making the language safer and simpler to use, and to promote uniformity in code style. - - - [Trait parameters](other-new-features/trait-parameters.html) replace [early initializers](dropped-features/early-initializers.html) with a more generally useful construct. - - [Given instances](contextual/givens.html) - replace implicit objects and defs, focussing on intent over mechanism. - - [Using clauses](contextual/using-clauses.html) replace implicit parameters, avoiding their ambiguities. - - [Extension methods](contextual/extension-methods.html) replace implicit classes with a clearer and simpler mechanism. - - [Opaque type aliases](other-new-features/opaques.html) replace most uses - of value classes while guaranteeing absence of boxing. - - [Top-level definitions](dropped-features/package-objects.html) replace package objects, dropping syntactic boilerplate. - - [Export clauses](other-new-features/export.html) - provide a simple and general way to express aggregation, which can replace the - previous facade pattern of package objects inheriting from classes. - - [Vararg splices](changed-features/vararg-splices.html) now use the form `*` instead of `@ _*`, mirroring vararg expressions, - - [Creator applications](other-new-features/creator-applications.html) allow using simple function call syntax - instead of `new` expressions. `new` expressions stay around as a fallback for - the cases where creator applications cannot be used. - -With the exception of early initializers and old-style vararg splices, all superseded constructs continue to be available in Scala 3.0. The plan is to deprecate and phase them out later. - -Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by project Valhalla. - -**Status: bimodal: now or never / can delay** - -These are essential simplifications. If we decide to adopt them, we should do it for 3.0. Otherwise we are faced with the awkward situation that the Scala 3 documentation has to describe an old feature that will be replaced or superseded by a simpler one in the future. - -On the other hand, we need to decide now only about the new features in this list. The decision to drop the superseded features can be delayed. Of course, adopting a new feature without deciding to drop the superseded feature will make the language larger. - -**Migration cost: moderate** - -For the next several versions, old features will remain available and deprecation and rewrite techniques can make any migration effort low and gradual. - - -## Restrictions - -These constructs are restricted to make the language safer. - - - [Implicit Conversions](contextual/conversions.html): there is only one way to define implicit conversions instead of many, and potentially surprising implicit conversions require a language import. - - [Given Imports](contextual/given-imports.html): implicits now require a special form of import, to make the import clearly visible. - - [Type Projection](dropped-features/type-projection.html): only classes can be used as prefix `C` of a type projection `C#A`. Type projection on abstract types is no longer supported since it is unsound. - - [Multiversal equality](contextual/multiversal-equality.html) implements an "opt-in" scheme to rule out nonsensical comparisons with `==` and `!=`. - - [infix](https://github.com/lampepfl/dotty/pull/5975) - makes method application syntax uniform across code bases. - -Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-source 3.0-migration`. - -**Status: now or never** - -These are essential restrictions. If we decide to adopt them, we should do it for 3.0. Otherwise we are faced with the awkward situation that the Scala 3 documentation has to describe a feature that will be restricted in the future. - -**Migration cost: low to high** - - - _low_: multiversal equality rules out code that is nonsensical, so any rewrites required by its adoption should be classified as bug fixes. - - _moderate_: Restrictions to implicits can be accommodated by straightforward rewriting. - - _high_: Unrestricted type projection cannot always rewritten directly since it is unsound in general. - -## Dropped Constructs - -These constructs are proposed to be dropped without a new construct replacing them. The motivation for dropping these constructs is to simplify the language and its implementation. - - - [DelayedInit](dropped-features/delayed-init.html), - - [Existential types](dropped-features/existential-types.html), - - [Procedure syntax](dropped-features/procedure-syntax.html), - - [Class shadowing](dropped-features/class-shadowing.html), - - [XML literals](dropped-features/xml.html), - - [Symbol literals](dropped-features/symlits.html), - - [Auto application](dropped-features/auto-apply.html), - - [Weak conformance](dropped-features/weak-conformance.html), - - [Compound types](new-types/intersection-types.html), - - [Auto tupling](https://github.com/lampepfl/dotty/pull/4311) (implemented, but not merged). - -The date when these constructs are dropped varies. The current status is: - - - Not implemented at all: - - DelayedInit, existential types, weak conformance. - - Supported under `-source 3.0-migration`: - - procedure syntax, class shadowing, symbol literals, auto application, auto tupling in a restricted form. - - Supported in 3.0, to be deprecated and phased out later: - - XML literals, compound types. - -**Status: mixed** - -Currently unimplemented features would require considerable implementation effort which would in most cases make the compiler more buggy and fragile and harder to understand. If we do not decide to drop them, they will probably show up as "not yet implemented" in the Scala 3.0 release. - -Currently implemented features could stay around indefinitely. Updated docs may simply ignore them, in the expectation that they might go away eventually. So the decision about their removal can be delayed. - -**Migration cost: moderate to high** - -Dropped features require rewrites to avoid their use in programs. These rewrites can sometimes be automatic (e.g. for procedure syntax, symbol literals, auto application) -and sometimes need to be manual (e.g. class shadowing, auto tupling). Sometimes the rewrites would have to be non-local, affecting use sites as well as definition sites (e.g., in the case of `DelayedInit`, unless we find a solution). - -## Changes - -These constructs have undergone changes to make them more regular and useful. - - - [Structural Types](changed-features/structural-types.html): They now allow pluggable implementations, which greatly increases their usefulness. Some usage patterns are restricted compared to the status quo. - - [Name-based pattern matching](changed-features/pattern-matching.html): The existing undocumented Scala 2 implementation has been codified in a slightly simplified form. - - [Eta expansion](changed-features/eta-expansion.html) is now performed universally also in the absence of an expected type. The postfix `_` operator is thus made redundant. It will be deprecated and dropped after Scala 3.0. - - [Implicit Resolution](changed-features/implicit-resolution.html): The implicit resolution rules have been cleaned up to make them more useful and less surprising. Implicit scope is restricted to no longer include package prefixes. - -Most aspects of old-style implicit resolution are still available under `-source 3.0-migration`. The other changes in this list are applied unconditionally. - -**Status: strongly advisable** - -The features have been implemented in their new form in Scala 3.0's compiler. They provide clear improvements in simplicity and functionality compared to the status quo. Going back would require significant implementation effort for a net loss of functionality. - -**Migration cost: low to high** - -Only a few programs should require changes, but some necessary changes might be non-local (as in the case of restrictions to implicit scope). - -## New Constructs - -These are additions to the language that make it more powerful or pleasant to use. - - - [Enums](enums/enums.html) provide concise syntax for enumerations and [algebraic data types](enums/adts.html). - - [Parameter untupling](other-new-features/parameter-untupling.html) avoids having to use `case` for tupled parameter destructuring. - - [Dependent function types](new-types/dependent-function-types.html) generalize dependent methods to dependent function values and types. - - [Polymorphic function types](https://github.com/lampepfl/dotty/pull/4672) generalize polymorphic methods to dependent function values and types. _Current status_: There is a proposal, and a prototype implementation, but the implementation has not been finalized or merged yet. - - [Kind polymorphism](other-new-features/kind-polymorphism.html) allows the definition of operators working equally on types and type constructors. - -**Status: mixed** - -Enums offer an essential simplification of fundamental use patterns, so they should be adopted for Scala 3.0. Auto-parameter tupling is a very small change that removes some awkwardness, so it might as well be adopted now. The other features constitute more specialized functionality which could be introduced in later versions. On the other hand, except for polymorphic function types they are all fully implemented, so if the Scala 3.0 spec does not include them, they might be still made available under a language flag. - -**Migration cost: none** - -Being new features, existing code migrates without changes. To be sure, sometimes it would be attractive to rewrite code to make use of the new features in order to increase clarity and conciseness. - -## Metaprogramming - -The following constructs together aim to put metaprogramming in Scala on a new basis. So far, metaprogramming was achieved by a combination of macros and libraries such as [Shapeless](https://github.com/milessabin/shapeless) that were in turn based on some key macros. Current Scala 2 macro mechanisms are a thin veneer on top the current Scala 2 compiler, which makes them fragile and in many cases impossible to port to Scala 3. - -It's worth noting that macros were never included in the [Scala 2 language specification](https://scala-lang.org/files/archive/spec/2.13/) and were so far made available only under an `-experimental` flag. This has not prevented their widespread usage. - -To enable porting most uses of macros, we are experimenting with the advanced language constructs listed below. These designs are more provisional than the rest of the proposed language constructs for Scala 3.0. There might still be some changes until the final release. Stabilizing the feature set needed for metaprogramming is our first priority. - -- [Match types](new-types/match-types.html) allow computation on types. -- [Inline](metaprogramming/inline.html) provides -by itself a straightforward implementation of some simple macros and is at the same time an essential building block for the implementation of complex macros. -- [Quotes and splices](metaprogramming/macros.html) provide a principled way to express macros and staging with a unified set of abstractions. -- [Type class derivation](contextual/derivation.html) provides an in-language implementation of the `Gen` macro in Shapeless and other foundational libraries. The new implementation is more robust, efficient and easier to use than the macro. -- [Implicit by-name parameters](contextual/by-name-context-parameters.html) provide a more robust in-language implementation of the `Lazy` macro in Shapeless. - -**Status: not yet settled** - -We know we need a practical replacement for current macros. The features listed above are very promising in that respect, but we need more complete implementations and more use cases to reach a final verdict. - -**Migration cost: very high** - -Existing macro libraries will have to be rewritten from the ground up. In many cases the rewritten libraries will turn out to be simpler and more robust than the old ones, but that does not relieve one of the cost of the rewrites. It's currently unclear to what degree users of macro libraries will be affected. We aim to provide sufficient functionality so that core macros can be re-implemented fully, but given the vast feature set of the various macro extensions to Scala 2 it is difficult to arrive at a workable limitation of scope. - -## Changes to Type Checking and Inference - -The Scala 3 compiler uses a new algorithm for type inference, which relies on a general subtype constraint solver. The new algorithm often [works better than the old](https://contributors.scala-lang.org/t/better-type-inference-for-scala-send-us-your-problematic-cases/2410), but there are inevitably situations where the results of both algorithms differ, leading to errors diagnosed by Scala 3 for programs that the Scala 2 compiler accepts. - -**Status: essential** - -The new type-checking and inference algorithms are the essential core of the new compiler. They cannot be reverted without dropping the whole implementation of Scala 3. - -**Migration cost: high** - -Some existing programs will break and, given the complex nature of type inference, it will not always be clear what change caused the breakage and how to fix it. - -In our experience, macros and changes in type and implicit argument inference together cause the large majority of problems encountered when porting existing code to Scala 3. The latter source of problems could be addressed systematically by a tool that added all inferred types and implicit arguments to a Scala 2 source code file. Most likely such a tool would be implemented as a [Scala 2 compiler plugin](https://docs.scala-lang.org/overviews/plugins/index.html). The resulting code would have a greatly increased likelihood to compile under Scala 3, but would often be bulky to the point of being unreadable. A second part of the rewriting tool should then selectively and iteratively remove type and implicit annotations that were synthesized by the first part as long as they compile under Scala 3. This second part could be implemented as a program that invokes the Scala 3 compiler `scalac` programmatically. - -Several people have proposed such a tool for some time now. I believe it is time we find the will and the resources to actually implement it. diff --git a/_scala3-reference/language-versions.md b/_scala3-reference/language-versions.md deleted file mode 100644 index 46636fb8d2..0000000000 --- a/_scala3-reference/language-versions.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Language Versions -type: chapter -num: 100 -previous-page: /scala3/reference/syntax -next-page: /scala3/reference/language-versions/source-compatibility ---- - -This chapter explains how different versions of the language interact with each other regarding their inputs and outputs. - -Additional information on interoperability and migration between Scala 2 and 3 can be found [here]({% link _overviews/scala3-migration/compatibility-intro.md %}). diff --git a/_scala3-reference/language-versions/binary-compatibility.md b/_scala3-reference/language-versions/binary-compatibility.md deleted file mode 100644 index 2d1eb6ee89..0000000000 --- a/_scala3-reference/language-versions/binary-compatibility.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Binary Compatibility -type: section -num: 102 -previous-page: /scala3/reference/language-versions/source-compatibility ---- - -In Scala 2, different minor versions of the compiler were free to change the way how they encoded different language features in JVM bytecode. So each bump of the compiler's minor version resulted in breaking the binary compatibility, and if a project had any Scala dependencies they all needed to be (cross-)compiled to the same minor Scala version that was used in that project itself. On the contrary, Scala 3 has a stable encoding into JVM bytecode. - -In addition to classfiles, the compilation process in Scala 3 also produces files with `.tasty` extension. [TASTy]({% link scala3/guides/tasty-overview.md %}) files represent fully elaborated Scala programs in the sense that they contain all the information that has been inferred by the compiler during the compilation (like contextual parameters and type parameters). Some of this information is lost during the generation of bytecode so Scala 3 compilers read TASTy files during compilation in addition to classfiles to know the exact types of values, methods, etc. in already compiled classes (although compilation from TASTy files only is also possible). TASTy files are also typically distributed together with classfiles in published artifacts. - -TASTy format is extensible but it preserves backward compatibility and the evolution happens between minor releases of the language. This means a Scala compiler in version `3.x1.y1` is able to read TASTy files produced by another compiler in version `3.x2.y2` if `x1 >= x2` (assuming two stable versions of the compiler are considered - `SNAPSHOT` or `NIGHTLY` compiler versions can read TASTy in an older stable format but their TASTY versions are not compatible between each other even if the compilers have the same minor version; also compilers in stable versions cannot read TASTy generated by an unstable version). - -TASTy version number has the format of `.-` and the numbering changes in parallel to language releases in such a way that a bump in language minor version corresponds to a bump in TASTy minor version (e.g. for Scala `3.0.0` the TASTy version is `28.0-0`). Experimental version set to 0 signifies a stable version while others are considered unstable/experimental. TASTy version is not strictly bound to the data format itself - any changes to the API of the standard library also require a change in TASTy minor version. - -Being able to bump the compiler version in a project without having to wait for all of its dependencies to do the same is already a big leap forward when compared to Scala 2. However, we might still try to do better, especially from the perspective of authors of libraries. -If you maintain a library and you would like it to be usable as a dependency for all Scala 3 projects, you would have to always emit TASTy in a version that would be readble by everyone, which would normally mean getting stuck at 3.0.x forever. - -To solve this problem a new experimental compiler flag `-Yscala-release ` (available since 3.1.2-RC1) has been added. Setting this flag makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this enforces emitting TASTy files in an older format ensuring that: -* the code contains no references to parts of the standard library which were added to the API after `` and would crash at runtime when a program is executed with the older version of the standard library on the classpath -* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `` (otherwise they could potentially leak such disallowed references to the standard library). - -If any of the checks above is not fulfilled or for any other reason older TASTy cannot be emitted (e.g. the code uses some new language features which cannot be expressed the the older format) the entire compilation fails (with errors reported for each of such issues). - -As this feature is experimental it does not have any special support in build tools yet (at least not in sbt 1.6.1 or lower). -E.g. when a project gets compiled with Scala compiler `3.x1.y1` and `-Yscala-release 3.x2` option and then published using sbt -then the standard library in version `3.x1.y1` gets added to the project's dependencies instead of `3.x2.y2`. -When the dependencies are added to the classpath during compilation with Scala `3.x2.y2` the compiler will crash while trying to read TASTy files in the newer format. -A currently known workaround is to modify the build definition of the dependent project by explicitly overriding the version of Scala standard library in dependencies, e.g. - -```scala -dependencyOverrides ++= Seq( - scalaOrganization.value %% "scala3-library" % scalaVersion.value, - scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value // for Scala.js projects -) -``` - -The behaviour of `-Yscala-release` flag might still change in the future, especially it's not guaranteed that every new version of the compiler would be able to generate TASTy in all older formats going back to the one produced by `3.0.x` compiler. diff --git a/_scala3-reference/language-versions/source-compatibility.md b/_scala3-reference/language-versions/source-compatibility.md deleted file mode 100644 index 6ae3cd5fb0..0000000000 --- a/_scala3-reference/language-versions/source-compatibility.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Source Compatibility -type: section -num: 101 -previous-page: /scala3/reference/language-versions -next-page: /scala3/reference/language-versions/binary-compatibility ---- - -Scala 3 does NOT guarantee source compatibility between different minor language versions (e.g. some syntax valid in 3.x might get deprecated and then phased out in 3.y for y > x). There are also some syntax structures that were valid in Scala 2 but are not anymore in Scala 3. However the compiler provides a possibility to specify the desired version of syntax used in a particular file or globally for a run of the compiler to make migration between versions easier. - -The default Scala language syntax version currently supported by the Dotty compiler is [`3.0`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$3/0$.html). There are also other language versions that can be specified instead: - -- [`3.0-migration`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$3/0-migration$.html): Same as `3.0` but with a Scala 2 compatibility mode that helps moving Scala 2.13 sources over to Scala 3. In particular, it - - - flags some Scala 2 constructs that are disallowed in Scala 3 as migration warnings instead of hard errors, - - changes some rules to be more lenient and backwards compatible with Scala 2.13 - - gives some additional warnings where the semantics has changed between Scala 2.13 and 3.0 - - in conjunction with `-rewrite`, offer code rewrites from Scala 2.13 to 3.0. - -- [`future`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$future$.html): A preview of changes introduced in the next versions after 3.0. In the doc pages here we refer to the language version with these changes as `3.1`, but it might be that some of these changes will be rolled out in later `3.x` versions. - -Some Scala 2 specific idioms will be dropped in this version. The feature set supported by this version will be refined over time as we approach its release. - -- [`future-migration`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$future-migration$.html): Same as `future` but with additional helpers to migrate from `3.0`. Similarly to the helpers available under `3.0-migration`, these include migration warnings and optional rewrites. - -There are two ways to specify a language version : - -- with a `-source` command line setting, e.g. `-source 3.0-migration`. -- with a `scala.language` import at the top of a source file, e.g: - -```scala -package p -import scala.language.`future-migration` - -class C { ... } -``` - -Language imports supersede command-line settings in the source files where they are specified. Only one language import specifying a source version is allowed in a source file, and it must come before any definitions in that file. diff --git a/_scala3-reference/metaprogramming.md b/_scala3-reference/metaprogramming.md deleted file mode 100644 index 18664703a0..0000000000 --- a/_scala3-reference/metaprogramming.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Metaprogramming" -type: chapter -num: 27 -previous-page: /scala3/reference/contextual/relationship-implicits -next-page: /scala3/reference/metaprogramming/inline ---- - -The following pages introduce the redesign of metaprogramming in Scala. They -introduce the following fundamental facilities: - -1. [`inline`](./metaprogramming/inline.html) is a new modifier that guarantees that - a definition will be inlined at the point of use. The primary motivation - behind inline is to reduce the overhead behind function calls and access to - values. The expansion will be performed by the Scala compiler during the - `Typer` compiler phase. As opposed to inlining in some other ecosystems, - inlining in Scala is not merely a request to the compiler but is a - _command_. The reason is that inlining in Scala can drive other compile-time - operations, like inline pattern matching (enabling type-level - programming), macros (enabling compile-time, generative, metaprogramming) and - runtime code generation (multi-stage programming). - -2. [Compile-time ops](./metaprogramming/compiletime-ops.html) are helper definitions in the - standard library that provide support for compile-time operations over values and types. - -3. [Macros](./metaprogramming/macros.html) are built on two well-known fundamental - operations: quotation and splicing. Quotation converts program code to - data, specifically, a (tree-like) representation of this code. It is - expressed as `'{...}` for expressions and as `'[...]` for types. Splicing, - expressed as `${ ... }`, goes the other way: it converts a program's representation - to program code. Together with `inline`, these two abstractions allow - to construct program code programmatically. - -4. [Runtime Staging](./metaprogramming/staging.html) Where macros construct code at _compile-time_, - staging lets programs construct new code at _runtime_. That way, - code generation can depend not only on static data but also on data available at runtime. This splits the evaluation of the program in two or more phases or ... - stages. Consequently, this method of generative programming is called "Multi-Stage Programming". Staging is built on the same foundations as macros. It uses - quotes and splices, but leaves out `inline`. - -5. [Reflection](./metaprogramming/reflection.html) Quotations are a "black-box" - representation of code. They can be parameterized and composed using - splices, but their structure cannot be analyzed from the outside. TASTy - reflection gives a way to analyze code structure by partly revealing the representation type of a piece of code in a standard API. The representation - type is a form of typed abstract syntax tree, which gives rise to the `TASTy` - moniker. - -6. [TASTy Inspection](./metaprogramming/tasty-inspect.html) Typed abstract syntax trees are serialized - in a custom compressed binary format stored in `.tasty` files. TASTy inspection allows - to load these files and analyze their content's tree structure. diff --git a/_scala3-reference/metaprogramming/compiletime-ops.md b/_scala3-reference/metaprogramming/compiletime-ops.md deleted file mode 100644 index 9bf0bc5379..0000000000 --- a/_scala3-reference/metaprogramming/compiletime-ops.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: "Compile-time operations" -type: section -num: 29 -previous-page: /scala3/reference/metaprogramming/inline -next-page: /scala3/reference/metaprogramming/macros ---- - -## The `scala.compiletime` Package - -The [`scala.compiletime`](https://scala-lang.org/api/3.x/scala/compiletime.html) package contains helper definitions that provide support for compile-time operations over values. They are described in the following. - -### `constValue` and `constValueOpt` - -`constValue` is a function that produces the constant value represented by a -type. - -```scala -import scala.compiletime.constValue -import scala.compiletime.ops.int.S - -transparent inline def toIntC[N]: Int = - inline constValue[N] match - case 0 => 0 - case _: S[n1] => 1 + toIntC[n1] - -inline val ctwo = toIntC[2] -``` - -`constValueOpt` is the same as `constValue`, however returning an `Option[T]` -enabling us to handle situations where a value is not present. Note that `S` is -the type of the successor of some singleton type. For example the type `S[1]` is -the singleton type `2`. - -### `erasedValue` - -So far we have seen inline methods that take terms (tuples and integers) as -parameters. What if we want to base case distinctions on types instead? For -instance, one would like to be able to write a function `defaultValue`, that, -given a type `T`, returns optionally the default value of `T`, if it exists. -We can already express this using rewrite match expressions and a simple -helper function, `scala.compiletime.erasedValue`, which is defined as follows: - -```scala -def erasedValue[T]: T -``` - -The `erasedValue` function _pretends_ to return a value of its type argument `T`. -Calling this function will always result in a compile-time error unless the call -is removed from the code while inlining. - -Using `erasedValue`, we can then define `defaultValue` as follows: - -```scala -import scala.compiletime.erasedValue - -transparent inline def defaultValue[T] = - inline erasedValue[T] match - case _: Byte => Some(0: Byte) - case _: Char => Some(0: Char) - case _: Short => Some(0: Short) - case _: Int => Some(0) - case _: Long => Some(0L) - case _: Float => Some(0.0f) - case _: Double => Some(0.0d) - case _: Boolean => Some(false) - case _: Unit => Some(()) - case _ => None -``` - -Then: - -```scala -val dInt: Some[Int] = defaultValue[Int] -val dDouble: Some[Double] = defaultValue[Double] -val dBoolean: Some[Boolean] = defaultValue[Boolean] -val dAny: None.type = defaultValue[Any] -``` - -As another example, consider the type-level version of `toInt` below: -given a _type_ representing a Peano number, -return the integer _value_ corresponding to it. -Consider the definitions of numbers as in the _Inline -Match_ section above. Here is how `toIntT` can be defined: - -```scala -transparent inline def toIntT[N <: Nat]: Int = - inline scala.compiletime.erasedValue[N] match - case _: Zero.type => 0 - case _: Succ[n] => toIntT[n] + 1 - -inline val two = toIntT[Succ[Succ[Zero.type]]] -``` - -`erasedValue` is an `erased` method so it cannot be used and has no runtime -behavior. Since `toIntT` performs static checks over the static type of `N` we -can safely use it to scrutinize its return type (`S[S[Z]]` in this case). - -### `error` - -The `error` method is used to produce user-defined compile errors during inline expansion. -It has the following signature: - -```scala -inline def error(inline msg: String): Nothing -``` - -If an inline expansion results in a call `error(msgStr)` the compiler -produces an error message containing the given `msgStr`. - -```scala -import scala.compiletime.{error, code} - -inline def fail() = - error("failed for a reason") - -fail() // error: failed for a reason -``` - -or - -```scala -inline def fail(p1: => Any) = - error(code"failed on: $p1") - -fail(identity("foo")) // error: failed on: identity("foo") -``` - -### The `scala.compiletime.ops` package - -The [`scala.compiletime.ops`](https://scala-lang.org/api/3.x/scala/compiletime/ops.html) package contains types that provide support for -primitive operations on singleton types. For example, -`scala.compiletime.ops.int.*` provides support for multiplying two singleton -`Int` types, and `scala.compiletime.ops.boolean.&&` for the conjunction of two -`Boolean` types. When all arguments to a type in `scala.compiletime.ops` are -singleton types, the compiler can evaluate the result of the operation. - -```scala -import scala.compiletime.ops.int.* -import scala.compiletime.ops.boolean.* - -val conjunction: true && true = true -val multiplication: 3 * 5 = 15 -``` - -Many of these singleton operation types are meant to be used infix (as in [SLS §3.2.10](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#infix-types)). - -Since type aliases have the same precedence rules as their term-level -equivalents, the operations compose with the expected precedence rules: - -```scala -import scala.compiletime.ops.int.* -val x: 1 + 2 * 3 = 7 -``` - -The operation types are located in packages named after the type of the -left-hand side parameter: for instance, `scala.compiletime.ops.int.+` represents -addition of two numbers, while `scala.compiletime.ops.string.+` represents string -concatenation. To use both and distinguish the two types from each other, a -match type can dispatch to the correct implementation: - -```scala -import scala.compiletime.ops.* - -import scala.annotation.infix - -type +[X <: Int | String, Y <: Int | String] = (X, Y) match - case (Int, Int) => int.+[X, Y] - case (String, String) => string.+[X, Y] - -val concat: "a" + "b" = "ab" -val addition: 1 + 1 = 2 -``` - -## Summoning Implicits Selectively - -It is foreseen that many areas of typelevel programming can be done with rewrite -methods instead of implicits. But sometimes implicits are unavoidable. The -problem so far was that the Prolog-like programming style of implicit search -becomes viral: Once some construct depends on implicit search it has to be -written as a logic program itself. Consider for instance the problem of creating -a `TreeSet[T]` or a `HashSet[T]` depending on whether `T` has an `Ordering` or -not. We can create a set of implicit definitions like this: - -```scala -trait SetFor[T, S <: Set[T]] - -class LowPriority: - implicit def hashSetFor[T]: SetFor[T, HashSet[T]] = ... - -object SetsFor extends LowPriority: - implicit def treeSetFor[T: Ordering]: SetFor[T, TreeSet[T]] = ... -``` - -Clearly, this is not pretty. Besides all the usual indirection of implicit -search, we face the problem of rule prioritization where we have to ensure that -`treeSetFor` takes priority over `hashSetFor` if the element type has an -ordering. This is solved (clumsily) by putting `hashSetFor` in a superclass -`LowPriority` of the object `SetsFor` where `treeSetFor` is defined. Maybe the -boilerplate would still be acceptable if the crufty code could be contained. -However, this is not the case. Every user of the abstraction has to be -parameterized itself with a `SetFor` implicit. Considering the simple task _"I -want a `TreeSet[T]` if `T` has an ordering and a `HashSet[T]` otherwise"_, this -seems like a lot of ceremony. - -There are some proposals to improve the situation in specific areas, for -instance by allowing more elaborate schemes to specify priorities. But they all -keep the viral nature of implicit search programs based on logic programming. - -By contrast, the new `summonFrom` construct makes implicit search available -in a functional context. To solve the problem of creating the right set, one -would use it as follows: - -```scala -import scala.compiletime.summonFrom - -inline def setFor[T]: Set[T] = summonFrom { - case ord: Ordering[T] => new TreeSet[T]()(using ord) - case _ => new HashSet[T] -} -``` - -A `summonFrom` call takes a pattern matching closure as argument. All patterns -in the closure are type ascriptions of the form `identifier : Type`. - -Patterns are tried in sequence. The first case with a pattern `x: T` such that an implicit value of type `T` can be summoned is chosen. - -Alternatively, one can also use a pattern-bound given instance, which avoids the explicit using clause. For instance, `setFor` could also be formulated as follows: - -```scala -import scala.compiletime.summonFrom - -inline def setFor[T]: Set[T] = summonFrom { - case given Ordering[T] => new TreeSet[T] - case _ => new HashSet[T] -} -``` - -`summonFrom` applications must be reduced at compile time. - -Consequently, if we summon an `Ordering[String]` the code above will return a -new instance of `TreeSet[String]`. - -```scala -summon[Ordering[String]] - -println(setFor[String].getClass) // prints class scala.collection.immutable.TreeSet -``` - -**Note** `summonFrom` applications can raise ambiguity errors. Consider the following -code with two givens in scope of type `A`. The pattern match in `f` will raise -an ambiguity error of `f` is applied. - -```scala -class A -given a1: A = new A -given a2: A = new A - -inline def f: Any = summonFrom { - case given _: A => ??? // error: ambiguous givens -} -``` - -## `summonInline` - -The shorthand `summonInline` provides a simple way to write a `summon` that is delayed until the call is inlined. -Unlike `summonFrom`, `summonInline` also yields the implicit-not-found error, if a given instance of the summoned -type is not found. -```scala -import scala.compiletime.summonInline -import scala.annotation.implicitNotFound - -@implicitNotFound("Missing One") -trait Missing1 - -@implicitNotFound("Missing Two") -trait Missing2 - -trait NotMissing -given NotMissing = ??? - -transparent inline def summonInlineCheck[T <: Int](inline t : T) : Any = - inline t match - case 1 => summonInline[Missing1] - case 2 => summonInline[Missing2] - case _ => summonInline[NotMissing] - -val missing1 = summonInlineCheck(1) // error: Missing One -val missing2 = summonInlineCheck(2) // error: Missing Two -val notMissing : NotMissing = summonInlineCheck(3) -``` - -## Reference - -For more information about compile-time operations, see [PR #4768](https://github.com/lampepfl/dotty/pull/4768), -which explains how `summonFrom`'s predecessor (implicit matches) can be used for typelevel programming and code specialization and [PR #7201](https://github.com/lampepfl/dotty/pull/7201) which explains the new `summonFrom` syntax. diff --git a/_scala3-reference/metaprogramming/inline.md b/_scala3-reference/metaprogramming/inline.md deleted file mode 100644 index 2e55b8e63c..0000000000 --- a/_scala3-reference/metaprogramming/inline.md +++ /dev/null @@ -1,380 +0,0 @@ ---- -title: Inline -type: section -num: 28 -previous-page: /scala3/reference/metaprogramming -next-page: /scala3/reference/metaprogramming/compiletime-ops ---- - -## Inline Definitions - -`inline` is a new [soft modifier](../soft-modifier.html) that guarantees that a -definition will be inlined at the point of use. Example: - -```scala -object Config: - inline val logging = false - -object Logger: - - private var indent = 0 - - inline def log[T](msg: String, indentMargin: =>Int)(op: => T): T = - if Config.logging then - println(s"${" " * indent}start $msg") - indent += indentMargin - val result = op - indent -= indentMargin - println(s"${" " * indent}$msg = $result") - result - else op -end Logger -``` - -The `Config` object contains a definition of the **inline value** `logging`. -This means that `logging` is treated as a _constant value_, equivalent to its -right-hand side `false`. The right-hand side of such an `inline val` must itself -be a [constant expression](https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#constant-expressions). -Used in this way, `inline` is equivalent to Java and Scala 2's `final`. Note that `final`, meaning -_inlined constant_, is still supported in Scala 3, but will be phased out. - -The `Logger` object contains a definition of the **inline method** `log`. This -method will always be inlined at the point of call. - -In the inlined code, an `if-then-else` with a constant condition will be rewritten -to its `then`- or `else`-part. Consequently, in the `log` method above the -`if Config.logging` with `Config.logging == true` will get rewritten into its -`then`-part. - -Here's an example: - -```scala -var indentSetting = 2 - -def factorial(n: BigInt): BigInt = - log(s"factorial($n)", indentSetting) { - if n == 0 then 1 - else n * factorial(n - 1) - } -``` - -If `Config.logging == false`, this will be rewritten (simplified) to: - -```scala -def factorial(n: BigInt): BigInt = - if n == 0 then 1 - else n * factorial(n - 1) -``` - -As you notice, since neither `msg` or `indentMargin` were used, they do not -appear in the generated code for `factorial`. Also note the body of our `log` -method: the `else-` part reduces to just an `op`. In the generated code we do -not generate any closures because we only refer to a by-name parameter *once*. -Consequently, the code was inlined directly and the call was beta-reduced. - -In the `true` case the code will be rewritten to: - -```scala -def factorial(n: BigInt): BigInt = - val msg = s"factorial($n)" - println(s"${" " * indent}start $msg") - Logger.inline$indent_=(indent.+(indentSetting)) - val result = - if n == 0 then 1 - else n * factorial(n - 1) - Logger.inline$indent_=(indent.-(indentSetting)) - println(s"${" " * indent}$msg = $result") - result -``` - -Note that the by-value parameter `msg` is evaluated only once, per the usual Scala -semantics, by binding the value and reusing the `msg` through the body of -`factorial`. Also, note the special handling of the assignment to the private var -`indent`. It is achieved by generating a setter method `def inline$indent_=` and calling it instead. - -### Recursive Inline Methods - -Inline methods can be recursive. For instance, when called with a constant -exponent `n`, the following method for `power` will be implemented by -straight inline code without any loop or recursion. - -```scala -inline def power(x: Double, n: Int): Double = - if n == 0 then 1.0 - else if n == 1 then x - else - val y = power(x, n / 2) - if n % 2 == 0 then y * y else y * y * x - -power(expr, 10) -// translates to -// -// val x = expr -// val y1 = x * x // ^2 -// val y2 = y1 * y1 // ^4 -// val y3 = y2 * x // ^5 -// y3 * y3 // ^10 -``` - -Parameters of inline methods can have an `inline` modifier as well. This means -that actual arguments to these parameters will be inlined in the body of the -`inline def`. `inline` parameters have call semantics equivalent to by-name parameters -but allow for duplication of the code in the argument. It is usually useful when constant -values need to be propagated to allow further optimizations/reductions. - -The following example shows the difference in translation between by-value, by-name and `inline` -parameters: - -```scala -inline def funkyAssertEquals(actual: Double, expected: =>Double, inline delta: Double): Unit = - if (actual - expected).abs > delta then - throw new AssertionError(s"difference between ${expected} and ${actual} was larger than ${delta}") - -funkyAssertEquals(computeActual(), computeExpected(), computeDelta()) -// translates to -// -// val actual = computeActual() -// def expected = computeExpected() -// if (actual - expected).abs > computeDelta() then -// throw new AssertionError(s"difference between ${expected} and ${actual} was larger than ${computeDelta()}") -``` - -### Rules for Overriding - -Inline methods can override other non-inline methods. The rules are as follows: - -1. If an inline method `f` implements or overrides another, non-inline method, the inline method can also be invoked at runtime. For instance, consider the scenario: - - ```scala - abstract class A: - def f: Int - def g: Int = f - - class B extends A: - inline def f = 22 - override inline def g = f + 11 - - val b = new B - val a: A = b - // inlined invocatons - assert(b.f == 22) - assert(b.g == 33) - // dynamic invocations - assert(a.f == 22) - assert(a.g == 33) - ``` - - The inlined invocations and the dynamically dispatched invocations give the same results. - -2. Inline methods are effectively final. - -3. Inline methods can also be abstract. An abstract inline method can be implemented only by other inline methods. It cannot be invoked directly: - - ```scala - abstract class A: - inline def f: Int - - object B extends A: - inline def f: Int = 22 - - B.f // OK - val a: A = B - a.f // error: cannot inline f in A. - ``` - -### Relationship to `@inline` - -Scala 2 also defines a `@inline` annotation which is used as a hint for the -backend to inline code. The `inline` modifier is a more powerful option: - -- expansion is guaranteed instead of best effort, -- expansion happens in the frontend instead of in the backend and -- expansion also applies to recursive methods. - - - -#### The definition of constant expression - -Right-hand sides of inline values and of arguments for inline parameters must be -constant expressions in the sense defined by the [SLS §6.24](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#constant-expressions), -including _platform-specific_ extensions such as constant folding of pure -numeric computations. - -An inline value must have a literal type such as `1` or `true`. - -```scala -inline val four = 4 -// equivalent to -inline val four: 4 = 4 -``` - -It is also possible to have inline vals of types that do not have a syntax, such as `Short(4)`. - -```scala -trait InlineConstants: - inline val myShort: Short - -object Constants extends InlineConstants: - inline val myShort/*: Short(4)*/ = 4 -``` - -## Transparent Inline Methods - -Inline methods can additionally be declared `transparent`. -This means that the return type of the inline method can be -specialized to a more precise type upon expansion. Example: - -```scala -class A -class B extends A: - def m = true - -transparent inline def choose(b: Boolean): A = - if b then new A else new B - -val obj1 = choose(true) // static type is A -val obj2 = choose(false) // static type is B - -// obj1.m // compile-time error: `m` is not defined on `A` -obj2.m // OK -``` - -Here, the inline method `choose` returns an instance of either of the two types `A` or `B`. -If `choose` had not been declared to be `transparent`, the result -of its expansion would always be of type `A`, even though the computed value might be of the subtype `B`. -The inline method is a "blackbox" in the sense that details of its implementation do not leak out. -But if a `transparent` modifier is given, the expansion is the type of the expanded body. If the argument `b` -is `true`, that type is `A`, otherwise it is `B`. Consequently, calling `m` on `obj2` -type-checks since `obj2` has the same type as the expansion of `choose(false)`, which is `B`. -Transparent inline methods are "whitebox" in the sense that the type -of an application of such a method can be more specialized than its declared -return type, depending on how the method expands. - -In the following example, we see how the return type of `zero` is specialized to -the singleton type `0` permitting the addition to be ascribed with the correct -type `1`. - -```scala -transparent inline def zero: Int = 0 - -val one: 1 = zero + 1 -``` - -### Transparent vs. non-transparent inline - -As we already discussed, transparent inline methods may influence type checking at call site. -Technically this implies that transparent inline methods must be expanded during type checking of the program. -Other inline methods are inlined later after the program is fully typed. - -For example, the following two functions will be typed the same way but will be inlined at different times. - -```scala -inline def f1: T = ... -transparent inline def f2: T = (...): T -``` - -A noteworthy difference is the behavior of `transparent inline given`. -If there is an error reported when inlining that definition, it will be considered as an implicit search mismatch and the search will continue. -A `transparent inline given` can add a type ascription in its RHS (as in `f2` from the previous example) to avoid the precise type but keep the search behavior. -On the other hand, an `inline given` is taken as an implicit and then inlined after typing. -Any error will be emitted as usual. - -## Inline Conditionals - -An if-then-else expression whose condition is a constant expression can be simplified to -the selected branch. Prefixing an if-then-else expression with `inline` enforces that -the condition has to be a constant expression, and thus guarantees that the conditional will always -simplify. - -Example: - -```scala -inline def update(delta: Int) = - inline if delta >= 0 then increaseBy(delta) - else decreaseBy(-delta) -``` - -A call `update(22)` would rewrite to `increaseBy(22)`. But if `update` was called with -a value that was not a compile-time constant, we would get a compile time error like the one -below: - -```scala - | inline if delta >= 0 then ??? - | ^ - | cannot reduce inline if - | its condition - | delta >= 0 - | is not a constant value - | This location is in code that was inlined at ... -``` - -In a transparent inline, an `inline if` will force the inlining of any inline definition in its condition during type checking. - -## Inline Matches - -A `match` expression in the body of an `inline` method definition may be -prefixed by the `inline` modifier. If there is enough static information to -unambiguously take a branch, the expression is reduced to that branch and the -type of the result is taken. If not, a compile-time error is raised that -reports that the match cannot be reduced. - -The example below defines an inline method with a -single inline match expression that picks a case based on its static type: - -```scala -transparent inline def g(x: Any): Any = - inline x match - case x: String => (x, x) // Tuple2[String, String](x, x) - case x: Double => x - -g(1.0d) // Has type 1.0d which is a subtype of Double -g("test") // Has type (String, String) -``` - -The scrutinee `x` is examined statically and the inline match is reduced -accordingly returning the corresponding value (with the type specialized because `g` is declared `transparent`). This example performs a simple type test over the -scrutinee. The type can have a richer structure like the simple ADT below. -`toInt` matches the structure of a number in [Church-encoding](https://en.wikipedia.org/wiki/Church_encoding) -and _computes_ the corresponding integer. - -```scala -trait Nat -case object Zero extends Nat -case class Succ[N <: Nat](n: N) extends Nat - -transparent inline def toInt(n: Nat): Int = - inline n match - case Zero => 0 - case Succ(n1) => toInt(n1) + 1 - -inline val natTwo = toInt(Succ(Succ(Zero))) -val intTwo: 2 = natTwo -``` - -`natTwo` is inferred to have the singleton type 2. - -### Reference - -For more information about the semantics of `inline`, see the [Scala 2020: Semantics-preserving inlining for metaprogramming](https://dl.acm.org/doi/10.1145/3426426.3428486) paper. diff --git a/_scala3-reference/metaprogramming/macros-spec.md b/_scala3-reference/metaprogramming/macros-spec.md deleted file mode 100644 index aa819c5ecb..0000000000 --- a/_scala3-reference/metaprogramming/macros-spec.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Macros Spec" ---- - -## Implementation - -### Syntax - -Compared to the [Scala 3 reference grammar](../syntax.html) -there are the following syntax changes: -``` -SimpleExpr ::= ... - | ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ - | ‘$’ ‘{’ Block ‘}’ -SimpleType ::= ... - | ‘$’ ‘{’ Block ‘}’ -``` -In addition, an identifier `$x` starting with a `$` that appears inside -a quoted expression or type is treated as a splice `${x}` and a quoted identifier -`'x` that appears inside a splice is treated as a quote `'{x}` - -### Implementation in `scalac` - -Quotes and splices are primitive forms in the generated abstract syntax trees. -Top-level splices are eliminated during macro expansion while typing. On the -other hand, top-level quotes are eliminated in an expansion phase `PickleQuotes` -phase (after typing and pickling). PCP checking occurs while preparing the RHS -of an inline method for top-level splices and in the `Staging` phase (after -typing and before pickling). - -Macro-expansion works outside-in. If the outermost scope is a splice, -the spliced AST will be evaluated in an interpreter. A call to a -previously compiled method can be implemented as a reflective call to -that method. With the restrictions on splices that are currently in -place that’s all that’s needed. We might allow more interpretation in -splices in the future, which would allow us to loosen the -restriction. Quotes in spliced, interpreted code are kept as they -are, after splices nested in the quotes are expanded. - -If the outermost scope is a quote, we need to generate code that -constructs the quoted tree at run-time. We implement this by -serializing the tree as a TASTy structure, which is stored -in a string literal. At runtime, an unpickler method is called to -deserialize the string into a tree. - -Splices inside quoted code insert the spliced tree as is, after -expanding any quotes in the spliced code recursively. - -## Formalization - -The phase consistency principle can be formalized in a calculus that -extends simply-typed lambda calculus with quotes and splices. - -### Syntax - -The syntax of terms, values, and types is given as follows: -``` -Terms t ::= x variable - (x: T) => t lambda - t t application - 't quote - $t splice - -Values v ::= (x: T) => t lambda - 'u quote - -Simple terms u ::= x | (x: T) => u | u u | 't - -Types T ::= A base type - T -> T function type - expr T quoted -``` -Typing rules are formulated using a stack of environments -`Es`. Individual environments `E` consist as usual of variable -bindings `x: T`. Environments can be combined using the two -combinators `'` and `$`. -``` -Environment E ::= () empty - E, x: T - -Env. stack Es ::= () empty - E simple - Es * Es combined - -Separator * ::= ' - $ -``` -The two environment combinators are both associative with left and -right identity `()`. - -### Operational semantics - -We define a small step reduction relation `-->` with the following rules: -``` - ((x: T) => t) v --> [x := v]t - - ${'u} --> u - - t1 --> t2 - ----------------- - e[t1] --> e[t2] -``` -The first rule is standard call-by-value beta-reduction. The second -rule says that splice and quotes cancel each other out. The third rule -is a context rule; it says that reduction is allowed in the hole `[ ]` -position of an evaluation context. Evaluation contexts `e` and -splice evaluation context `e_s` are defined syntactically as follows: -``` -Eval context e ::= [ ] | e t | v e | 'e_s[${e}] -Splice context e_s ::= [ ] | (x: T) => e_s | e_s t | u e_s -``` - -### Typing rules - -Typing judgments are of the form `Es |- t: T`. There are two -substructural rules which express the fact that quotes and splices -cancel each other out: -``` - Es1 * Es2 |- t: T - --------------------------- - Es1 $ E1 ' E2 * Es2 |- t: T - - - Es1 * Es2 |- t: T - --------------------------- - Es1 ' E1 $ E2 * Es2 |- t: T -``` -The lambda calculus fragment of the rules is standard, except that we -use a stack of environments. The rules only interact with the topmost -environment of the stack. -``` - x: T in E - -------------- - Es * E |- x: T - - - Es * E, x: T1 |- t: T2 - ------------------------------- - Es * E |- (x: T1) => t: T -> T2 - - - Es |- t1: T2 -> T Es |- t2: T2 - --------------------------------- - Es |- t1 t2: T -``` -The rules for quotes and splices map between `expr T` and `T` by trading `'` and `$` between -environments and terms. -``` - Es $ () |- t: expr T - -------------------- - Es |- $t: T - - - Es ' () |- t: T - ---------------- - Es |- 't: expr T -``` -The meta theory of a slightly simplified 2-stage variant of this calculus -is studied [separately](./simple-smp.html). - -## Going Further - -The metaprogramming framework as presented and currently implemented is quite restrictive -in that it does not allow for the inspection of quoted expressions and -types. It’s possible to work around this by providing all necessary -information as normal, unquoted inline parameters. But we would gain -more flexibility by allowing for the inspection of quoted code with -pattern matching. This opens new possibilities. - -For instance, here is a version of `power` that generates the multiplications -directly if the exponent is statically known and falls back to the dynamic -implementation of `power` otherwise. -```scala -import scala.quoted.* - -inline def power(x: Double, n: Int): Double = - ${ powerExpr('x, 'n) } - -private def powerExpr(x: Expr[Double], n: Expr[Int]) - (using Quotes): Expr[Double] = - n.value match - case Some(m) => powerExpr(x, m) - case _ => '{ dynamicPower($x, $n) } - -private def powerExpr(x: Expr[Double], n: Int) - (using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } - else if n == 1 then x - else if n % 2 == 0 then '{ val y = $x * $x; ${ powerExpr('y, n / 2) } } - else '{ $x * ${ powerExpr(x, n - 1) } } - -private def dynamicPower(x: Double, n: Int): Double = - if n == 0 then 1.0 - else if n % 2 == 0 then dynamicPower(x * x, n / 2) - else x * dynamicPower(x, n - 1) -``` - -In the above, the method `.value` maps a constant expression of the type -`Expr[T]` to its value of the type `T`. - -With the right extractors, the "AsFunction" conversion -that maps expressions over functions to functions over expressions can -be implemented in user code: -```scala -given AsFunction1[T, U]: Conversion[Expr[T => U], Expr[T] => Expr[U]] with - def apply(f: Expr[T => U]): Expr[T] => Expr[U] = - (x: Expr[T]) => f match - case Lambda(g) => g(x) - case _ => '{ ($f)($x) } -``` -This assumes an extractor -```scala -object Lambda: - def unapply[T, U](x: Expr[T => U]): Option[Expr[T] => Expr[U]] -``` -Once we allow inspection of code via extractors, it’s tempting to also -add constructors that create typed trees directly without going -through quotes. Most likely, those constructors would work over `Expr` -types which lack a known type argument. For instance, an `Apply` -constructor could be typed as follows: -```scala -def Apply(fn: Expr[Any], args: List[Expr[Any]]): Expr[Any] -``` -This would allow constructing applications from lists of arguments -without having to match the arguments one-by-one with the -corresponding formal parameter types of the function. We then need "at -the end" a method to convert an `Expr[Any]` to an `Expr[T]` where `T` is -given from the outside. For instance, if `code` yields a `Expr[Any]`, then -`code.atType[T]` yields an `Expr[T]`. The `atType` method has to be -implemented as a primitive; it would check that the computed type -structure of `Expr` is a subtype of the type structure representing -`T`. - -Before going down that route, we should evaluate in detail the tradeoffs it -presents. Constructing trees that are only verified _a posteriori_ -to be type correct loses a lot of guidance for constructing the right -trees. So we should wait with this addition until we have more -use-cases that help us decide whether the loss in type-safety is worth -the gain in flexibility. In this context, it seems that deconstructing types is -less error-prone than deconstructing terms, so one might also -envisage a solution that allows the former but not the latter. - -## Conclusion - -Metaprogramming has a reputation of being difficult and confusing. -But with explicit `Expr/Type` types and quotes and splices it can become -downright pleasant. A simple strategy first defines the underlying quoted or unquoted -values using `Expr` and `Type` and then inserts quotes and splices to make the types -line up. Phase consistency is at the same time a great guideline -where to insert a splice or a quote and a vital sanity check that -the result makes sense. diff --git a/_scala3-reference/metaprogramming/macros.md b/_scala3-reference/metaprogramming/macros.md deleted file mode 100644 index 9d70230c13..0000000000 --- a/_scala3-reference/metaprogramming/macros.md +++ /dev/null @@ -1,825 +0,0 @@ ---- -title: "Macros" -type: section -num: 30 -previous-page: /scala3/reference/metaprogramming/compiletime-ops -next-page: /scala3/reference/metaprogramming/staging ---- - -> When developing macros enable `-Xcheck-macros` scalac option flag to have extra runtime checks. - -## Macros: Quotes and Splices - -Macros are built on two well-known fundamental operations: quotation and splicing. -Quotation is expressed as `'{...}` for expressions and splicing is expressed as `${ ... }`. -Additionally, within a quote or a splice we can quote or splice identifiers directly (i.e. `'e` and `$e`). -Readers may notice the resemblance of the two aforementioned syntactic -schemes with the familiar string interpolation syntax. - -```scala -println(s"Hello, $name, here is the result of 1 + 1 = ${1 + 1}") -``` - -In string interpolation we _quoted_ a string and then we _spliced_ into it, two others. The first, `name`, is a reference to a value of type [`String`](https://scala-lang.org/api/3.x/scala/Predef$.html#String-0), and the second is an arithmetic expression that will be _evaluated_ followed by the splicing of its string representation. - -Quotes and splices in this section allow us to treat code in a similar way, -effectively supporting macros. The entry point for macros is an inline method -with a top-level splice. We call it a top-level because it is the only occasion -where we encounter a splice outside a quote (consider as a quote the -compilation-unit at the call-site). For example, the code below presents an -`inline` method `assert` which calls at compile-time a method `assertImpl` with -a boolean expression tree as argument. `assertImpl` evaluates the expression and -prints it again in an error message if it evaluates to `false`. - -```scala -import scala.quoted.* - -inline def assert(inline expr: Boolean): Unit = - ${ assertImpl('expr) } - -def assertImpl(expr: Expr[Boolean])(using Quotes) = '{ - if !$expr then - throw AssertionError(s"failed assertion: ${${ showExpr(expr) }}") -} - -def showExpr(expr: Expr[Boolean])(using Quotes): Expr[String] = - '{ [actual implementation later in this document] } -``` - -If `e` is an expression, then `'{e}` represents the typed -abstract syntax tree representing `e`. If `T` is a type, then `Type.of[T]` -represents the type structure representing `T`. The precise -definitions of "typed abstract syntax tree" or "type-structure" do not -matter for now, the terms are used only to give some -intuition. Conversely, `${e}` evaluates the expression `e`, which must -yield a typed abstract syntax tree or type structure, and embeds the -result as an expression (respectively, type) in the enclosing program. - -Quotations can have spliced parts in them; in this case the embedded -splices are evaluated and embedded as part of the formation of the -quotation. - -Quotes and splices can also be applied directly to identifiers. An identifier -`$x` starting with a `$` that appears inside a quoted expression or type is treated as a -splice `${x}`. Analogously, an quoted identifier `'x` that appears inside a splice -is treated as a quote `'{x}`. See the Syntax section below for details. - -Quotes and splices are duals of each other. -For arbitrary expressions `e` we have: - -```scala -${'{e}} = e -'{${e}} = e -``` - -## Types for Quotations - -The type signatures of quotes and splices can be described using -two fundamental types: - -- `Expr[T]`: abstract syntax trees representing expressions of type `T` -- `Type[T]`: non erased representation of type `T`. - -Quoting takes expressions of type `T` to expressions of type `Expr[T]` -and it takes types `T` to expressions of type `Type[T]`. Splicing -takes expressions of type `Expr[T]` to expressions of type `T` and it -takes expressions of type `Type[T]` to types `T`. - -The two types can be defined in package [`scala.quoted`](https://scala-lang.org/api/3.x/scala/quoted.html) as follows: - -```scala -package scala.quoted - -sealed trait Expr[+T] -sealed trait Type[T] -``` - -Both `Expr` and `Type` are abstract and sealed, so all constructors for -these types are provided by the system. One way to construct values of -these types is by quoting, the other is by type-specific lifting -operations that will be discussed later on. - -## The Phase Consistency Principle - -A fundamental *phase consistency principle* (PCP) regulates accesses -to free variables in quoted and spliced code: - -- _For any free variable reference `x`, the number of quoted scopes and the number of spliced scopes between the reference to `x` and the definition of `x` must be equal_. - -Here, `this`-references count as free variables. On the other -hand, we assume that all imports are fully expanded and that `_root_` is -not a free variable. So references to global definitions are -allowed everywhere. - -The phase consistency principle can be motivated as follows: First, -suppose the result of a program `P` is some quoted text `'{ ... x -... }` that refers to a free variable `x` in `P`. This can be -represented only by referring to the original variable `x`. Hence, the -result of the program will need to persist the program state itself as -one of its parts. We don’t want to do this, hence this situation -should be made illegal. Dually, suppose a top-level part of a program -is a spliced text `${ ... x ... }` that refers to a free variable `x` -in `P`. This would mean that we refer during _construction_ of `P` to -a value that is available only during _execution_ of `P`. This is of -course impossible and therefore needs to be ruled out. Now, the -small-step evaluation of a program will reduce quotes and splices in -equal measure using the cancellation rules above. But it will neither -create nor remove quotes or splices individually. So the PCP ensures -that program elaboration will lead to neither of the two unwanted -situations described above. - -In what concerns the range of features it covers, this form of macros introduces -a principled metaprogramming framework that is quite close to the MetaML family of -languages. One difference is that MetaML does not have an equivalent of the PCP - -quoted code in MetaML _can_ access variables in its immediately enclosing -environment, with some restrictions and caveats since such accesses involve -serialization. However, this does not constitute a fundamental gain in -expressiveness. - -## From `Expr`s to Functions and Back - -It is possible to convert any `Expr[T => R]` into `Expr[T] => Expr[R]` and back. -These conversions can be implemented as follows: - -```scala -def to[T: Type, R: Type](f: Expr[T] => Expr[R])(using Quotes): Expr[T => R] = - '{ (x: T) => ${ f('x) } } - -def from[T: Type, R: Type](f: Expr[T => R])(using Quotes): Expr[T] => Expr[R] = - (x: Expr[T]) => '{ $f($x) } -``` - -Note how the fundamental phase consistency principle works in two -different directions here for `f` and `x`. In the method `to`, the reference to `f` is -legal because it is quoted, then spliced, whereas the reference to `x` -is legal because it is spliced, then quoted. - -They can be used as follows: - -```scala -val f1: Expr[Int => String] = - to((x: Expr[Int]) => '{ $x.toString }) // '{ (x: Int) => x.toString } - -val f2: Expr[Int] => Expr[String] = - from('{ (x: Int) => x.toString }) // (x: Expr[Int]) => '{ ((x: Int) => x.toString)($x) } -f2('{2}) // '{ ((x: Int) => x.toString)(2) } -``` - -One limitation of `from` is that it does not β-reduce when a lambda is called immediately, as evidenced in the code `{ ((x: Int) => x.toString)(2) }`. -In some cases we want to remove the lambda from the code, for this we provide the method `Expr.betaReduce` that turns a tree -describing a function into a function mapping trees to trees. - -```scala -object Expr: - ... - def betaReduce[...](...)(...): ... = ... -``` - -The definition of `Expr.betaReduce(f)(x)` is assumed to be functionally the same as -`'{($f)($x)}`, however it should optimize this call by returning the -result of beta-reducing `f(x)` if `f` is a known lambda expression. -`Expr.betaReduce` distributes applications of `Expr` over function arrows: - -```scala -Expr.betaReduce(_): Expr[(T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R]) -``` - -## Lifting Types - -Types are not directly affected by the phase consistency principle. -It is possible to use types defined at any level in any other level. -But, if a type is used in a subsequent stage it will need to be lifted to a `Type`. -Indeed, the definition of `to` above uses `T` in the next stage, there is a -quote but no splice between the parameter binding of `T` and its -usage. But the code can be rewritten by adding an explicit binding of a `Type[T]`: - -```scala -def to[T, R](f: Expr[T] => Expr[R])(using t: Type[T])(using Type[R], Quotes): Expr[T => R] = - '{ (x: t.Underlying) => ${ f('x) } } -``` - -In this version of `to`, the type of `x` is now the result of -inserting the type `Type[T]` and selecting its `Underlying`. - -To avoid clutter, the compiler converts any type reference to -a type `T` in subsequent phases to `summon[Type[T]].Underlying`. - -And to avoid duplication it does it once per type, and creates -an alias for that type at the start of the quote. - -For instance, the user-level definition of `to`: - -```scala -def to[T, R](f: Expr[T] => Expr[R])(using t: Type[T], r: Type[R])(using Quotes): Expr[T => R] = - '{ (x: T) => ${ f('x) } } -``` - -would be rewritten to - -```scala -def to[T, R](f: Expr[T] => Expr[R])(using t: Type[T], r: Type[R])(using Quotes): Expr[T => R] = - '{ - type T = t.Underlying - (x: T) => ${ f('x) } - } -``` - -The `summon` query succeeds because there is a given instance of -type `Type[T]` available (namely the given parameter corresponding -to the context bound `: Type`), and the reference to that value is -phase-correct. If that was not the case, the phase inconsistency for -`T` would be reported as an error. - -## Lifting Expressions - -Consider the following implementation of a staged interpreter that implements -a compiler through staging. - -```scala -import scala.quoted.* - -enum Exp: - case Num(n: Int) - case Plus(e1: Exp, e2: Exp) - case Var(x: String) - case Let(x: String, e: Exp, in: Exp) - -import Exp.* -``` - -The interpreted language consists of numbers `Num`, addition `Plus`, and variables -`Var` which are bound by `Let`. Here are two sample expressions in the language: - -```scala -val exp = Plus(Plus(Num(2), Var("x")), Num(4)) -val letExp = Let("x", Num(3), exp) -``` - -Here’s a compiler that maps an expression given in the interpreted -language to quoted Scala code of type `Expr[Int]`. -The compiler takes an environment that maps variable names to Scala `Expr`s. - -```scala -import scala.quoted.* - -def compile(e: Exp, env: Map[String, Expr[Int]])(using Quotes): Expr[Int] = - e match - case Num(n) => - Expr(n) - case Plus(e1, e2) => - '{ ${ compile(e1, env) } + ${ compile(e2, env) } } - case Var(x) => - env(x) - case Let(x, e, body) => - '{ val y = ${ compile(e, env) }; ${ compile(body, env + (x -> 'y)) } } -``` - -Running `compile(letExp, Map())` would yield the following Scala code: - -```scala -'{ val y = 3; (2 + y) + 4 } -``` - -The body of the first clause, `case Num(n) => Expr(n)`, looks suspicious. `n` -is declared as an `Int`, yet it is converted to an `Expr[Int]` with `Expr()`. -Shouldn’t `n` be quoted? In fact this would not -work since replacing `n` by `'n` in the clause would not be phase -correct. - -The `Expr.apply` method is defined in package `quoted`: - -```scala -package quoted - -object Expr: - ... - def apply[T: ToExpr](x: T)(using Quotes): Expr[T] = - summon[ToExpr[T]].toExpr(x) -``` - -This method says that values of types implementing the `ToExpr` type class can be -converted to `Expr` values using `Expr.apply`. - -Scala 3 comes with given instances of `ToExpr` for -several types including `Boolean`, `String`, and all primitive number -types. For example, `Int` values can be converted to `Expr[Int]` -values by wrapping the value in a `Literal` tree node. This makes use -of the underlying tree representation in the compiler for -efficiency. But the `ToExpr` instances are nevertheless not _magic_ -in the sense that they could all be defined in a user program without -knowing anything about the representation of `Expr` trees. For -instance, here is a possible instance of `ToExpr[Boolean]`: - -```scala -given ToExpr[Boolean] with - def toExpr(b: Boolean) = - if b then '{ true } else '{ false } -``` - -Once we can lift bits, we can work our way up. For instance, here is a -possible implementation of `ToExpr[Int]` that does not use the underlying -tree machinery: - -```scala -given ToExpr[Int] with - def toExpr(n: Int) = n match - case Int.MinValue => '{ Int.MinValue } - case _ if n < 0 => '{ - ${ toExpr(-n) } } - case 0 => '{ 0 } - case _ if n % 2 == 0 => '{ ${ toExpr(n / 2) } * 2 } - case _ => '{ ${ toExpr(n / 2) } * 2 + 1 } -``` - -Since `ToExpr` is a type class, its instances can be conditional. For example, -a `List` is liftable if its element type is: - -```scala -given [T: ToExpr : Type]: ToExpr[List[T]] with - def toExpr(xs: List[T]) = xs match - case head :: tail => '{ ${ Expr(head) } :: ${ toExpr(tail) } } - case Nil => '{ Nil: List[T] } -``` - -In the end, `ToExpr` resembles very much a serialization -framework. Like the latter it can be derived systematically for all -collections, case classes and enums. Note also that the synthesis -of _type-tag_ values of type `Type[T]` is essentially the type-level -analogue of lifting. - -Using lifting, we can now give the missing definition of `showExpr` in the introductory example: - -```scala -def showExpr[T](expr: Expr[T])(using Quotes): Expr[String] = - val code: String = expr.show - Expr(code) -``` - -That is, the `showExpr` method converts its `Expr` argument to a string (`code`), and lifts -the result back to an `Expr[String]` using `Expr.apply`. - -## Lifting Types - -The previous section has shown that the metaprogramming framework has -to be able to take a type `T` and convert it to a type tree of type -`Type[T]` that can be reified. This means that all free variables of -the type tree refer to types and values defined in the current stage. - -For a reference to a global class, this is easy: Just issue the fully -qualified name of the class. Members of reifiable types are handled by -just reifying the containing type together with the member name. But -what to do for references to type parameters or local type definitions -that are not defined in the current stage? Here, we cannot construct -the `Type[T]` tree directly, so we need to get it from a recursive -implicit search. For instance, to implement - -```scala -summon[Type[List[T]]] -``` - -where `T` is not defined in the current stage, we construct the type constructor -of `List` applied to the splice of the result of searching for a given instance for `Type[T]`: - -```scala -Type.of[ List[ summon[Type[T]].Underlying ] ] -``` - -This is exactly the algorithm that Scala 2 uses to search for type tags. -In fact Scala 2's type tag feature can be understood as a more ad-hoc version of -`quoted.Type`. As was the case for type tags, the implicit search for a `quoted.Type` -is handled by the compiler, using the algorithm sketched above. - -## Relationship with `inline` - -Seen by itself, principled metaprogramming looks more like a framework for -runtime metaprogramming than one for compile-time metaprogramming with macros. -But combined with Scala 3’s `inline` feature it can be turned into a compile-time -system. The idea is that macro elaboration can be understood as a combination of -a macro library and a quoted program. For instance, here’s the `assert` macro -again together with a program that calls `assert`. - -```scala -object Macros: - - inline def assert(inline expr: Boolean): Unit = - ${ assertImpl('expr) } - - def assertImpl(expr: Expr[Boolean])(using Quotes) = - val failMsg: Expr[String] = Expr("failed assertion: " + expr.show) - '{ if !($expr) then throw new AssertionError($failMsg) } - -@main def program = - val x = 1 - Macros.assert(x != 0) -``` - -Inlining the `assert` function would give the following program: - -```scala -@main def program = - val x = 1 - ${ Macros.assertImpl('{ x != 0}) } -``` - -The example is only phase correct because `Macros` is a global value and -as such not subject to phase consistency checking. Conceptually that’s -a bit unsatisfactory. If the PCP is so fundamental, it should be -applicable without the global value exception. But in the example as -given this does not hold since both `assert` and `program` call -`assertImpl` with a splice but no quote. - -However, one could argue that the example is really missing -an important aspect: The macro library has to be compiled in a phase -prior to the program using it, but in the code above, macro -and program are defined together. A more accurate view of -macros would be to have the user program be in a phase after the macro -definitions, reflecting the fact that macros have to be defined and -compiled before they are used. Hence, conceptually the program part -should be treated by the compiler as if it was quoted: - -```scala -@main def program = '{ - val x = 1 - ${ Macros.assertImpl('{ x != 0 }) } -} -``` - -If `program` is treated as a quoted expression, the call to -`Macro.assertImpl` becomes phase correct even if macro library and -program are conceptualized as local definitions. - -But what about the call from `assert` to `assertImpl`? Here, we need a -tweak of the typing rules. An inline function such as `assert` that -contains a splice operation outside an enclosing quote is called a -_macro_. Macros are supposed to be expanded in a subsequent phase, -i.e. in a quoted context. Therefore, they are also type checked as if -they were in a quoted context. For instance, the definition of -`assert` is typechecked as if it appeared inside quotes. This makes -the call from `assert` to `assertImpl` phase-correct, even if we -assume that both definitions are local. - -The `inline` modifier is used to declare a `val` that is -either a constant or is a parameter that will be a constant when instantiated. This -aspect is also important for macro expansion. - -To get values out of expressions containing constants `Expr` provides the method -`value` (or `valueOrError`). This will convert the `Expr[T]` into a `Some[T]` (or `T`) when the -expression contains value. Otherwise it will return `None` (or emit an error). -To avoid having incidental val bindings generated by the inlining of the `def` -it is recommended to use an inline parameter. To illustrate this, consider an -implementation of the `power` function that makes use of a statically known exponent: - -```scala -inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - -private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - n.value match - case Some(m) => powerCode(x, m) - case None => '{ Math.pow($x, $n.toDouble) } - -private def powerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } - else if n == 1 then x - else if n % 2 == 0 then '{ val y = $x * $x; ${ powerCode('y, n / 2) } } - else '{ $x * ${ powerCode(x, n - 1) } } -``` - -## Scope Extrusion - -Quotes and splices are duals as far as the PCP is concerned. But there is an -additional restriction that needs to be imposed on splices to guarantee -soundness: code in splices must be free of side effects. The restriction -prevents code like this: - -```scala -var x: Expr[T] = ... -'{ (y: T) => ${ x = 'y; 1 } } -``` - -This code, if it was accepted, would _extrude_ a reference to a quoted variable -`y` from its scope. This would subsequently allow access to a variable outside the -scope where it is defined, which is likely problematic. The code is clearly -phase consistent, so we cannot use PCP to rule it out. Instead, we postulate a -future effect system that can guarantee that splices are pure. In the absence of -such a system we simply demand that spliced expressions are pure by convention, -and allow for undefined compiler behavior if they are not. This is analogous to -the status of pattern guards in Scala, which are also required, but not -verified, to be pure. - -[Multi-Stage Programming](./staging.html) introduces one additional method where -you can expand code at runtime with a method `run`. There is also a problem with -that invocation of `run` in splices. Consider the following expression: - -```scala -'{ (x: Int) => ${ run('x); 1 } } -``` - -This is again phase correct, but will lead us into trouble. Indeed, evaluating -the splice will reduce the expression `run('x)` to `x`. But then the result - -```scala -'{ (x: Int) => ${ x; 1 } } -``` - -is no longer phase correct. To prevent this soundness hole it seems easiest to -classify `run` as a side-effecting operation. It would thus be prevented from -appearing in splices. In a base language with side effects we would have to do this -anyway: Since `run` runs arbitrary code it can always produce a side effect if -the code it runs produces one. - -## Example Expansion - -Assume we have two methods, one `map` that takes an `Expr[Array[T]]` and a -function `f` and one `sum` that performs a sum by delegating to `map`. - -```scala -object Macros: - - def map[T](arr: Expr[Array[T]], f: Expr[T] => Expr[Unit]) - (using Type[T], Quotes): Expr[Unit] = '{ - var i: Int = 0 - while i < ($arr).length do - val element: T = ($arr)(i) - ${f('element)} - i += 1 - } - - def sum(arr: Expr[Array[Int]])(using Quotes): Expr[Int] = '{ - var sum = 0 - ${ map(arr, x => '{sum += $x}) } - sum - } - - inline def sum_m(arr: Array[Int]): Int = ${sum('arr)} - -end Macros -``` - -A call to `sum_m(Array(1,2,3))` will first inline `sum_m`: - -```scala -val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) -${_root_.Macros.sum('arr)} -``` - -then it will splice `sum`: - -```scala -val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) - -var sum = 0 -${ map('arr, x => '{sum += $x}) } -sum -``` - -then it will inline `map`: - -```scala -val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) - -var sum = 0 -val f = x => '{sum += $x} -${ _root_.Macros.map('arr, 'f)(Type.of[Int])} -sum -``` - -then it will expand and splice inside quotes `map`: - -```scala -val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) - -var sum = 0 -val f = x => '{sum += $x} -var i: Int = 0 -while i < arr.length do - val element: Int = (arr)(i) - sum += element - i += 1 -sum -``` - -Finally cleanups and dead code elimination: - -```scala -val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) -var sum = 0 -var i: Int = 0 -while i < arr.length do - val element: Int = arr(i) - sum += element - i += 1 -sum -``` - -## Find implicits within a macro - -Similarly to the `summonFrom` construct, it is possible to make implicit search available -in a quote context. For this we simply provide `scala.quoted.Expr.summon`: - -```scala -import scala.collection.immutable.{ TreeSet, HashSet } -inline def setFor[T]: Set[T] = ${ setForExpr[T] } - -def setForExpr[T: Type](using Quotes): Expr[Set[T]] = - Expr.summon[Ordering[T]] match - case Some(ord) => '{ new TreeSet[T]()($ord) } - case _ => '{ new HashSet[T] } -``` - -## Relationship with Transparent Inline - -[Inline](./inline.html) documents inlining. The code below introduces a transparent -inline method that can calculate either a value of type `Int` or a value of type -`String`. - -```scala -transparent inline def defaultOf(inline str: String) = - ${ defaultOfImpl('str) } - -def defaultOfImpl(strExpr: Expr[String])(using Quotes): Expr[Any] = - strExpr.valueOrError match - case "int" => '{1} - case "string" => '{"a"} - -// in a separate file -val a: Int = defaultOf("int") -val b: String = defaultOf("string") - -``` - -## Defining a macro and using it in a single project - -It is possible to define macros and use them in the same project as long as the implementation -of the macros does not have run-time dependencies on code in the file where it is used. -It might still have compile-time dependencies on types and quoted code that refers to the use-site file. - -To provide this functionality Scala 3 provides a transparent compilation mode where files that -try to expand a macro but fail because the macro has not been compiled yet are suspended. -If there are any suspended files when the compilation ends, the compiler will automatically restart -compilation of the suspended files using the output of the previous (partial) compilation as macro classpath. -In case all files are suspended due to cyclic dependencies the compilation will fail with an error. - -## Pattern matching on quoted expressions - -It is possible to deconstruct or extract values out of `Expr` using pattern matching. - -`scala.quoted` contains objects that can help extracting values from `Expr`. - -- `scala.quoted.Expr`/`scala.quoted.Exprs`: matches an expression of a value (or list of values) and returns the value (or list of values). -- `scala.quoted.Const`/`scala.quoted.Consts`: Same as `Expr`/`Exprs` but only works on primitive values. -- `scala.quoted.Varargs`: matches an explicit sequence of expressions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`. - -These could be used in the following way to optimize any call to `sum` that has statically known values. - -```scala -inline def sum(inline args: Int*): Int = ${ sumExpr('args) } -private def sumExpr(argsExpr: Expr[Seq[Int]])(using Quotes): Expr[Int] = - argsExpr match - case Varargs(args @ Exprs(argValues)) => - // args is of type Seq[Expr[Int]] - // argValues is of type Seq[Int] - Expr(argValues.sum) // precompute result of sum - case Varargs(argExprs) => // argExprs is of type Seq[Expr[Int]] - val staticSum: Int = argExprs.map(_.value.getOrElse(0)).sum - val dynamicSum: Seq[Expr[Int]] = argExprs.filter(_.value.isEmpty) - dynamicSum.foldLeft(Expr(staticSum))((acc, arg) => '{ $acc + $arg }) - case _ => - '{ $argsExpr.sum } -``` - -### Quoted patterns - -Quoted pattens allow deconstructing complex code that contains a precise structure, types or methods. -Patterns `'{ ... }` can be placed in any location where Scala expects a pattern. - -For example - -```scala -optimize { - sum(sum(1, a, 2), 3, b) -} // should be optimized to 6 + a + b -``` - -```scala -def sum(args: Int*): Int = args.sum -inline def optimize(inline arg: Int): Int = ${ optimizeExpr('arg) } -private def optimizeExpr(body: Expr[Int])(using Quotes): Expr[Int] = - body match - // Match a call to sum without any arguments - case '{ sum() } => Expr(0) - // Match a call to sum with an argument $n of type Int. - // n will be the Expr[Int] representing the argument. - case '{ sum($n) } => n - // Match a call to sum and extracts all its args in an `Expr[Seq[Int]]` - case '{ sum(${Varargs(args)}: _*) } => sumExpr(args) - case body => body - -private def sumExpr(args1: Seq[Expr[Int]])(using Quotes): Expr[Int] = - def flatSumArgs(arg: Expr[Int]): Seq[Expr[Int]] = arg match - case '{ sum(${Varargs(subArgs)}: _*) } => subArgs.flatMap(flatSumArgs) - case arg => Seq(arg) - val args2 = args1.flatMap(flatSumArgs) - val staticSum: Int = args2.map(_.value.getOrElse(0)).sum - val dynamicSum: Seq[Expr[Int]] = args2.filter(_.value.isEmpty) - dynamicSum.foldLeft(Expr(staticSum))((acc, arg) => '{ $acc + $arg }) -``` - -### Recovering precise types using patterns - -Sometimes it is necessary to get a more precise type for an expression. This can be achieved using the following pattern match. - -```scala -def f(expr: Expr[Any])(using Quotes) = expr match - case '{ $x: t } => - // If the pattern match succeeds, then there is - // some type `t` such that - // - `x` is bound to a variable of type `Expr[t]` - // - `t` is bound to a new type `t` and a given - // instance `Type[t]` is provided for it - // That is, we have `x: Expr[t]` and `given Type[t]`, - // for some (unknown) type `t`. -``` - -This might be used to then perform an implicit search as in: - -```scala -extension (inline sc: StringContext) - inline def showMe(inline args: Any*): String = ${ showMeExpr('sc, 'args) } - -private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using Quotes): Expr[String] = - import quotes.reflect.report - argsExpr match - case Varargs(argExprs) => - val argShowedExprs = argExprs.map { - case '{ $arg: tp } => - Expr.summon[Show[tp]] match - case Some(showExpr) => - '{ $showExpr.show($arg) } - case None => - report.error(s"could not find implicit for ${Type.show[Show[tp]]}", arg); '{???} - } - val newArgsExpr = Varargs(argShowedExprs) - '{ $sc.s($newArgsExpr: _*) } - case _ => - // `new StringContext(...).showMeExpr(args: _*)` not an explicit `showMeExpr"..."` - report.error(s"Args must be explicit", argsExpr) - '{???} - -trait Show[-T]: - def show(x: T): String - -// in a different file -given Show[Boolean] with - def show(b: Boolean) = "boolean!" - -println(showMe"${true}") -``` - -### Open code patterns - -Quoted pattern matching also provides higher-order patterns to match open terms. If a quoted term contains a definition, -then the rest of the quote can refer to this definition. - -```scala -'{ - val x: Int = 4 - x * x -} -``` - -To match such a term we need to match the definition and the rest of the code, but we need to explicitly state that the rest of the code may refer to this definition. - -```scala -case '{ val y: Int = $x; $body(y): Int } => -``` - -Here `$x` will match any closed expression while `$body(y)` will match an expression that is closed under `y`. Then -the subexpression of type `Expr[Int]` is bound to `body` as an `Expr[Int => Int]`. The extra argument represents the references to `y`. Usually this expression is used in combination with `Expr.betaReduce` to replace the extra argument. - -```scala -inline def eval(inline e: Int): Int = ${ evalExpr('e) } - -private def evalExpr(e: Expr[Int])(using Quotes): Expr[Int] = e match - case '{ val y: Int = $x; $body(y): Int } => - // body: Expr[Int => Int] where the argument represents - // references to y - evalExpr(Expr.betaReduce('{$body(${evalExpr(x)})})) - case '{ ($x: Int) * ($y: Int) } => - (x.value, y.value) match - case (Some(a), Some(b)) => Expr(a * b) - case _ => e - case _ => e -``` - -```scala -eval { // expands to the code: (16: Int) - val x: Int = 4 - x * x -} -``` - -We can also close over several bindings using `$b(a1, a2, ..., an)`. -To match an actual application we can use braces on the function part `${b}(a1, a2, ..., an)`. - -## More details - -[More details](./macros-spec.html) diff --git a/_scala3-reference/metaprogramming/reflection.md b/_scala3-reference/metaprogramming/reflection.md deleted file mode 100644 index a7c65cff68..0000000000 --- a/_scala3-reference/metaprogramming/reflection.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: "Reflection" -type: section -num: 32 -previous-page: /scala3/reference/metaprogramming/staging -next-page: /scala3/reference/metaprogramming/tasty-inspect ---- - -Reflection enables inspection and construction of Typed Abstract Syntax Trees -(Typed-AST). It may be used on quoted expressions (`quoted.Expr`) and quoted -types (`quoted.Type`) from [Macros](./macros.html) or on full TASTy files. - -If you are writing macros, please first read [Macros](./macros.html). -You may find all you need without using quote reflection. - -## API: From quotes and splices to TASTy reflect trees and back - -With `quoted.Expr` and `quoted.Type` we can compute code but also analyze code -by inspecting the ASTs. [Macros](./macros.html) provide the guarantee that the -generation of code will be type-correct. Using quote reflection will break these -guarantees and may fail at macro expansion time, hence additional explicit -checks must be done. - -To provide reflection capabilities in macros we need to add an implicit parameter -of type `scala.quoted.Quotes` and import `quotes.reflect.*` from it in the scope -where it is used. - -```scala -import scala.quoted.* - -inline def natConst(inline x: Int): Int = ${natConstImpl('{x})} - -def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] = - import quotes.reflect.* - ... -``` - -### Extractors - -`import quotes.reflect.*` will provide all extractors and methods on `quotes.reflect.Tree`s. -For example the `Literal(_)` extractor used below. - -```scala -def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] = - import quotes.reflect.* - val tree: Term = x.asTerm - tree match - case Inlined(_, _, Literal(IntConstant(n))) => - if n <= 0 then - report.error("Parameter must be natural number") - '{0} - else - tree.asExprOf[Int] - case _ => - report.error("Parameter must be a known constant") - '{0} -``` - -We can easily know which extractors are needed using `Printer.TreeStructure.show`, -which returns the string representation the structure of the tree. Other printers -can also be found in the `Printer` module. - -```scala -tree.show(using Printer.TreeStructure) -// or -Printer.TreeStructure.show(tree) -``` - -The methods `quotes.reflect.Term.{asExpr, asExprOf}` provide a way to go back to -a `quoted.Expr`. Note that `asExpr` returns a `Expr[Any]`. On the other hand -`asExprOf[T]` returns a `Expr[T]`, if the type does not conform to it an exception -will be thrown at runtime. - -### Positions - -The `Position` in the context provides an `ofMacroExpansion` value. It corresponds -to the expansion site for macros. The macro authors can obtain various information -about that expansion site. The example below shows how we can obtain position -information such as the start line, the end line or even the source code at the -expansion point. - -```scala -def macroImpl()(quotes: Quotes): Expr[Unit] = - import quotes.reflect.* - val pos = Position.ofMacroExpansion - - val path = pos.sourceFile.jpath.toString - val start = pos.start - val end = pos.end - val startLine = pos.startLine - val endLine = pos.endLine - val startColumn = pos.startColumn - val endColumn = pos.endColumn - val sourceCode = pos.sourceCode - ... -``` - -### Tree Utilities - -`quotes.reflect` contains three facilities for tree traversal and -transformation. - -`TreeAccumulator` ties the knot of a traversal. By calling `foldOver(x, tree)(owner)` -we can dive into the `tree` node and start accumulating values of type `X` (e.g., -of type `List[Symbol]` if we want to collect symbols). The code below, for -example, collects the `val` definitions in the tree. - -```scala -def collectPatternVariables(tree: Tree)(using ctx: Context): List[Symbol] = - val acc = new TreeAccumulator[List[Symbol]]: - def foldTree(syms: List[Symbol], tree: Tree)(owner: Symbol): List[Symbol] = tree match - case ValDef(_, _, rhs) => - val newSyms = tree.symbol :: syms - foldTree(newSyms, body)(tree.symbol) - case _ => - foldOverTree(syms, tree)(owner) - acc(Nil, tree) -``` - -A `TreeTraverser` extends a `TreeAccumulator` and performs the same traversal -but without returning any value. Finally, a `TreeMap` performs a transformation. - -#### ValDef.let - -`quotes.reflect.ValDef` also offers a method `let` that allows us to bind the `rhs` (right-hand side) to a `val` and use it in `body`. -Additionally, `lets` binds the given `terms` to names and allows to use them in the `body`. -Their type definitions are shown below: - -```scala -def let(rhs: Term)(body: Ident => Term): Term = ... - -def lets(terms: List[Term])(body: List[Term] => Term): Term = ... -``` diff --git a/_scala3-reference/metaprogramming/simple-smp.md b/_scala3-reference/metaprogramming/simple-smp.md deleted file mode 100644 index 6a7e2c0fba..0000000000 --- a/_scala3-reference/metaprogramming/simple-smp.md +++ /dev/null @@ -1,232 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "The Meta-theory of Symmetric Metaprogramming" ---- - -This note presents a simplified variant of -[principled metaprogramming](./macros.html) -and sketches its soundness proof. The variant treats only dialogues -between two stages. A program can have quotes which can contain -splices (which can contain quotes, which can contain splices, and so -on). Or the program could start with a splice with embedded -quotes. The essential restriction is that (1) a term can contain top-level -quotes or top-level splices, but not both, and (2) quotes cannot appear -directly inside quotes and splices cannot appear directly inside -splices. In other words, the universe is restricted to two phases -only. - -Under this restriction we can simplify the typing rules so that there are -always exactly two environments instead of having a stack of environments. -The variant presented here differs from the full calculus also in that we -replace evaluation contexts with contextual typing rules. While this -is more verbose, it makes it easier to set up the meta theory. - -## Syntax -``` -Terms t ::= x variable - (x: T) => t lambda - t t application - ’t quote - ~t splice - -Simple terms u ::= x | (x: T) => u | u u - -Values v ::= (x: T) => t lambda - ’u quoted value - -Types T ::= A base type - T -> T function type - ’T quoted type -``` -## Operational semantics - -### Evaluation -``` - ((x: T) => t) v --> [x := v]t - - t1 --> t2 - --------------- - t1 t --> t2 t - - t1 --> t2 - --------------- - v t1 --> v t2 - - t1 ==> t2 - ------------- - ’t1 --> ’t2 -``` - -### Splicing -``` - ~’u ==> u - - t1 ==> t2 - ------------------------------- - (x: T) => t1 ==> (x: T) => t2 - - t1 ==> t2 - --------------- - t1 t ==> t2 t - - t1 ==> t2 - --------------- - u t1 ==> u t2 - - t1 --> t2 - ------------- - ~t1 ==> ~t2 - -``` -## Typing Rules - -Typing judgments are of the form `E1 * E2 |- t: T` where `E1, E2` are environments and -`*` is one of `~` and `’`. -``` - x: T in E2 - --------------- - E1 * E2 |- x: T - - - E1 * E2, x: T1 |- t: T2 - -------------------------------- - E1 * E2 |- (x: T1) => t: T -> T2 - - - E1 * E2 |- t1: T2 -> T E1 * E2 |- t2: T2 - ------------------------------------------- - E1 * E2 |- t1 t2: T - - - E2 ’ E1 |- t: T - ----------------- - E1 ~ E2 |- ’t: ’T - - - E2 ~ E1 |- t: ’T - ---------------- - E1 ’ E2 |- ~t: T -``` - -(Curiously, this looks a bit like a Christmas tree). - -## Soundness - -The meta-theory typically requires mutual inductions over two judgments. - -### Progress Theorem - - 1. If `E1 ~ |- t: T` then either `t = v` for some value `v` or `t --> t2` for some term `t2`. - 2. If ` ’ E2 |- t: T` then either `t = u` for some simple term `u` or `t ==> t2` for some term `t2`. - -Proof by structural induction over terms. - -To prove (1): - - - the cases for variables, lambdas and applications are as in [STLC](https://en.wikipedia.org/wiki/Simply_typed_lambda_calculus). - - If `t = ’t2`, then by inversion we have ` ’ E1 |- t2: T2` for some type `T2`. - By the second [induction hypothesis](https://en.wikipedia.org/wiki/Mathematical_induction) (I.H.), we have one of: - - `t2 = u`, hence `’t2` is a value, - - `t2 ==> t3`, hence `’t2 --> ’t3`. - - The case `t = ~t2` is not typable. - -To prove (2): - - - If `t = x` then `t` is a simple term. - - If `t = (x: T) => t2`, then either `t2` is a simple term, in which case `t` is as well. - Or by the second I.H. `t2 ==> t3`, in which case `t ==> (x: T) => t3`. - - If `t = t1 t2` then one of three cases applies: - - - `t1` and `t2` are a simple term, then `t` is as well a simple term. - - `t1` is not a simple term. Then by the second I.H., `t1 ==> t12`, hence `t ==> t12 t2`. - - `t1` is a simple term but `t2` is not. Then by the second I.H. `t2 ==> t22`, hence `t ==> t1 t22`. - - - The case `t = ’t2` is not typable. - - If `t = ~t2` then by inversion we have `E2 ~ |- t2: ’T2`, for some type `T2`. - By the first I.H., we have one of - - - `t2 = v`. Since `t2: ’T2`, we must have `v = ’u`, for some simple term `u`, hence `t = ~’u`. - By quote-splice reduction, `t ==> u`. - - `t2 --> t3`. Then by the context rule for `’t`, `t ==> ’t3`. - - -### Substitution Lemma - - 1. If `E1 ~ E2 |- s: S` and `E1 ~ E2, x: S |- t: T` then `E1 ~ E2 |- [x := s]t: T`. - 2. If `E1 ~ E2 |- s: S` and `E2, x: S ’ E1 |- t: T` then `E2 ’ E1 |- [x := s]t: T`. - -The proofs are by induction on typing derivations for `t`, analogous -to the proof for STL (with (2) a bit simpler than (1) since we do not -need to swap lambda bindings with the bound variable `x`). The -arguments that link the two hypotheses are as follows. - -To prove (1), let `t = ’t1`. Then `T = ’T1` for some type `T1` and the last typing rule is -``` - E2, x: S ’ E1 |- t1: T1 - ------------------------- - E1 ~ E2, x: S |- ’t1: ’T1 -``` -By the second I.H. `E2 ’ E1 |- [x := s]t1: T1`. By typing, `E1 ~ E2 |- ’[x := s]t1: ’T1`. -Since `[x := s]t = [x := s](’t1) = ’[x := s]t1` we get `[x := s]t: ’T1`. - -To prove (2), let `t = ~t1`. Then the last typing rule is -``` - E1 ~ E2, x: S |- t1: ’T - ----------------------- - E2, x: S ’ E1 |- ~t1: T -``` -By the first I.H., `E1 ~ E2 |- [x := s]t1: ’T`. By typing, `E2 ’ E1 |- ~[x := s]t1: T`. -Since `[x := s]t = [x := s](~t1) = ~[x := s]t1` we get `[x := s]t: T`. - - -### Preservation Theorem - - 1. If `E1 ~ E2 |- t1: T` and `t1 --> t2` then `E1 ~ E2 |- t2: T`. - 2. If `E1 ’ E2 |- t1: T` and `t1 ==> t2` then `E1 ’ E2 |- t2: T`. - -The proof is by structural induction on evaluation derivations. The proof of (1) is analogous -to the proof for STL, using the substitution lemma for the beta reduction case, with the addition of reduction of quoted terms, which goes as follows: - - - Assume the last rule was - ``` - t1 ==> t2 - ------------- - ’t1 --> ’t2 - ``` - By inversion of typing rules, we must have `T = ’T1` for some type `T1` such that `t1: T1`. - By the second I.H., `t2: T1`, hence `’t2: `T1`. - - -To prove (2): - - - Assume the last rule was `~’u ==> u`. The typing proof of `~’u` must have the form - - ``` - E1 ’ E2 |- u: T - ----------------- - E1 ~ E2 |- ’u: ’T - ----------------- - E1 ’ E2 |- ~’u: T - ``` - Hence, `E1 ’ E2 |- u: T`. - - - Assume the last rule was - ``` - t1 ==> t2 - ------------------------------- - (x: S) => t1 ==> (x: T) => t2 - ``` - By typing inversion, `E1 ' E2, x: S |- t1: T1` for some type `T1` such that `T = S -> T1`. - By the I.H, `t2: T1`. By the typing rule for lambdas the result follows. - - - The context rules for applications are equally straightforward. - - - Assume the last rule was - ``` - t1 ==> t2 - ------------- - ~t1 ==> ~t2 - ``` - By inversion of typing rules, we must have `t1: ’T`. - By the first I.H., `t2: ’T`, hence `~t2: T`. diff --git a/_scala3-reference/metaprogramming/staging.md b/_scala3-reference/metaprogramming/staging.md deleted file mode 100644 index bb35f51055..0000000000 --- a/_scala3-reference/metaprogramming/staging.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: "Runtime Multi-Stage Programming" -type: section -num: 31 -previous-page: /scala3/reference/metaprogramming/macros -next-page: /scala3/reference/metaprogramming/reflection ---- - -The framework expresses at the same time compile-time metaprogramming and -multi-stage programming. We can think of compile-time metaprogramming as a -two stage compilation process: one that we write the code in top-level splices, -that will be used for code generation (macros) and one that will perform all -necessary evaluations at compile-time and an object program that we will run -as usual. What if we could synthesize code at run-time and offer one extra stage -to the programmer? Then we can have a value of type `Expr[T]` at run-time that we -can essentially treat as a typed-syntax tree that we can either _show_ as a -string (pretty-print) or compile and run. If the number of quotes exceeds the -number of splices by more than one (effectively handling at run-time values of type -`Expr[Expr[T]]`, `Expr[Expr[Expr[T]]]`, ...) then we talk about Multi-Stage -Programming. - -The motivation behind this _paradigm_ is to let runtime information affect or -guide code-generation. - -Intuition: The phase in which code is run is determined by the difference -between the number of splice scopes and quote scopes in which it is embedded. - - - If there are more splices than quotes, the code is run at compile-time i.e. - as a macro. In the general case, this means running an interpreter that - evaluates the code, which is represented as a typed abstract syntax tree. The - interpreter can fall back to reflective calls when evaluating an application - of a previously compiled method. If the splice excess is more than one, it - would mean that a macro’s implementation code (as opposed to the code it - expands to) invokes other macros. If macros are realized by interpretation, - this would lead to towers of interpreters, where the first interpreter would - itself interpret an interpreter code that possibly interprets another - interpreter and so on. - - - If the number of splices equals the number of quotes, the code is compiled - and run as usual. - - - If the number of quotes exceeds the number of splices, the code is staged. - That is, it produces a typed abstract syntax tree or type structure at - run-time. A quote excess of more than one corresponds to multi-staged - programming. - -Providing an interpreter for the full language is quite difficult, and it is -even more difficult to make that interpreter run efficiently. So we currently -impose the following restrictions on the use of splices. - - 1. A top-level splice must appear in an inline method (turning that method - into a macro) - - 2. The splice must call a previously compiled - method passing quoted arguments, constant arguments or inline arguments. - - 3. Splices inside splices (but no intervening quotes) are not allowed. - - -## API - -The framework as discussed so far allows code to be staged, i.e. be prepared -to be executed at a later stage. To run that code, there is another method -in class `Expr` called `run`. Note that `$` and `run` both map from `Expr[T]` -to `T` but only `$` is subject to the [PCP](./macros.html#the-phase-consistency-principle), whereas `run` is just a normal method. -`scala.quoted.staging.run` provides a `Quotes` that can be used to show the expression in its scope. -On the other hand `scala.quoted.staging.withQuotes` provides a `Quotes` without evaluating the expression. - -```scala -package scala.quoted.staging - -def run[T](expr: Quotes ?=> Expr[T])(using Compiler): T = ... - -def withQuotes[T](thunk: Quotes ?=> T)(using Compiler): T = ... -``` - -## Create a new Scala 3 project with staging enabled - -```shell -sbt new scala/scala3-staging.g8 -``` - -From [`scala/scala3-staging.g8`](https://github.com/scala/scala3-staging.g8). - -It will create a project with the necessary dependencies and some examples. - -In case you prefer to create the project on your own, make sure to define the following dependency in your [`build.sbt` build definition](https://www.scala-sbt.org/1.x/docs/Basic-Def.html) - -```scala -libraryDependencies += "org.scala-lang" %% "scala3-staging" % scalaVersion.value -``` - -and in case you use `scalac`/`scala` directly, then use the `-with-compiler` flag for both: - -```shell -scalac -with-compiler -d out Test.scala -scala -with-compiler -classpath out Test -``` - -## Example - -Now take exactly the same example as in [Macros](./macros.html). Assume that we -do not want to pass an array statically but generate code at run-time and pass -the value, also at run-time. Note, how we make a future-stage function of type -`Expr[Array[Int] => Int]` in line 6 below. Using `staging.run { ... }` we can evaluate an -expression at runtime. Within the scope of `staging.run` we can also invoke `show` on an expression -to get a source-like representation of the expression. - -```scala -import scala.quoted.* - -// make available the necessary compiler for runtime code generation -given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) - -val f: Array[Int] => Int = staging.run { - val stagedSum: Expr[Array[Int] => Int] = - '{ (arr: Array[Int]) => ${sum('arr)}} - println(stagedSum.show) // Prints "(arr: Array[Int]) => { var sum = 0; ... }" - stagedSum -} - -f.apply(Array(1, 2, 3)) // Returns 6 -``` diff --git a/_scala3-reference/metaprogramming/tasty-inspect.md b/_scala3-reference/metaprogramming/tasty-inspect.md deleted file mode 100644 index 32bc2efa43..0000000000 --- a/_scala3-reference/metaprogramming/tasty-inspect.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: "TASTy Inspection" -type: section -num: 33 -previous-page: /scala3/reference/metaprogramming/reflection -next-page: /scala3/reference/other-new-features ---- - -```scala -libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersion.value -``` - -TASTy files contain the full typed tree of a class including source positions -and documentation. This is ideal for tools that analyze or extract semantic -information from the code. To avoid the hassle of working directly with the TASTy -file we provide the `Inspector` which loads the contents and exposes it -through the TASTy reflect API. - -## Inspecting TASTy files - -To inspect the trees of a TASTy file a consumer can be defined in the following way. - -```scala -import scala.quoted.* -import scala.tasty.inspector.* - -class MyInspector extends Inspector: - def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = - import quotes.reflect.* - for tasty <- tastys do - val tree = tasty.ast - // Do something with the tree -``` - -Then the consumer can be instantiated with the following code to get the tree of the `foo/Bar.tasty` file. - -```scala -object Test: - def main(args: Array[String]): Unit = - val tastyFiles = List("foo/Bar.tasty") - TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector) -``` - -Note that if we need to run the main (in the example below defined in an object called `Test`) after compilation we need to make the compiler available to the runtime: - -```shell -scalac -d out Test.scala -scala -with-compiler -classpath out Test -``` - -## Template project - -Using sbt version `1.1.5+`, do: - -```shell -sbt new scala/scala3-tasty-inspector.g8 -``` - -in the folder where you want to clone the template. diff --git a/_scala3-reference/new-types.md b/_scala3-reference/new-types.md deleted file mode 100644 index e5d26e9c59..0000000000 --- a/_scala3-reference/new-types.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: New Types -type: chapter -num: 2 -previous-page: /scala3/reference/overview -next-page: /scala3/reference/new-types/intersection-types ---- - -This chapter documents the new types introduced in Scala 3. diff --git a/_scala3-reference/new-types/dependent-function-types-spec.md b/_scala3-reference/new-types/dependent-function-types-spec.md deleted file mode 100644 index 07a07b7428..0000000000 --- a/_scala3-reference/new-types/dependent-function-types-spec.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Dependent Function Types - More Details" ---- - -Initial implementation in [PR #3464](https://github.com/lampepfl/dotty/pull/3464). - -## Syntax - -``` -FunArgTypes ::= InfixType - | ‘(’ [ FunArgType {',' FunArgType } ] ‘)’ - | ‘(’ TypedFunParam {',' TypedFunParam } ‘)’ -TypedFunParam ::= id ‘:’ Type -``` - -Dependent function types associate to the right, e.g. -`(s: S) => (t: T) => U` is the same as `(s: S) => ((t: T) => U)`. - -## Implementation - -Dependent function types are shorthands for class types that define `apply` -methods with a dependent result type. Dependent function types desugar to -refinement types of `scala.FunctionN`. A dependent function type -`(x1: K1, ..., xN: KN) => R` of arity `N` translates to: - -```scala -FunctionN[K1, ..., Kn, R']: - def apply(x1: K1, ..., xN: KN): R -``` - -where the result type parameter `R'` is the least upper approximation of the -precise result type `R` without any reference to value parameters `x1, ..., xN`. - -The syntax and semantics of anonymous dependent functions is identical to the -one of regular functions. Eta expansion is naturally generalized to produce -dependent function types for methods with dependent result types. - -Dependent functions can be implicit, and generalize to arity `N > 22` in the -same way that other functions do, see -[the corresponding documentation](../dropped-features/limit22.html). - -## Examples - -The example below defines a trait `C` and the two dependent function types -`DF` and `IDF` and prints the results of the respective function applications: - -[depfuntype.scala]: https://github.com/lampepfl/dotty/blob/master/tests/pos/depfuntype.scala - -```scala -trait C { type M; val m: M } - -type DF = (x: C) => x.M - -type IDF = (x: C) ?=> x.M - -@main def test = - val c = new C { type M = Int; val m = 3 } - - val depfun: DF = (x: C) => x.m - val t = depfun(c) - println(s"t=$t") // prints "t=3" - - val idepfun: IDF = summon[C].m - val u = idepfun(using c) - println(s"u=$u") // prints "u=3" - -``` - -In the following example the depend type `f.Eff` refers to the effect type `CanThrow`: - -[eff-dependent.scala]: https://github.com/lampepfl/dotty/blob/master/tests/run/eff-dependent.scala - -```scala -trait Effect - -// Type X => Y -abstract class Fun[-X, +Y]: - type Eff <: Effect - def apply(x: X): Eff ?=> Y - -class CanThrow extends Effect -class CanIO extends Effect - -given ct: CanThrow = new CanThrow -given ci: CanIO = new CanIO - -class I2S extends Fun[Int, String]: - type Eff = CanThrow - def apply(x: Int) = x.toString - -class S2I extends Fun[String, Int]: - type Eff = CanIO - def apply(x: String) = x.length - -// def map(f: A => B)(xs: List[A]): List[B] -def map[A, B](f: Fun[A, B])(xs: List[A]): f.Eff ?=> List[B] = - xs.map(f.apply) - -// def mapFn[A, B]: (A => B) -> List[A] -> List[B] -def mapFn[A, B]: (f: Fun[A, B]) => List[A] => f.Eff ?=> List[B] = - f => xs => map(f)(xs) - -// def compose(f: A => B)(g: B => C)(x: A): C -def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): - f.Eff ?=> g.Eff ?=> C = - g(f(x)) - -// def composeFn: (A => B) -> (B => C) -> A -> C -def composeFn[A, B, C]: - (f: Fun[A, B]) => (g: Fun[B, C]) => A => f.Eff ?=> g.Eff ?=> C = - f => g => x => compose(f)(g)(x) - -@main def test = - val i2s = new I2S - val s2i = new S2I - - assert(mapFn(i2s)(List(1, 2, 3)).mkString == "123") - assert(composeFn(i2s)(s2i)(22) == 2) -``` - -### Type Checking - -After desugaring no additional typing rules are required for dependent function types. diff --git a/_scala3-reference/new-types/dependent-function-types.md b/_scala3-reference/new-types/dependent-function-types.md deleted file mode 100644 index ffa9012c4a..0000000000 --- a/_scala3-reference/new-types/dependent-function-types.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Dependent Function Types" -type: section -num: 7 -previous-page: /scala3/reference/new-types/match-types -next-page: /scala3/reference/new-types/polymorphic-function-types ---- - -A dependent function type is a function type whose result depends -on the function's parameters. For example: - -```scala -trait Entry { type Key; val key: Key } - -def extractKey(e: Entry): e.Key = e.key // a dependent method - -val extractor: (e: Entry) => e.Key = extractKey // a dependent function value -// ^^^^^^^^^^^^^^^^^^^ -// a dependent function type -``` - -Scala already has _dependent methods_, i.e. methods where the result -type refers to some of the parameters of the method. Method -`extractKey` is an example. Its result type, `e.Key` refers to its -parameter `e` (we also say, `e.Key` _depends_ on `e`). But so far it -was not possible to turn such methods into function values, so that -they can be passed as parameters to other functions, or returned as -results. Dependent methods could not be turned into functions simply -because there was no type that could describe them. - -In Scala 3 this is now possible. The type of the `extractor` value above is - -```scala -(e: Entry) => e.Key -``` - -This type describes function values that take any argument `e` of type -`Entry` and return a result of type `e.Key`. - -Recall that a normal function type `A => B` is represented as an -instance of the [`Function1` trait](https://scala-lang.org/api/3.x/scala/Function1.html) -(i.e. `Function1[A, B]`) and analogously for functions with more parameters. Dependent functions -are also represented as instances of these traits, but they get an additional -refinement. In fact, the dependent function type above is just syntactic sugar for - -```scala -Function1[Entry, Entry#Key]: - def apply(e: Entry): e.Key -``` - -[More details](./dependent-function-types-spec.html) diff --git a/_scala3-reference/new-types/intersection-types-spec.md b/_scala3-reference/new-types/intersection-types-spec.md deleted file mode 100644 index aeceeac048..0000000000 --- a/_scala3-reference/new-types/intersection-types-spec.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Intersection Types - More Details" ---- - -## Syntax - -Syntactically, the type `S & T` is an infix type, where the infix operator is `&`. -The operator `&` is a normal identifier -with the usual precedence and subject to usual resolving rules. -Unless shadowed by another definition, it resolves to the type `scala.&`, -which acts as a type alias to an internal representation of intersection types. - -``` -Type ::= ...| InfixType -InfixType ::= RefinedType {id [nl] RefinedType} -``` - -## Subtyping Rules - -``` -T <: A T <: B ----------------- - T <: A & B - - A <: T ----------------- - A & B <: T - - B <: T ----------------- - A & B <: T -``` - -From the rules above, we can show that `&` is _commutative_: `A & B <: B & A` for any type `A` and `B`. - -``` - B <: B A <: A ----------- ----------- -A & B <: B A & B <: A ---------------------------- - A & B <: B & A -``` - -In another word, `A & B` is the same type as `B & A`, in the sense that the two types -have the same values and are subtypes of each other. - -If `C` is a type constructor, then `C[A] & C[B]` can be simplified using the following three rules: - -- If `C` is covariant, `C[A] & C[B] ~> C[A & B]` -- If `C` is contravariant, `C[A] & C[B] ~> C[A | B]` -- If `C` is non-variant, emit a compile error - -When `C` is covariant, `C[A & B] <: C[A] & C[B]` can be derived: - -``` - A <: A B <: B - ---------- --------- - A & B <: A A & B <: B ---------------- ----------------- -C[A & B] <: C[A] C[A & B] <: C[B] ------------------------------------------- - C[A & B] <: C[A] & C[B] -``` - -When `C` is contravariant, `C[A | B] <: C[A] & C[B]` can be derived: - -``` - A <: A B <: B - ---------- --------- - A <: A | B B <: A | B -------------------- ---------------- -C[A | B] <: C[A] C[A | B] <: C[B] --------------------------------------------------- - C[A | B] <: C[A] & C[B] -``` - -## Erasure - -The erased type for `S & T` is the erased _glb_ (greatest lower bound) of the -erased type of `S` and `T`. The rules for erasure of intersection types are given -below in pseudocode: - -``` -|S & T| = glb(|S|, |T|) - -glb(JArray(A), JArray(B)) = JArray(glb(A, B)) -glb(JArray(T), _) = JArray(T) -glb(_, JArray(T)) = JArray(T) -glb(A, B) = A if A extends B -glb(A, B) = B if B extends A -glb(A, _) = A if A is not a trait -glb(_, B) = B if B is not a trait -glb(A, _) = A // use first -``` - -In the above, `|T|` means the erased type of `T`, `JArray` refers to -the type of Java Array. - -See also: [`TypeErasure#erasedGlb`](https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/TypeErasure.scala#L289). - -## Relationship with Compound Type (`with`) - -Intersection types `A & B` replace compound types `A with B` in Scala 2. For the -moment, the syntax `A with B` is still allowed and interpreted as `A & B`, but -its usage as a type (as opposed to in a `new` or `extends` clause) will be -deprecated and removed in the future. diff --git a/_scala3-reference/new-types/intersection-types.md b/_scala3-reference/new-types/intersection-types.md deleted file mode 100644 index e912ec6994..0000000000 --- a/_scala3-reference/new-types/intersection-types.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Intersection Types -type: section -num: 3 -previous-page: /scala3/reference/new-types -next-page: /scala3/reference/new-types/union-types ---- - -Used on types, the `&` operator creates an intersection type. - -## Type Checking - -The type `S & T` represents values that are of the type `S` and `T` at the same time. - -```scala -trait Resettable: - def reset(): Unit - -trait Growable[T]: - def add(t: T): Unit - -def f(x: Resettable & Growable[String]) = - x.reset() - x.add("first") -``` - -The parameter `x` is required to be _both_ a `Resettable` and a -`Growable[String]`. - -The members of an intersection type `A & B` are all the members of `A` and all -the members of `B`. For instance `Resettable & Growable[String]` -has member methods `reset` and `add`. - -`&` is _commutative_: `A & B` is the same type as `B & A`. - -If a member appears in both `A` and `B`, its type in `A & B` is the intersection -of its type in `A` and its type in `B`. For instance, assume the definitions: - -```scala -trait A: - def children: List[A] - -trait B: - def children: List[B] - -val x: A & B = new C -val ys: List[A & B] = x.children -``` - -The type of `children` in `A & B` is the intersection of `children`'s -type in `A` and its type in `B`, which is `List[A] & List[B]`. This -can be further simplified to `List[A & B]` because `List` is -covariant. - -One might wonder how the compiler could come up with a definition for -`children` of type `List[A & B]` since what is given are `children` -definitions of type `List[A]` and `List[B]`. The answer is the compiler does not -need to. `A & B` is just a type that represents a set of requirements for -values of the type. At the point where a value is _constructed_, one -must make sure that all inherited members are correctly defined. -So if one defines a class `C` that inherits `A` and `B`, one needs -to give at that point a definition of a `children` method with the required type. - -```scala -class C extends A, B: - def children: List[A & B] = ??? -``` - - -[More details](./intersection-types-spec.html) diff --git a/_scala3-reference/new-types/match-types.md b/_scala3-reference/new-types/match-types.md deleted file mode 100644 index 70d34ff416..0000000000 --- a/_scala3-reference/new-types/match-types.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: "Match Types" -type: section -num: 6 -previous-page: /scala3/reference/new-types/type-lambdas -next-page: /scala3/reference/new-types/dependent-function-types ---- - -A match type reduces to one of its right-hand sides, depending on the type of -its scrutinee. For example: - -```scala -type Elem[X] = X match - case String => Char - case Array[t] => t - case Iterable[t] => t -``` - -This defines a type that reduces as follows: - -```scala -Elem[String] =:= Char -Elem[Array[Int]] =:= Int -Elem[List[Float]] =:= Float -Elem[Nil.type] =:= Nothing -``` - -Here `=:=` is understood to mean that left and right-hand sides are mutually -subtypes of each other. - -In general, a match type is of the form - -```scala -S match { P1 => T1 ... Pn => Tn } -``` - -where `S`, `T1`, ..., `Tn` are types and `P1`, ..., `Pn` are type patterns. Type -variables in patterns start with a lower case letter, as usual. - -Match types can form part of recursive type definitions. Example: - -```scala -type LeafElem[X] = X match - case String => Char - case Array[t] => LeafElem[t] - case Iterable[t] => LeafElem[t] - case AnyVal => X -``` - -Recursive match type definitions can also be given an upper bound, like this: - -```scala -type Concat[Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match - case EmptyTuple => Ys - case x *: xs => x *: Concat[xs, Ys] -``` - -In this definition, every instance of `Concat[A, B]`, whether reducible or not, -is known to be a subtype of `Tuple`. This is necessary to make the recursive -invocation `x *: Concat[xs, Ys]` type check, since `*:` demands a `Tuple` as its -right operand. - -## Dependent Typing - -Match types can be used to define dependently typed methods. For instance, here -is the value level counterpart to the `LeafElem` type defined above (note the -use of the match type as the return type): - -```scala -def leafElem[X](x: X): LeafElem[X] = x match - case x: String => x.charAt(0) - case x: Array[t] => leafElem(x(0)) - case x: Iterable[t] => leafElem(x.head) - case x: AnyVal => x -``` - -This special mode of typing for match expressions is only used when the -following conditions are met: - -1. The match expression patterns do not have guards -2. The match expression scrutinee's type is a subtype of the match type - scrutinee's type -3. The match expression and the match type have the same number of cases -4. The match expression patterns are all [Typed Patterns](https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#typed-patterns), - and these types are `=:=` to their corresponding type patterns in the match - type - -## Representation of Match Types - -The internal representation of a match type -``` -S match { P1 => T1 ... Pn => Tn } -``` -is `Match(S, C1, ..., Cn) <: B` where each case `Ci` is of the form -``` -[Xs] =>> P => T -``` - -Here, `[Xs]` is a type parameter clause of the variables bound in pattern `Pi`. -If there are no bound type variables in a case, the type parameter clause is -omitted and only the function type `P => T` is kept. So each case is either a -unary function type or a type lambda over a unary function type. - -`B` is the declared upper bound of the match type, or `Any` if no such bound is -given. We will leave it out in places where it does not matter for the -discussion. The scrutinee, bound, and pattern types must all be first-order -types. - -## Match Type Reduction - -Match type reduction follows the semantics of match expressions, that is, a -match type of the form `S match { P1 => T1 ... Pn => Tn }` reduces to `Ti` if -and only if `s: S match { _: P1 => T1 ... _: Pn => Tn }` evaluates to a value of -type `Ti` for all `s: S`. - -The compiler implements the following reduction algorithm: - -- If the scrutinee type `S` is an empty set of values (such as `Nothing` or - `String & Int`), do not reduce. -- Sequentially consider each pattern `Pi` - - If `S <: Pi` reduce to `Ti`. - - Otherwise, try constructing a proof that `S` and `Pi` are disjoint, or, in - other words, that no value `s` of type `S` is also of type `Pi`. - - If such proof is found, proceed to the next case (`Pi+1`), otherwise, do - not reduce. - -Disjointness proofs rely on the following properties of Scala types: - -1. Single inheritance of classes -2. Final classes cannot be extended -3. Constant types with distinct values are nonintersecting -4. Singleton paths to distinct values are nonintersecting, such as `object` definitions or singleton enum cases. - -Type parameters in patterns are minimally instantiated when computing `S <: Pi`. -An instantiation `Is` is _minimal_ for `Xs` if all type variables in `Xs` that -appear covariantly and nonvariantly in `Is` are as small as possible and all -type variables in `Xs` that appear contravariantly in `Is` are as large as -possible. Here, "small" and "large" are understood with respect to `<:`. - -For simplicity, we have omitted constraint handling so far. The full formulation -of subtyping tests describes them as a function from a constraint and a pair of -types to either _success_ and a new constraint or _failure_. In the context of -reduction, the subtyping test `S <: [Xs := Is] P` is understood to leave the -bounds of all variables in the input constraint unchanged, i.e. existing -variables in the constraint cannot be instantiated by matching the scrutinee -against the patterns. - -## Subtyping Rules for Match Types - -The following rules apply to match types. For simplicity, we omit environments -and constraints. - -1. The first rule is a structural comparison between two match types: - - ``` - S match { P1 => T1 ... Pm => Tm } <: T match { Q1 => U1 ... Qn => Un } - ``` - - if - - ``` - S =:= T, m >= n, Pi =:= Qi and Ti <: Ui for i in 1..n - ``` - - I.e. scrutinees and patterns must be equal and the corresponding bodies must - be subtypes. No case re-ordering is allowed, but the subtype can have more - cases than the supertype. - -2. The second rule states that a match type and its redux are mutual subtypes. - - ``` - S match { P1 => T1 ... Pn => Tn } <: U - U <: S match { P1 => T1 ... Pn => Tn } - ``` - - if - - `S match { P1 => T1 ... Pn => Tn }` reduces to `U` - -3. The third rule states that a match type conforms to its upper bound: - - ``` - (S match { P1 => T1 ... Pn => Tn } <: B) <: B - ``` - -## Termination - -Match type definitions can be recursive, which means that it's possible to run -into an infinite loop while reducing match types. - -Since reduction is linked to subtyping, we already have a cycle detection -mechanism in place. As a result, the following will already give a reasonable -error message: - -```scala -type L[X] = X match - case Int => L[X] - -def g[X]: L[X] = ??? -``` - -```scala - | val x: Int = g[Int] - | ^ - |Recursion limit exceeded. - |Maybe there is an illegal cyclic reference? - |If that's not the case, you could also try to - |increase the stacksize using the -Xss JVM option. - |A recurring operation is (inner to outer): - | - | subtype LazyRef(Test.L[Int]) <:< Int -``` - -Internally, the Scala compiler detects these cycles by turning selected stack overflows into -type errors. If there is a stack overflow during subtyping, the exception will -be caught and turned into a compile-time error that indicates a trace of the -subtype tests that caused the overflow without showing a full stack trace. - -## Variance Laws for Match Types - -**Note:** This section does not reflect the current implementation. - -Within a match type `Match(S, Cs) <: B`, all occurrences of type variables count -as covariant. By the nature of the cases `Ci` this means that occurrences in -pattern position are contravariant (since patterns are represented as function -type arguments). - -## Related Work - -Match types have similarities with -[closed type families](https://wiki.haskell.org/GHC/Type_families) in Haskell. -Some differences are: - -- Subtyping instead of type equalities. -- Match type reduction does not tighten the underlying constraint, whereas type - family reduction does unify. This difference in approach mirrors the - difference between local type inference in Scala and global type inference in - Haskell. - -Match types are also similar to Typescript's -[conditional types](https://github.com/Microsoft/TypeScript/pull/21316). The -main differences here are: - - - Conditional types only reduce if both the scrutinee and pattern are ground, - whereas match types also work for type parameters and abstract types. - - Match types support direct recursion. - - Conditional types distribute through union types. diff --git a/_scala3-reference/new-types/polymorphic-function-types.md b/_scala3-reference/new-types/polymorphic-function-types.md deleted file mode 100644 index 58224ef0de..0000000000 --- a/_scala3-reference/new-types/polymorphic-function-types.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: "Polymorphic Function Types" -type: section -num: 8 -previous-page: /scala3/reference/new-types/dependent-function-types -next-page: /scala3/reference/enums ---- - -A polymorphic function type is a function type which accepts type parameters. -For example: - -```scala -// A polymorphic method: -def foo[A](xs: List[A]): List[A] = xs.reverse - -// A polymorphic function value: -val bar: [A] => List[A] => List[A] -// ^^^^^^^^^^^^^^^^^^^^^^^^^ -// a polymorphic function type - = [A] => (xs: List[A]) => foo[A](xs) -``` - -Scala already has _polymorphic methods_, i.e. methods which accepts type parameters. -Method `foo` above is an example, accepting a type parameter `A`. -So far, it -was not possible to turn such methods into polymorphic function values like `bar` above, -which can be passed as parameters to other functions, or returned as results. - -In Scala 3 this is now possible. The type of the `bar` value above is - -```scala -[A] => List[A] => List[A] -``` - -This type describes function values which take a type `A` as a parameter, -then take a list of type `List[A]`, and return a list of the same type `List[A]`. - -[More details](https://github.com/lampepfl/dotty/pull/4672) - - -## Example Usage - -Polymorphic function type are particularly useful -when callers of a method are required to provide a -function which has to be polymorphic, -meaning that it should accept arbitrary types as part of its inputs. - -For instance, consider the situation where we have -a data type to represent the expressions of a simple language -(consisting only of variables and function applications) -in a strongly-typed way: - -```scala -enum Expr[A]: - case Var(name: String) - case Apply[A, B](fun: Expr[B => A], arg: Expr[B]) extends Expr[A] -``` - -We would like to provide a way for users to map a function -over all immediate subexpressions of a given `Expr`. -This requires the given function to be polymorphic, -since each subexpression may have a different type. -Here is how to implement this using polymorphic function types: - -```scala -def mapSubexpressions[A](e: Expr[A])(f: [B] => Expr[B] => Expr[B]): Expr[A] = - e match - case Apply(fun, arg) => Apply(f(fun), f(arg)) - case Var(n) => Var(n) -``` - -And here is how to use this function to _wrap_ each subexpression -in a given expression with a call to some `wrap` function, -defined as a variable: - -```scala -val e0 = Apply(Var("f"), Var("a")) -val e1 = mapSubexpressions(e0)( - [B] => (se: Expr[B]) => Apply(Var[B => B]("wrap"), se)) -println(e1) // Apply(Apply(Var(wrap),Var(f)),Apply(Var(wrap),Var(a))) -``` - -## Relationship With Type Lambdas - -Polymorphic function types are not to be confused with -[_type lambdas_](type-lambdas.html). -While the former describes the _type_ of a polymorphic _value_, -the latter is an actual function value _at the type level_. - -A good way of understanding the difference is to notice that -**_type lambdas are applied in types, -whereas polymorphic functions are applied in terms_**: -One would call the function `bar` above -by passing it a type argument `bar[Int]` _within a method body_. -On the other hand, given a type lambda such as `type F = [A] =>> List[A]`, -one would call `F` _within a type expression_, as in `type Bar = F[Int]`. diff --git a/_scala3-reference/new-types/type-lambdas-spec.md b/_scala3-reference/new-types/type-lambdas-spec.md deleted file mode 100644 index 06bd314512..0000000000 --- a/_scala3-reference/new-types/type-lambdas-spec.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Type Lambdas - More Details" ---- - -## Syntax - -``` -Type ::= ... | TypeParamClause ‘=>>’ Type -TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’ -TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] -``` - -### Type Checking - -A type lambda such as `[X] =>> F[X]` defines a function from types to types. The parameter(s) may carry bounds. -If a parameter is bounded, as in `[X >: L <: U] =>> F[X]` it is checked that arguments to the parameters conform to the bounds `L` and `U`. -Only the upper bound `U` can be F-bounded, i.e. `X` can appear in it. - -## Subtyping Rules - -Assume two type lambdas -```scala -type TL1 = [X >: L1 <: U1] =>> R1 -type TL2 = [X >: L2 <: U2] =>> R2 -``` -Then `TL1 <: TL2`, if - - - the type interval `L2..U2` is contained in the type interval `L1..U1` (i.e. -`L1 <: L2` and `U2 <: U1`), - - `R1 <: R2` - -Here we have relied on [alpha renaming](https://en.wikipedia.org/wiki/Lambda_calculus#%CE%B1-conversion) to match the two bound types `X`. - -A partially applied type constructor such as `List` is assumed to be equivalent to -its eta expansion. I.e, `List = [X] =>> List[X]`. This allows type constructors to be compared with type lambdas. - -## Relationship with Parameterized Type Definitions - -A parameterized type definition -```scala -type T[X] = R -``` -is regarded as a shorthand for an unparameterized definition with a type lambda as right-hand side: -```scala -type T = [X] =>> R -``` -If the type definition carries `+` or `-` variance annotations, -it is checked that the variance annotations are satisfied by the type lambda. -For instance, -```scala -type F2[A, +B] = A => B -``` -expands to -```scala -type F2 = [A, B] =>> A => B -``` -and at the same time it is checked that the parameter `B` appears covariantly in `A => B`. - -A parameterized abstract type -```scala -type T[X] >: L <: U -``` -is regarded as shorthand for an unparameterized abstract type with type lambdas as bounds. -```scala -type T >: ([X] =>> L) <: ([X] =>> U) -``` -However, if `L` is `Nothing` it is not parameterized, since `Nothing` is treated as a bottom type for all kinds. For instance, -```scala -type T[X] <: X => X -``` -is expanded to -```scala -type T >: Nothing <: ([X] =>> X => X) -``` -instead of -```scala -type T >: ([X] =>> Nothing) <: ([X] =>> X => X) -``` - -The same expansions apply to type parameters. For instance, -```scala -[F[X] <: Coll[X]] -``` -is treated as a shorthand for -```scala -[F >: Nothing <: [X] =>> Coll[X]] -``` -Abstract types and opaque type aliases remember the variances they were created with. So the type -```scala -type F2[-A, +B] -``` -is known to be contravariant in `A` and covariant in `B` and can be instantiated only -with types that satisfy these constraints. Likewise -```scala -opaque type O[X] = List[X] -``` -`O` is known to be invariant (and not covariant, as its right-hand side would suggest). On the other hand, a transparent alias -```scala -type O2[X] = List[X] -``` -would be treated as covariant, `X` is used covariantly on its right-hand side. - -**Note**: The decision to treat `Nothing` as universal bottom type is provisional, and might be changed after further discussion. - -**Note**: Scala 2 and 3 differ in that Scala 2 also treats `Any` as universal top-type. This is not done in Scala 3. See also the discussion on [kind polymorphism](../other-new-features/kind-polymorphism.html) - -## Curried Type Parameters - -The body of a type lambda can again be a type lambda. Example: -```scala -type TL = [X] =>> [Y] =>> (X, Y) -``` -Currently, no special provision is made to infer type arguments to such curried type lambdas. This is left for future work. - - - diff --git a/_scala3-reference/new-types/type-lambdas.md b/_scala3-reference/new-types/type-lambdas.md deleted file mode 100644 index 8b12fbcece..0000000000 --- a/_scala3-reference/new-types/type-lambdas.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Type Lambdas" -type: section -num: 5 -previous-page: /scala3/reference/new-types/union-types -next-page: /scala3/reference/new-types/match-types ---- - -A _type lambda_ lets one express a higher-kinded type directly, without -a type definition. - -```scala -[X, Y] =>> Map[Y, X] -``` - -For instance, the type above defines a binary type constructor, which maps arguments `X` and `Y` to `Map[Y, X]`. -Type parameters of type lambdas can have bounds, but they cannot carry `+` or `-` variance annotations. - -[More details](./type-lambdas-spec.html) diff --git a/_scala3-reference/new-types/union-types-spec.md b/_scala3-reference/new-types/union-types-spec.md deleted file mode 100644 index 9cfa27f619..0000000000 --- a/_scala3-reference/new-types/union-types-spec.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Union Types - More Details" ---- - -## Syntax - -Syntactically, unions follow the same rules as intersections, but have a lower precedence, see -[Intersection Types - More Details](./intersection-types-spec.html). - -### Interaction with pattern matching syntax -`|` is also used in pattern matching to separate pattern alternatives and has -lower precedence than `:` as used in typed patterns, this means that: - -```scala -case _: A | B => ... -``` - -is still equivalent to: - -```scala -case (_: A) | B => ... -``` - -and not to: - -```scala -case _: (A | B) => ... -``` - -## Subtyping Rules - -- `A` is always a subtype of `A | B` for all `A`, `B`. -- If `A <: C` and `B <: C` then `A | B <: C` -- Like `&`, `|` is commutative and associative: - - ```scala - A | B =:= B | A - A | (B | C) =:= (A | B) | C - ``` - -- `&` is distributive over `|`: - - ```scala - A & (B | C) =:= A & B | A & C - ``` - -From these rules it follows that the _least upper bound_ (LUB) of a set of types -is the union of these types. This replaces the -[definition of least upper bound in the Scala 2 specification](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#least-upper-bounds-and-greatest-lower-bounds). - -## Motivation - -The primary reason for introducing union types in Scala is that they allow us to -guarantee that for every set of types, we can always form a finite LUB. This is -both useful in practice (infinite LUBs in Scala 2 were approximated in an ad-hoc -way, resulting in imprecise and sometimes incredibly long types) and in theory -(the type system of Scala 3 is based on the -[DOT calculus](https://infoscience.epfl.ch/record/227176/files/soundness_oopsla16.pdf), -which has union types). - -Additionally, union types are a useful construct when trying to give types to existing -dynamically typed APIs, this is why they're [an integral part of TypeScript](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types) -and have even been [partially implemented in Scala.js](https://github.com/scala-js/scala-js/blob/master/library/src/main/scala/scala/scalajs/js/Union.scala). - -## Join of a union type - -In some situation described below, a union type might need to be widened to -a non-union type, for this purpose we define the _join_ of a union type `T1 | -... | Tn` as the smallest intersection type of base class instances of -`T1`,...,`Tn`. Note that union types might still appear as type arguments in the -resulting type, this guarantees that the join is always finite. - -### Example - -Given - -```scala -trait C[+T] -trait D -trait E -class A extends C[A] with D -class B extends C[B] with D with E -``` - -The join of `A | B` is `C[A | B] & D` - -## Type inference - -When inferring the result type of a definition (`val`, `var`, or `def`) and the -type we are about to infer is a union type, then we replace it by its join. -Similarly, when instantiating a type argument, if the corresponding type -parameter is not upper-bounded by a union type and the type we are about to -instantiate is a union type, we replace it by its join. This mirrors the -treatment of singleton types which are also widened to their underlying type -unless explicitly specified. The motivation is the same: inferring types -which are "too precise" can lead to unintuitive typechecking issues later on. - -**Note:** Since this behavior limits the usability of union types, it might -be changed in the future. For example by not widening unions that have been -explicitly written down by the user and not inferred, or by not widening a type -argument when the corresponding type parameter is covariant. - -See [PR #2330](https://github.com/lampepfl/dotty/pull/2330) and -[Issue #4867](https://github.com/lampepfl/dotty/issues/4867) for further discussions. - -### Example - -```scala -import scala.collection.mutable.ListBuffer -val x = ListBuffer(Right("foo"), Left(0)) -val y: ListBuffer[Either[Int, String]] = x -``` - -This code typechecks because the inferred type argument to `ListBuffer` in the -right-hand side of `x` was `Left[Int, Nothing] | Right[Nothing, String]` which -was widened to `Either[Int, String]`. If the compiler hadn't done this widening, -the last line wouldn't typecheck because `ListBuffer` is invariant in its -argument. - - -## Members - -The members of a union type are the members of its join. - -### Example - -The following code does not typecheck, because method `hello` is not a member of -`AnyRef` which is the join of `A | B`. - -```scala -trait A { def hello: String } -trait B { def hello: String } - -def test(x: A | B) = x.hello // error: value `hello` is not a member of A | B -``` - -On the other hand, the following would be allowed - -```scala -trait C { def hello: String } -trait A extends C with D -trait B extends C with E - -def test(x: A | B) = x.hello // ok as `hello` is a member of the join of A | B which is C -``` - -## Exhaustivity checking - -If the selector of a pattern match is a union type, the match is considered -exhaustive if all parts of the union are covered. - -## Erasure - -The erased type for `A | B` is the _erased least upper bound_ of the erased -types of `A` and `B`. Quoting from the documentation of `TypeErasure#erasedLub`, -the erased LUB is computed as follows: - -- if both argument are arrays of objects, an array of the erased LUB of the element types -- if both arguments are arrays of same primitives, an array of this primitive -- if one argument is array of primitives and the other is array of objects, - [`Object`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html) -- if one argument is an array, [`Object`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html) -- otherwise a common superclass or trait S of the argument classes, with the - following two properties: - * S is minimal: no other common superclass or trait derives from S - * S is last : in the linearization of the first argument type `|A|` - there are no minimal common superclasses or traits that - come after S. - The reason to pick last is that we prefer classes over traits that way, - which leads to more predictable bytecode and (?) faster dynamic dispatch. diff --git a/_scala3-reference/new-types/union-types.md b/_scala3-reference/new-types/union-types.md deleted file mode 100644 index 0d3df4645d..0000000000 --- a/_scala3-reference/new-types/union-types.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Union Types" -type: section -num: 4 -previous-page: /scala3/reference/new-types/intersection-types -next-page: /scala3/reference/new-types/type-lambdas ---- - -A union type `A | B` has as values all values of type `A` and also all values of type `B`. - - -```scala -case class UserName(name: String) -case class Password(hash: Hash) - -def help(id: UserName | Password) = - val user = id match - case UserName(name) => lookupName(name) - case Password(hash) => lookupPassword(hash) - ... -``` - -Union types are duals of intersection types. `|` is _commutative_: -`A | B` is the same type as `B | A`. - -The compiler will assign a union type to an expression only if such a -type is explicitly given. This can be seen in the following [REPL](https://docs.scala-lang.org/overviews/repl/overview.html) transcript: - -```scala -scala> val password = Password(123) -val password: Password = Password(123) - -scala> val name = UserName("Eve") -val name: UserName = UserName(Eve) - -scala> if true then name else password -val res2: Object = UserName(Eve) - -scala> val either: Password | UserName = if true then name else password -val either: Password | UserName = UserName(Eve) -``` - -The type of `res2` is `Object & Product`, which is a supertype of -`UserName` and `Password`, but not the least supertype `Password | -UserName`. If we want the least supertype, we have to give it -explicitly, as is done for the type of `either`. - -[More details](./union-types-spec.html) diff --git a/_scala3-reference/other-new-features.md b/_scala3-reference/other-new-features.md deleted file mode 100644 index faa2aefc6a..0000000000 --- a/_scala3-reference/other-new-features.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Other New Features" -type: chapter -num: 34 -previous-page: /scala3/reference/metaprogramming/tasty-inspect -next-page: /scala3/reference/other-new-features/trait-parameters ---- - -The following pages document new features of Scala 3. diff --git a/_scala3-reference/other-new-features/control-syntax.md b/_scala3-reference/other-new-features/control-syntax.md deleted file mode 100644 index 0fefba262b..0000000000 --- a/_scala3-reference/other-new-features/control-syntax.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: New Control Syntax -type: section -num: 46 -previous-page: /scala3/reference/other-new-features/targetName -next-page: /scala3/reference/other-new-features/indentation ---- - -Scala 3 has a new "quiet" syntax for control expressions that does not rely on -enclosing the condition in parentheses, and also allows to drop parentheses or braces -around the generators of a `for`-expression. Examples: -```scala -if x < 0 then - "negative" -else if x == 0 then - "zero" -else - "positive" - -if x < 0 then -x else x - -while x >= 0 do x = f(x) - -for x <- xs if x > 0 -yield x * x - -for - x <- xs - y <- ys -do - println(x + y) - -try body -catch case ex: IOException => handle -``` - -The rules in detail are: - - - The condition of an `if`-expression can be written without enclosing parentheses if it is followed by a `then`. - - The condition of a `while`-loop can be written without enclosing parentheses if it is followed by a `do`. - - The enumerators of a `for`-expression can be written without enclosing parentheses or braces if they are followed by a `yield` or `do`. - - A `do` in a `for`-expression expresses a `for`-loop. - - A `catch` can be followed by a single case on the same line. - If there are multiple cases, these have to appear within braces (just like in Scala 2) - or an indented block. -### Rewrites - -The Scala 3 compiler can rewrite source code from old syntax to new syntax and back. -When invoked with options `-rewrite -new-syntax` it will rewrite from old to new syntax, dropping parentheses and braces in conditions and enumerators. When invoked with options `-rewrite -old-syntax` it will rewrite in the reverse direction, inserting parentheses and braces as needed. diff --git a/_scala3-reference/other-new-features/creator-applications.md b/_scala3-reference/other-new-features/creator-applications.md deleted file mode 100644 index 0422bbfac7..0000000000 --- a/_scala3-reference/other-new-features/creator-applications.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: "Universal Apply Methods" -type: section -num: 37 -previous-page: /scala3/reference/other-new-features/transparent-traits -next-page: /scala3/reference/other-new-features/export ---- - -Scala case classes generate apply methods, so that values of case classes can be created using simple -function application, without needing to write `new`. - -Scala 3 generalizes this scheme to all concrete classes. Example: - -```scala -class StringBuilder(s: String): - def this() = this("") - -StringBuilder("abc") // old: new StringBuilder("abc") -StringBuilder() // old: new StringBuilder() -``` - -This works since a companion object with two `apply` methods -is generated together with the class. The object looks like this: - -```scala -object StringBuilder: - inline def apply(s: String): StringBuilder = new StringBuilder(s) - inline def apply(): StringBuilder = new StringBuilder() -``` - -The synthetic object `StringBuilder` and its `apply` methods are called _constructor proxies_. -Constructor proxies are generated even for Java classes and classes coming from Scala 2. -The precise rules are as follows: - - 1. A constructor proxy companion object `object C` is created for a concrete class `C`, - provided the class does not have already a companion, and there is also no other value - or method named `C` defined or inherited in the scope where `C` is defined. - - 2. Constructor proxy `apply` methods are generated for a concrete class provided - - - the class has a companion object (which might have been generated in step 1), and - - that companion object does not already define a member named `apply`. - - Each generated `apply` method forwards to one constructor of the class. It has the - same type and value parameters as the constructor. - -Constructor proxy companions cannot be used as values by themselves. A proxy companion object must -be selected with `apply` (or be applied to arguments, in which case the `apply` is implicitly -inserted). - -Constructor proxies are also not allowed to shadow normal definitions. That is, -if an identifier resolves to a constructor proxy, and the same identifier is also -defined or imported in some other scope, an ambiguity is reported. - -### Motivation - -Leaving out `new` hides an implementation detail and makes code more pleasant to read. Even though -it requires a new rule, it will likely increase the perceived regularity of the language, since case -classes already provide function call creation syntax (and are often defined for this reason alone). diff --git a/_scala3-reference/other-new-features/explicit-nulls.md b/_scala3-reference/other-new-features/explicit-nulls.md deleted file mode 100644 index d33d86c931..0000000000 --- a/_scala3-reference/other-new-features/explicit-nulls.md +++ /dev/null @@ -1,540 +0,0 @@ ---- -title: "Explicit Nulls" -type: section -num: 48 -previous-page: /scala3/reference/other-new-features/indentation -next-page: /scala3/reference/other-new-features/safe-initialization ---- - -Explicit nulls is an opt-in feature that modifies the Scala type system, which makes reference types -(anything that extends `AnyRef`) _non-nullable_. - -This means the following code will no longer typecheck: - -```scala -val x: String = null // error: found `Null`, but required `String` -``` - -Instead, to mark a type as nullable we use a [union type](../new-types/union-types.html) - -```scala -val x: String | Null = null // ok -``` - -A nullable type could have null value during runtime; hence, it is not safe to select a member without checking its nullity. - -```scala -x.trim // error: trim is not member of String | Null -``` - -Explicit nulls are enabled via a `-Yexplicit-nulls` flag. - -Read on for details. - -## New Type Hierarchy - -When explicit nulls are enabled, the type hierarchy changes so that `Null` is only a subtype of -`Any`, as opposed to every reference type, which means `null` is no longer a value of `AnyRef` and its subtypes. - -This is the new type hierarchy: - -!["Type Hierarchy for Explicit Nulls"](/resources/images/scala3/explicit-nulls/explicit-nulls-type-hierarchy.png) - -After erasure, `Null` remains a subtype of all reference types (as forced by the JVM). - -## Working with `Null` - -To make working with nullable values easier, we propose adding a few utilities to the standard library. -So far, we have found the following useful: - -- An extension method `.nn` to "cast away" nullability - - ```scala - extension [T](x: T | Null) - inline def nn: T = - assert(x != null) - x.asInstanceOf[T] - ``` - - This means that given `x: String | Null`, `x.nn` has type `String`, so we can call all the - usual methods on it. Of course, `x.nn` will throw a NPE if `x` is `null`. - - Don't use `.nn` on mutable variables directly, because it may introduce an unknown type into the type of the variable. - -- An `unsafeNulls` language feature. - - When imported, `T | Null` can be used as `T`, similar to regular Scala (without explicit nulls). - - See [UnsafeNulls](#unsafenulls) section for more details. - -## Unsoundness - -The new type system is unsound with respect to `null`. This means there are still instances where an expression has a non-nullable type like `String`, but its value is actually `null`. - -The unsoundness happens because uninitialized fields in a class start out as `null`: - -```scala -class C: - val f: String = foo(f) - def foo(f2: String): String = f2 - -val c = new C() -// c.f == "field is null" -``` - -The unsoundness above can be caught by the compiler with the option `-Ysafe-init`. -More details can be found in [safe initialization](./safe-initialization.html). - -## Equality - -We don't allow the double-equal (`==` and `!=`) and reference (`eq` and `ne`) comparison between -`AnyRef` and `Null` anymore, since a variable with a non-nullable type cannot have `null` as value. -`null` can only be compared with `Null`, nullable union (`T | Null`), or `Any` type. - -For some reason, if we really want to compare `null` with non-null values, we have to provide a type hint (e.g. `: Any`). - -```scala -val x: String = ??? -val y: String | Null = ??? - -x == null // error: Values of types String and Null cannot be compared with == or != -x eq null // error -"hello" == null // error - -y == null // ok -y == x // ok - -(x: String | Null) == null // ok -(x: Any) == null // ok -``` - -## Java Interoperability - -The Scala compiler can load Java classes in two ways: from source or from bytecode. In either case, -when a Java class is loaded, we "patch" the type of its members to reflect that Java types -remain implicitly nullable. - -Specifically, we patch - -- the type of fields - -- the argument type and return type of methods - -We illustrate the rules with following examples: - -- The first two rules are easy: we nullify reference types but not value types. - - ```java - class C { - String s; - int x; - } - ``` - - ==> - - ```scala - class C: - val s: String | Null - val x: Int - ``` - -- We nullify type parameters because in Java a type parameter is always nullable, so the following code compiles. - - ```java - class C { T foo() { return null; } } - ``` - - ==> - - ```scala - class C[T] { def foo(): T | Null } - ``` - - Notice this is rule is sometimes too conservative, as witnessed by - - ```scala - class InScala: - val c: C[Bool] = ??? // C as above - val b: Bool = c.foo() // no longer typechecks, since foo now returns Bool | Null - ``` - -- We can reduce the number of redundant nullable types we need to add. Consider - - ```java - class Box { T get(); } - class BoxFactory { Box makeBox(); } - ``` - - ==> - - ```scala - class Box[T] { def get(): T | Null } - class BoxFactory[T] { def makeBox(): Box[T] | Null } - ``` - - Suppose we have a `BoxFactory[String]`. Notice that calling `makeBox()` on it returns a - `Box[String] | Null`, not a `Box[String | Null] | Null`. This seems at first - glance unsound ("What if the box itself has `null` inside?"), but is sound because calling - `get()` on a `Box[String]` returns a `String | Null`. - - Notice that we need to patch _all_ Java-defined classes that transitively appear in the - argument or return type of a field or method accessible from the Scala code being compiled. - Absent crazy reflection magic, we think that all such Java classes _must_ be visible to - the Typer in the first place, so they will be patched. - -- We will append `Null` to the type arguments if the generic class is defined in Scala. - - ```java - class BoxFactory { - Box makeBox(); // Box is Scala-defined - List>> makeCrazyBoxes(); // List is Java-defined - } - ``` - - ==> - - ```scala - class BoxFactory[T]: - def makeBox(): Box[T | Null] | Null - def makeCrazyBoxes(): java.util.List[Box[java.util.List[T] | Null]] | Null - ``` - - In this case, since `Box` is Scala-defined, we will get `Box[T | Null] | Null`. - This is needed because our nullability function is only applied (modularly) to the Java - classes, but not to the Scala ones, so we need a way to tell `Box` that it contains a - nullable value. - - The `List` is Java-defined, so we don't append `Null` to its type argument. But we - still need to nullify its inside. - -- We don't nullify _simple_ literal constant (`final`) fields, since they are known to be non-null - - ```java - class Constants { - final String NAME = "name"; - final int AGE = 0; - final char CHAR = 'a'; - - final String NAME_GENERATED = getNewName(); - } - ``` - - ==> - - ```scala - class Constants: - val NAME: String("name") = "name" - val AGE: Int(0) = 0 - val CHAR: Char('a') = 'a' - - val NAME_GENERATED: String | Null = getNewName() - ``` - -- We don't append `Null` to a field nor to a return type of a method which is annotated with a - `NotNull` annotation. - - ```java - class C { - @NotNull String name; - @NotNull List getNames(String prefix); // List is Java-defined - @NotNull Box getBoxedName(); // Box is Scala-defined - } - ``` - - ==> - - ```scala - class C: - val name: String - def getNames(prefix: String | Null): java.util.List[String] // we still need to nullify the paramter types - def getBoxedName(): Box[String | Null] // we don't append `Null` to the outmost level, but we still need to nullify inside - ``` - - The annotation must be from the list below to be recognized as `NotNull` by the compiler. - Check `Definitions.scala` for an updated list. - - ```scala - // A list of annotations that are commonly used to indicate - // that a field/method argument or return type is not null. - // These annotations are used by the nullification logic in - // JavaNullInterop to improve the precision of type nullification. - // We don't require that any of these annotations be present - // in the class path, but we want to create Symbols for the - // ones that are present, so they can be checked during nullification. - @tu lazy val NotNullAnnots: List[ClassSymbol] = ctx.getClassesIfDefined( - "javax.annotation.Nonnull" :: - "edu.umd.cs.findbugs.annotations.NonNull" :: - "androidx.annotation.NonNull" :: - "android.support.annotation.NonNull" :: - "android.annotation.NonNull" :: - "com.android.annotations.NonNull" :: - "org.eclipse.jdt.annotation.NonNull" :: - "org.checkerframework.checker.nullness.qual.NonNull" :: - "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: - "org.jetbrains.annotations.NotNull" :: - "lombok.NonNull" :: - "io.reactivex.annotations.NonNull" :: Nil map PreNamedString) - ``` - -### Override check - -When we check overriding between Scala classes and Java classes, the rules are relaxed for `Null` type with this feature, in order to help users to working with Java libraries. - -Suppose we have Java method `String f(String x)`, we can override this method in Scala in any of the following forms: - -```scala -def f(x: String | Null): String | Null - -def f(x: String): String | Null - -def f(x: String | Null): String - -def f(x: String): String -``` - -Note that some of the definitions could cause unsoundness. For example, the return type is not nullable, but a `null` value is actually returned. - -## Flow Typing - -We added a simple form of flow-sensitive type inference. The idea is that if `p` is a -stable path or a trackable variable, then we can know that `p` is non-null if it's compared -with `null`. This information can then be propagated to the `then` and `else` branches -of an if-statement (among other places). - -Example: - -```scala -val s: String | Null = ??? -if s != null then - // s: String - -// s: String | Null - -assert(s != null) -// s: String -``` - -A similar inference can be made for the `else` case if the test is `p == null` - -```scala -if s == null then - // s: String | Null -else - // s: String -``` - -`==` and `!=` is considered a comparison for the purposes of the flow inference. - -### Logical Operators - -We also support logical operators (`&&`, `||`, and `!`): - -```scala -val s: String | Null = ??? -val s2: String | Null = ??? -if s != null && s2 != null then - // s: String - // s2: String - -if s == null || s2 == null then - // s: String | Null - // s2: String | Null -else - // s: String - // s2: String -``` - -### Inside Conditions - -We also support type specialization _within_ the condition, taking into account that `&&` and `||` are short-circuiting: - -```scala -val s: String | Null = ??? - -if s != null && s.length > 0 then // s: String in `s.length > 0` - // s: String - -if s == null || s.length > 0 then // s: String in `s.length > 0` - // s: String | Null -else - // s: String -``` - -### Match Case - -The non-null cases can be detected in match statements. - -```scala -val s: String | Null = ??? - -s match - case _: String => // s: String - case _ => -``` - -### Mutable Variable - -We are able to detect the nullability of some local mutable variables. A simple example is: - -```scala -class C(val x: Int, val next: C | Null) - -var xs: C | Null = C(1, C(2, null)) -// xs is trackable, since all assignments are in the same method -while xs != null do - // xs: C - val xsx: Int = xs.x - val xscpy: C = xs - xs = xscpy // since xscpy is non-null, xs still has type C after this line - // xs: C - xs = xs.next // after this assignment, xs can be null again - // xs: C | Null -``` - -When dealing with local mutable variables, there are two questions: - -1. Whether to track a local mutable variable during flow typing. - We track a local mutable variable if the variable is not assigned in a closure. - For example, in the following code `x` is assigned to by the closure `y`, so we do not - do flow typing on `x`. - - ```scala - var x: String | Null = ??? - def y = - x = null - - if x != null then - // y can be called here, which would break the fact - val a: String = x // error: x is captured and mutated by the closure, not trackable - ``` - -2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable. - We only want to do flow typing on a use that belongs to the same method as the definition - of the local variable. - For example, in the following code, even `x` is not assigned to by a closure, we can only - use flow typing in one of the occurrences (because the other occurrence happens within a - nested closure). - - ```scala - var x: String | Null = ??? - def y = - if x != null then - // not safe to use the fact (x != null) here - // since y can be executed at the same time as the outer block - val _: String = x - if x != null then - val a: String = x // ok to use the fact here - x = null - ``` - -See [more examples](https://github.com/lampepfl/dotty/blob/master/tests/explicit-nulls/neg/flow-varref-in-closure.scala). - -Currently, we are unable to track paths with a mutable variable prefix. -For example, `x.a` if `x` is mutable. - -### Unsupported Idioms - -We don't support: - -- flow facts not related to nullability (`if x == 0 then { // x: 0.type not inferred }`) -- tracking aliasing between non-nullable paths - - ```scala - val s: String | Null = ??? - val s2: String | Null = ??? - if s != null && s == s2 then - // s: String inferred - // s2: String not inferred - ``` - -### UnsafeNulls - -It is difficult to work with many nullable values, we introduce a language feature `unsafeNulls`. -Inside this "unsafe" scope, all `T | Null` values can be used as `T`. - -Users can import `scala.language.unsafeNulls` to create such scopes, or use `-language:unsafeNulls` to enable this feature globally (for migration purpose only). - -Assume `T` is a reference type (a subtype of `AnyRef`), the following unsafe operation rules are -applied in this unsafe-nulls scope: - -1. the members of `T` can be found on `T | Null` - -2. a value with type `T` can be compared with `T | Null` and `Null` - -3. suppose `T1` is not a subtype of `T2` using explicit-nulls subtyping (where `Null` is a direct -subtype of Any), extension methods and implicit conversions designed for `T2` can be used for -`T1` if `T1` is a subtype of `T2` using regular subtyping rules (where `Null` is a subtype of every -reference type) - -4. suppose `T1` is not a subtype of `T2` using explicit-nulls subtyping, a value with type `T1` -can be used as `T2` if `T1` is a subtype of `T2` using regular subtyping rules - -Addtionally, `null` can be used as `AnyRef` (`Object`), which means you can select `.eq` or `.toString` on it. - -The program in `unsafeNulls` will have a **similar** semantic as regular Scala, but not **equivalent**. - -For example, the following code cannot be compiled even using unsafe nulls. Because of the -Java interoperation, the type of the get method becomes `T | Null`. - -```Scala -def head[T](xs: java.util.List[T]): T = xs.get(0) // error -``` - -Since the compiler doesn’t know whether `T` is a reference type, it is unable to cast `T | Null` -to `T`. A `.nn` need to be inserted after `xs.get(0)` by user manually to fix the error, which -strips the `Null` from its type. - -The intention of this `unsafeNulls` is to give users a better migration path for explicit nulls. -Projects for Scala 2 or regular Scala 3 can try this by adding `-Yexplicit-nulls -language:unsafeNulls` -to the compile options. A small number of manual modifications are expected. To migrate to the full -explicit nulls feature in the future, `-language:unsafeNulls` can be dropped and add -`import scala.language.unsafeNulls` only when needed. - -```scala -def f(x: String): String = ??? -def nullOf[T >: Null]: T = null - -import scala.language.unsafeNulls - -val s: String | Null = ??? -val a: String = s // unsafely convert String | Null to String - -val b1 = s.trim // call .trim on String | Null unsafely -val b2 = b1.length - -f(s).trim // pass String | Null as an argument of type String unsafely - -val c: String = null // Null to String - -val d1: Array[String] = ??? -val d2: Array[String | Null] = d1 // unsafely convert Array[String] to Array[String | Null] -val d3: Array[String] = Array(null) // unsafe - -class C[T >: Null <: String] // define a type bound with unsafe conflict bound - -val n = nullOf[String] // apply a type bound unsafely -``` - -Without the `unsafeNulls`, all these unsafe operations will not be type-checked. - -`unsafeNulls` also works for extension methods and implicit search. - -```scala -import scala.language.unsafeNulls - -val x = "hello, world!".split(" ").map(_.length) - -given Conversion[String, Array[String]] = _ => ??? - -val y: String | Null = ??? -val z: Array[String | Null] = y -``` - -## Binary Compatibility - -Our strategy for binary compatibility with Scala binaries that predate explicit nulls -and new libraries compiled without `-Yexplicit-nulls` is to leave the types unchanged -and be compatible but unsound. - -[More details](https://dotty.epfl.ch/3.0.0/docs/internals/explicit-nulls.html) diff --git a/_scala3-reference/other-new-features/export.md b/_scala3-reference/other-new-features/export.md deleted file mode 100644 index 49a190c4c9..0000000000 --- a/_scala3-reference/other-new-features/export.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: "Export Clauses" -type: section -num: 38 -previous-page: /scala3/reference/other-new-features/creator-applications -next-page: /scala3/reference/other-new-features/opaques ---- - -An export clause defines aliases for selected members of an object. Example: - -```scala -class BitMap -class InkJet - -class Printer: - type PrinterType - def print(bits: BitMap): Unit = ??? - def status: List[String] = ??? - -class Scanner: - def scan(): BitMap = ??? - def status: List[String] = ??? - -class Copier: - private val printUnit = new Printer { type PrinterType = InkJet } - private val scanUnit = new Scanner - - export scanUnit.scan - export printUnit.{status => _, *} - - def status: List[String] = printUnit.status ++ scanUnit.status -``` - -The two `export` clauses define the following _export aliases_ in class `Copier`: - -```scala -final def scan(): BitMap = scanUnit.scan() -final def print(bits: BitMap): Unit = printUnit.print(bits) -final type PrinterType = printUnit.PrinterType -``` - -They can be accessed inside `Copier` as well as from outside: - -```scala -val copier = new Copier -copier.print(copier.scan()) -``` - -An export clause has the same format as an import clause. Its general form is: - -```scala -export path . { sel_1, ..., sel_n } -``` - -It consists of a qualifier expression `path`, which must be a stable identifier, followed by -one or more selectors `sel_i` that identify what gets an alias. Selectors can be -of one of the following forms: - - - A _simple selector_ `x` creates aliases for all eligible members of `path` that are named `x`. - - A _renaming selector_ `x => y` creates aliases for all eligible members of `path` that are named `x`, but the alias is named `y` instead of `x`. - - An _omitting selector_ `x => _` prevents `x` from being aliased by a subsequent - wildcard selector. - - A _given selector_ `given x` has an optional type bound `x`. It creates aliases for all eligible given instances that conform to either `x`, or `Any` if `x` is omitted, except for members that are named by a previous simple, renaming, or omitting selector. - - A _wildcard selector_ `*` creates aliases for all eligible members of `path` except for given instances, - synthetic members generated by the compiler and those members that are named by a previous simple, renaming, or omitting selector. - \ - Notes: - - eligible construtor proxies are also included, even though they are synthetic members. - - members created by an export are also included. They are created by the compiler, but are not considered synthetic. - -A member is _eligible_ if all of the following holds: - - - its owner is not a base class of the class[(\*)](#note_class) containing the export clause, - - the member does not override a concrete definition that has as owner - a base class of the class containing the export clause. - - it is accessible at the export clause, - - it is not a constructor, nor the (synthetic) class part of an object, - - it is a given instance (declared with `given`) if and only if the export is from a _given selector_. - -It is a compile-time error if a simple or renaming selector does not identify any eligible members. - -Type members are aliased by type definitions, and term members are aliased by method definitions. Export aliases copy the type and value parameters of the members they refer to. -Export aliases are always `final`. Aliases of given instances are again defined as givens (and aliases of old-style implicits are `implicit`). Aliases of extensions are again defined as extensions. Aliases of inline methods or values are again defined `inline`. There are no other modifiers that can be given to an alias. This has the following consequences for overriding: - - - Export aliases cannot be overridden, since they are final. - - Export aliases cannot override concrete members in base classes, since they are - not marked `override`. - - However, export aliases can implement deferred members of base classes. - -Export aliases for public value definitions that are accessed without -referring to private values in the qualifier path -are marked by the compiler as "stable" and their result types are the singleton types of the aliased definitions. This means that they can be used as parts of stable identifier paths, even though they are technically methods. For instance, the following is OK: -```scala -class C { type T } -object O { val c: C = ... } -export O.c -def f: c.T = ... -``` - - -**Restrictions:** - - 1. Export clauses can appear in classes or they can appear at the top-level. An export clause cannot appear as a statement in a block. - 1. If an export clause contains a wildcard or given selector, it is forbidden for its qualifier path to refer to a package. This is because it is not yet known how to safely track wildcard dependencies to a package for the purposes of incremental compilation. - - 1. Simple renaming exports like - ```scala - export status as stat - ``` - are not supported yet. They would run afoul of the restriction that the - exported `a` cannot be already a member of the object containing the export. - This restriction might be lifted in the future. - - -(\*) **Note:** Unless otherwise stated, the term "class" in this discussion also includes object and trait definitions. - -## Motivation - -It is a standard recommendation to prefer composition over inheritance. This is really an application of the principle of least power: Composition treats components as blackboxes whereas inheritance can affect the internal workings of components through overriding. Sometimes the close coupling implied by inheritance is the best solution for a problem, but where this is not necessary the looser coupling of composition is better. - -So far, object-oriented languages including Scala made it much easier to use inheritance than composition. Inheritance only requires an `extends` clause whereas composition required a verbose elaboration of a sequence of forwarders. So in that sense, object-oriented languages are pushing -programmers to a solution that is often too powerful. Export clauses redress the balance. They make composition relationships as concise and easy to express as inheritance relationships. Export clauses also offer more flexibility than extends clauses since members can be renamed or omitted. - -Export clauses also fill a gap opened by the shift from package objects to top-level definitions. One occasionally useful idiom that gets lost in this shift is a package object inheriting from some class. The idiom is often used in a facade like pattern, to make members -of internal compositions available to users of a package. Top-level definitions are not wrapped in a user-defined object, so they can't inherit anything. However, top-level definitions can be export clauses, which supports the facade design pattern in a safer and -more flexible way. - -## Syntax changes: - -``` -TemplateStat ::= ... - | Export -TopStat ::= ... - | Export -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec -ImportSpec ::= NamedSelector - | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ -NamedSelector ::= id [‘as’ (id | ‘_’)] -WildCardSelector ::= ‘*’ | ‘given’ [InfixType] -ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} -``` - -## Elaboration of Export Clauses - -Export clauses raise questions about the order of elaboration during type checking. -Consider the following example: - -```scala -class B { val c: Int } -object a { val b = new B } -export a.* -export b.* -``` - -Is the `export b.*` clause legal? If yes, what does it export? Is it equivalent to `export a.b.*`? What about if we swap the last two clauses? - -``` -export b.* -export a.* -``` - -To avoid tricky questions like these, we fix the elaboration order of exports as follows. - -Export clauses are processed when the type information of the enclosing object or class is completed. Completion so far consisted of the following steps: - - 1. Elaborate any annotations of the class. - 2. Elaborate the parameters of the class. - 3. Elaborate the self type of the class, if one is given. - 4. Enter all definitions of the class as class members, with types to be completed - on demand. - 5. Determine the types of all parents of the class. - - With export clauses, the following steps are added: - - 6. Compute the types of all paths in export clauses. - 7. Enter export aliases for the eligible members of all paths in export clauses. - -It is important that steps 6 and 7 are done in sequence: We first compute the types of _all_ -paths in export clauses and only after this is done we enter any export aliases as class members. This means that a path of an export clause cannot refer to an alias made available -by another export clause of the same class. diff --git a/_scala3-reference/other-new-features/indentation-experimental.md b/_scala3-reference/other-new-features/indentation-experimental.md deleted file mode 100644 index 389219d8e8..0000000000 --- a/_scala3-reference/other-new-features/indentation-experimental.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Fewer Braces" ---- - -By and large, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to function can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. - -To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import -```scala -import language.experimental.fewerBraces -``` -Alternatively, it can be enabled with command line option `-language:experimental.fewerBraces`. - -This variant is more contentious and less stable than the rest of the significant indentation scheme. It allows to replace a function argument in braces by a `:` at the end of a line and indented code, similar to the convention for class bodies. It also allows to leave out braces around arguments that are multi-line function values. - -## Using `:` At End Of Line - - -Similar to what is done for classes and objects, a `:` that follows a function reference at the end of a line means braces can be omitted for function arguments. Example: -```scala -times(10): - println("ah") - println("ha") -``` - -The colon can also follow an infix operator: - -```scala -credentials ++ : - val file = Path.userHome / ".credentials" - if file.exists - then Seq(Credentials(file)) - else Seq() -``` - -Function calls that take multiple argument lists can also be handled this way: - -```scala -val firstLine = files.get(fileName).fold: - val fileNames = files.values - s"""no file named $fileName found among - |${values.mkString(\n)}""".stripMargin - : - f => - val lines = f.iterator.map(_.readLine) - lines.mkString("\n) -``` - - -## Lambda Arguments Without Braces - -Braces can also be omitted around multiple line function value arguments: -```scala -val xs = elems.map x => - val y = x - 1 - y * y -xs.foldLeft (x, y) => - x + y -``` -Braces can be omitted if the lambda starts with a parameter list and `=>` or `=>?` at the end of one line and it has an indented body on the following lines. - -## Syntax Changes - -``` -SimpleExpr ::= ... - | SimpleExpr `:` IndentedArgument - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument -InfixExpr ::= ... - | InfixExpr id `:` IndentedArgument -IndentedArgument ::= indent (CaseClauses | Block) outdent -``` - -Note that a lambda argument must have the `=>` at the end of a line for braces -to be optional. For instance, the following would also be incorrect: - -```scala - xs.map x => x + 1 // error: braces or parentheses are required -``` -The lambda has to be enclosed in braces or parentheses: -```scala - xs.map(x => x + 1) // ok -``` diff --git a/_scala3-reference/other-new-features/indentation.md b/_scala3-reference/other-new-features/indentation.md deleted file mode 100644 index 6a71e351f3..0000000000 --- a/_scala3-reference/other-new-features/indentation.md +++ /dev/null @@ -1,455 +0,0 @@ ---- -title: "Optional Braces" -type: section -num: 47 -previous-page: /scala3/reference/other-new-features/control-syntax -next-page: /scala3/reference/other-new-features/explicit-nulls ---- - -Scala 3 enforces some rules on indentation and allows some occurrences of braces `{...}` to be optional: - -- First, some badly indented programs are flagged with warnings. -- Second, some occurrences of braces `{...}` are made optional. Generally, the rule - is that adding a pair of optional braces will not change the meaning of a well-indented program. - -These changes can be turned off with the compiler flag `-no-indent`. - -### Indentation Rules - -The compiler enforces two rules for well-indented programs, flagging violations as warnings. - - 1. In a brace-delimited region, no statement is allowed to start to the left - of the first statement after the opening brace that starts a new line. - - This rule is helpful for finding missing closing braces. It prevents errors like: - - ```scala - if (x < 0) { - println(1) - println(2) - - println("done") // error: indented too far to the left - ``` - - 2. If significant indentation is turned off (i.e. under Scala 2 mode or under `-no-indent`) and we are at the start of an indented sub-part of an expression, and the indented part ends in a newline, the next statement must start at an indentation width less than the sub-part. This prevents errors where an opening brace was forgotten, as in - - ```scala - if (x < 0) - println(1) - println(2) // error: missing `{` - ``` - -These rules still leave a lot of leeway how programs should be indented. For instance, they do not impose -any restrictions on indentation within expressions, nor do they require that all statements of an indentation block line up exactly. - -The rules are generally helpful in pinpointing the root cause of errors related to missing opening or closing braces. These errors are often quite hard to diagnose, in particular in large programs. - -### Optional Braces - -The compiler will insert `` or `` -tokens at certain line breaks. Grammatically, pairs of `` and `` tokens have the same effect as pairs of braces `{` and `}`. - -The algorithm makes use of a stack `IW` of previously encountered indentation widths. The stack initially holds a single element with a zero indentation width. The _current indentation width_ is the indentation width of the top of the stack. - -There are two rules: - - 1. An `` is inserted at a line break, if - - - An indentation region can start at the current position in the source, and - - the first token on the next line has an indentation width strictly greater - than the current indentation width - - An indentation region can start - - - after the leading parameters of an `extension`, or - - after a `with` in a given instance, or - - after a ": at end of line" token (see below) - - after one of the following tokens: - - ``` - = => ?=> <- catch do else finally for - if match return then throw try while yield - ``` - - - after the closing `)` of a condition in an old-style `if` or `while`. - - after the closing `)` or `}` of the enumerations of an old-style `for` loop without a `do`. - - If an `` is inserted, the indentation width of the token on the next line - is pushed onto `IW`, which makes it the new current indentation width. - - 2. An `` is inserted at a line break, if - - - the first token on the next line has an indentation width strictly less - than the current indentation width, and - - the last token on the previous line is not one of the following tokens - which indicate that the previous statement continues: - ``` - then else do catch finally yield match - ``` - - if the first token on the next line is a - [leading infix operator](../changed-features/operators.html). - then its indentation width is less then the current indentation width, - and it either matches a previous indentation width or is also less - than the enclosing indentation width. - - If an `` is inserted, the top element is popped from `IW`. - If the indentation width of the token on the next line is still less than the new current indentation width, step (2) repeats. Therefore, several `` tokens - may be inserted in a row. - - The following two additional rules support parsing of legacy code with ad-hoc layout. They might be withdrawn in future language versions: - - - An `` is also inserted if the next token following a statement sequence starting with an `` closes an indentation region, i.e. is one of `then`, `else`, `do`, `catch`, `finally`, `yield`, `}`, `)`, `]` or `case`. - - An `` is finally inserted in front of a comma that follows a statement sequence starting with an `` if the indented region is itself enclosed in parentheses - -It is an error if the indentation width of the token following an `` does not match the indentation of some previous line in the enclosing indentation region. For instance, the following would be rejected. - -```scala -if x < 0 then - -x - else // error: `else` does not align correctly - x -``` - -Indentation tokens are only inserted in regions where newline statement separators are also inferred: -at the top-level, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types. - -**Note:** The rules for leading infix operators above are there to make sure that -```scala - one - + two.match - case 1 => b - case 2 => c - + three -``` -is parsed as `one + (two.match ...) + three`. Also, that -```scala -if x then - a - + b - + c -else d -``` -is parsed as `if x then a + b + c else d`. - -### Optional Braces Around Template Bodies - -The Scala grammar uses the term _template body_ for the definitions of a class, trait, or object that are normally enclosed in braces. The braces around a template body can also be omitted by means of the following rule. - -If at the point where a template body can start there is a `:` that occurs at the end -of a line, and that is followed by at least one indented statement, the recognized -token is changed from ":" to ": at end of line". The latter token is one of the tokens -that can start an indentation region. The Scala grammar is changed so an optional ": at end of line" is allowed in front of a template body. - -Analogous rules apply for enum bodies and local packages containing nested definitions. - -With these new rules, the following constructs are all valid: - -```scala -trait A: - def f: Int - -class C(x: Int) extends A: - def f = x - -object O: - def f = 3 - -enum Color: - case Red, Green, Blue - -new A: - def f = 3 - -package p: - def a = 1 - -package q: - def b = 2 -``` - -In each case, the `:` at the end of line can be replaced without change of meaning by a pair of braces that enclose the following indented definition(s). - -The syntax changes allowing this are as follows: - -``` -Template ::= InheritClauses [colonEol] [TemplateBody] -EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody -Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ -SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] -``` - -Here, `colonEol` stands for ": at end of line", as described above. -The lexical analyzer is modified so that a `:` at the end of a line -is reported as `colonEol` if the parser is at a point where a `colonEol` is -valid as next token. - -### Spaces vs Tabs - -Indentation prefixes can consist of spaces and/or tabs. Indentation widths are the indentation prefixes themselves, ordered by the string prefix relation. So, so for instance "2 tabs, followed by 4 spaces" is strictly less than "2 tabs, followed by 5 spaces", but "2 tabs, followed by 4 spaces" is incomparable to "6 tabs" or to "4 spaces, followed by 2 tabs". It is an error if the indentation width of some line is incomparable with the indentation width of the region that's current at that point. To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file. - -### Indentation and Braces - -Indentation can be mixed freely with braces `{...}`, as well as brackets `[...]` and parentheses `(...)`. For interpreting indentation inside such regions, the following rules apply. - - 1. The assumed indentation width of a multiline region enclosed in braces is the - indentation width of the first token that starts a new line after the opening brace. - - 2. The assumed indentation width of a multiline region inside brackets or parentheses is: - - - if the opening bracket or parenthesis is at the end of a line, the indentation width of token following it, - - otherwise, the indentation width of the enclosing region. - - 3. On encountering a closing brace `}`, bracket `]` or parenthesis `)`, as many `` tokens as necessary are inserted to close all open nested indentation regions. - -For instance, consider: -```scala -{ - val x = f(x: Int, y => - x * ( - y + 1 - ) + - (x + - x) - ) -} -``` - - Here, the indentation width of the region enclosed by the braces is 3 (i.e. the indentation width of the -statement starting with `val`). - - The indentation width of the region in parentheses that follows `f` is also 3, since the opening - parenthesis is not at the end of a line. - - The indentation width of the region in parentheses around `y + 1` is 9 - (i.e. the indentation width of `y + 1`). - - Finally, the indentation width of the last region in parentheses starting with `(x` is 6 (i.e. the indentation width of the indented region following the `=>`. - -### Special Treatment of Case Clauses - -The indentation rules for `match` expressions and `catch` clauses are refined as follows: - -- An indentation region is opened after a `match` or `catch` also if the following `case` - appears at the indentation width that's current for the `match` itself. -- In that case, the indentation region closes at the first token at that - same indentation width that is not a `case`, or at any token with a smaller - indentation width, whichever comes first. - -The rules allow to write `match` expressions where cases are not indented themselves, as in the example below: - -```scala -x match -case 1 => print("I") -case 2 => print("II") -case 3 => print("III") -case 4 => print("IV") -case 5 => print("V") - -println(".") -``` - -### The End Marker - -Indentation-based syntax has many advantages over other conventions. But one possible problem is that it makes it hard to discern when a large indentation region ends, since there is no specific token that delineates the end. Braces are not much better since a brace by itself also contains no information about what region is closed. - -To solve this problem, Scala 3 offers an optional `end` marker. Example: - -```scala -def largeMethod(...) = - ... - if ... then ... - else - ... // a large block - end if - ... // more code -end largeMethod -``` - -An `end` marker consists of the identifier `end` and a follow-on specifier token that together constitute all the tokes of a line. Possible specifier tokens are -identifiers or one of the following keywords - -```scala -if while for match try new this val given -``` - -End markers are allowed in statement sequences. The specifier token `s` of an end marker must correspond to the statement that precedes it. This means: - -- If the statement defines a member `x` then `s` must be the same identifier `x`. -- If the statement defines a constructor then `s` must be `this`. -- If the statement defines an anonymous given, then `s` must be `given`. -- If the statement defines an anonymous extension, then `s` must be `extension`. -- If the statement defines an anonymous class, then `s` must be `new`. -- If the statement is a `val` definition binding a pattern, then `s` must be `val`. -- If the statement is a package clause that refers to package `p`, then `s` must be the same identifier `p`. -- If the statement is an `if`, `while`, `for`, `try`, or `match` statement, then `s` must be that same token. - -For instance, the following end markers are all legal: - -```scala -package p1.p2: - - abstract class C(): - - def this(x: Int) = - this() - if x > 0 then - val a :: b = - x :: Nil - end val - var y = - x - end y - while y > 0 do - println(y) - y -= 1 - end while - try - x match - case 0 => println("0") - case _ => - end match - finally - println("done") - end try - end if - end this - - def f: String - end C - - object C: - given C = - new C: - def f = "!" - end f - end new - end given - end C - - extension (x: C) - def ff: String = x.f ++ x.f - end extension - -end p2 -``` - -#### When to Use End Markers - -It is recommended that `end` markers are used for code where the extent of an indentation region is not immediately apparent "at a glance". People will have different preferences what this means, but one can nevertheless give some guidelines that stem from experience. An end marker makes sense if - -- the construct contains blank lines, or -- the construct is long, say 15-20 lines or more, -- the construct ends heavily indented, say 4 indentation levels or more. - -If none of these criteria apply, it's often better to not use an end marker since the code will be just as clear and more concise. If there are several ending regions that satisfy one of the criteria above, we usually need an end marker only for the outermost closed region. So cascades of end markers as in the example above are usually better avoided. - -#### Syntax - -``` -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL -EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ -BlockStat ::= ... | EndMarker -TemplateStat ::= ... | EndMarker -TopStat ::= ... | EndMarker -``` - -### Example - -Here is a (somewhat meta-circular) example of code using indentation. It provides a concrete representation of indentation widths as defined above together with efficient operations for constructing and comparing indentation widths. - -```scala -enum IndentWidth: - case Run(ch: Char, n: Int) - case Conc(l: IndentWidth, r: Run) - - def <= (that: IndentWidth): Boolean = this match - case Run(ch1, n1) => - that match - case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) - case Conc(l, r) => this <= l - case Conc(l1, r1) => - that match - case Conc(l2, r2) => l1 == l2 && r1 <= r2 - case _ => false - - def < (that: IndentWidth): Boolean = - this <= that && !(that <= this) - - override def toString: String = - this match - case Run(ch, n) => - val kind = ch match - case ' ' => "space" - case '\t' => "tab" - case _ => s"'$ch'-character" - val suffix = if n == 1 then "" else "s" - s"$n $kind$suffix" - case Conc(l, r) => - s"$l, $r" - -object IndentWidth: - private inline val MaxCached = 40 - - private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _)) - private val tabs = IArray.tabulate(MaxCached + 1)(new Run('\t', _)) - - def Run(ch: Char, n: Int): Run = - if n <= MaxCached && ch == ' ' then - spaces(n) - else if n <= MaxCached && ch == '\t' then - tabs(n) - else - new Run(ch, n) - end Run - - val Zero = Run(' ', 0) -end IndentWidth -``` - -### Settings and Rewrites - -Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. - -The Scala 3 compiler can rewrite source code to indented code and back. -When invoked with options `-rewrite -indent` it will rewrite braces to -indented regions where possible. When invoked with options `-rewrite -no-indent` it will rewrite in the reverse direction, inserting braces for indentation regions. -The `-indent` option only works on [new-style syntax](./control-syntax.html). So to go from old-style syntax to new-style indented code one has to invoke the compiler twice, first with options `-rewrite -new-syntax`, then again with options -`-rewrite -indent`. To go in the opposite direction, from indented code to old-style syntax, it's `-rewrite -no-indent`, followed by `-rewrite -old-syntax`. - -### Variant: Indentation Marker `:` - -Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to function can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. - -To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import -```scala -import language.experimental.fewerBraces -``` -This variant is more contentious and less stable than the rest of the significant indentation scheme. In this variant, a colon `:` at the end of a line is also one of the possible tokens that opens an indentation region. Examples: - -```scala -times(10): - println("ah") - println("ha") -``` - -or - -```scala -xs.map: - x => - val y = x - 1 - y * y -``` - -The colon is usable not only for lambdas and by-name parameters, but -also even for ordinary parameters: - -```scala -credentials ++ : - val file = Path.userHome / ".credentials" - if file.exists - then Seq(Credentials(file)) - else Seq() -``` - -How does this syntax variant work? Colons at the end of lines are their own token, distinct from normal `:`. -The Scala grammar is changed so that colons at end of lines are accepted at all points -where an opening brace enclosing an argument is legal. Special provisions are taken so that method result types can still use a colon on the end of a line, followed by the actual type on the next. diff --git a/_scala3-reference/other-new-features/kind-polymorphism.md b/_scala3-reference/other-new-features/kind-polymorphism.md deleted file mode 100644 index 7e69bca4ea..0000000000 --- a/_scala3-reference/other-new-features/kind-polymorphism.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Kind Polymorphism" -type: section -num: 42 -previous-page: /scala3/reference/other-new-features/parameter-untupling -next-page: /scala3/reference/other-new-features/matchable ---- - -Normally type parameters in Scala are partitioned into _kinds_. First-level types are types of values. Higher-kinded types are type constructors -such as `List` or `Map`. The kind of a type is indicated by the top type of which it is a subtype. Normal types are subtypes of `Any`, -covariant single argument type constructors such as `List` are subtypes of `[+X] =>> Any`, and the `Map` type constructor is -a subtype of `[X, +Y] =>> Any`. - -A type can be used only as prescribed by its kind. Subtypes of `Any` cannot be applied to type arguments whereas subtypes of `[X] =>> Any` -_must_ be applied to a type argument, unless they are passed to type parameters of the same kind. - -Sometimes we would like to have type parameters that can have more than one kind, for instance to define an implicit -value that works for parameters of any kind. This is now possible through a form of (_subtype_) kind polymorphism. -Kind polymorphism relies on the special type `scala.AnyKind` that can be used as an upper bound of a type. - -```scala -def f[T <: AnyKind] = ... -``` - -The actual type arguments of `f` can then be types of arbitrary kinds. So the following would all be legal: - -```scala -f[Int] -f[List] -f[Map] -f[[X] =>> String] -``` - -We call type parameters and abstract types with an `AnyKind` upper bound _any-kinded types_. -Since the actual kind of an any-kinded type is unknown, its usage must be heavily restricted: An any-kinded type -can be neither the type of a value, nor can it be instantiated with type parameters. So about the only -thing one can do with an any-kinded type is to pass it to another any-kinded type argument. -Nevertheless, this is enough to achieve some interesting generalizations that work across kinds, typically -through advanced uses of implicits. - -(todo: insert good concise example) - -Some technical details: `AnyKind` is a synthesized class just like `Any`, but without any members. It extends no other class. -It is declared `abstract` and `final`, so it can be neither instantiated nor extended. - -`AnyKind` plays a special role in Scala's subtype system: It is a supertype of all other types no matter what their kind is. It is also assumed to be kind-compatible with all other types. Furthermore, `AnyKind` is treated as a higher-kinded type (so it cannot be used as a type of values), but at the same time it has no type parameters (so it cannot be instantiated). - -**Note**: This feature is considered experimental but stable and it can be disabled under compiler flag -(i.e. `-Yno-kind-polymorphism`). diff --git a/_scala3-reference/other-new-features/matchable.md b/_scala3-reference/other-new-features/matchable.md deleted file mode 100644 index 1b2996f662..0000000000 --- a/_scala3-reference/other-new-features/matchable.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: "The Matchable Trait" -type: section -num: 43 -previous-page: /scala3/reference/other-new-features/kind-polymorphism -next-page: /scala3/reference/other-new-features/threadUnsafe-annotation ---- - -A new trait `Matchable` controls the ability to pattern match. - -### The Problem - -The Scala 3 standard library has a type `IArray` for immutable -arrays that is defined like this: - -```scala - opaque type IArray[+T] = Array[_ <: T] -``` - -The `IArray` type offers extension methods for `length` and `apply`, but not for `update`; hence it seems values of type `IArray` cannot be updated. - -However, there is a potential hole due to pattern matching. Consider: - -```scala -val imm: IArray[Int] = ... -imm match - case a: Array[Int] => a(0) = 1 -``` - -The test will succeed at runtime since `IArray`s _are_ represented as -`Array`s at runtime. But if we allowed it, it would break the fundamental abstraction of immutable arrays. - -__Aside:__ One could also achieve the same by casting: - -```scala -imm.asInstanceOf[Array[Int]](0) = 1 -``` - -But that is not as much of a problem since in Scala `asInstanceOf` is understood to be low-level and unsafe. By contrast, a pattern match that compiles without warning or error should not break abstractions. - -Note also that the problem is not tied to opaque types as match selectors. The following slight variant with a value of parametric -type `T` as match selector leads to the same problem: - -```scala -def f[T](x: T) = x match - case a: Array[Int] => a(0) = 0 -f(imm) -``` - -Finally, note that the problem is not linked to just opaque types. No unbounded type parameter or abstract type should be decomposable with a pattern match. - -### The Solution - -There is a new type `scala.Matchable` that controls pattern matching. When typing a pattern match of a constructor pattern `C(...)` or -a type pattern `_: C` it is required that the selector type conforms -to `Matchable`. If that's not the case a warning is issued. For instance when compiling the example at the start of this section we get: - -``` -> sc ../new/test.scala -source future --- Warning: ../new/test.scala:4:12 --------------------------------------------- -4 | case a: Array[Int] => a(0) = 0 - | ^^^^^^^^^^ - | pattern selector should be an instance of Matchable, - | but it has unmatchable type IArray[Int] instead -``` - -To allow migration from Scala 2 and cross-compiling -between Scala 2 and 3 the warning is turned on only for `-source future-migration` or higher. - -`Matchable` is a universal trait with `Any` as its parent class. It is -extended by both `AnyVal` and `AnyRef`. Since `Matchable` is a supertype of every concrete value or reference class it means that instances of such classes can be matched as before. However, match selectors of the following types will produce a warning: - -- Type `Any`: if pattern matching is required one should use `Matchable` instead. -- Unbounded type parameters and abstract types: If pattern matching is required they should have an upper bound `Matchable`. -- Type parameters and abstract types that are only bounded by some - universal trait: Again, `Matchable` should be added as a bound. - -Here is the hierarchy of top-level classes and traits with their defined methods: - -```scala -abstract class Any: - def getClass - def isInstanceOf - def asInstanceOf - def == - def != - def ## - def equals - def hashCode - def toString - -trait Matchable extends Any - -class AnyVal extends Any, Matchable -class Object extends Any, Matchable -``` - -`Matchable` is currently a marker trait without any methods. Over time -we might migrate methods `getClass` and `isInstanceOf` to it, since these are closely related to pattern-matching. - -### `Matchable` and Universal Equality - -Methods that pattern-match on selectors of type `Any` will need a cast once the -Matchable warning is turned on. The most common such method is the universal -`equals` method. It will have to be written as in the following example: - -```scala -class C(val x: String): - - override def equals(that: Any): Boolean = - that.asInstanceOf[Matchable] match - case that: C => this.x == that.x - case _ => false -``` - -The cast of `that` to `Matchable` serves as an indication that universal equality -is unsafe in the presence of abstract types and opaque types since it cannot properly distinguish the meaning of a type from its representation. The cast -is guaranteed to succeed at run-time since `Any` and `Matchable` both erase to -`Object`. - -For instance, consider the definitions - -```scala -opaque type Meter = Double -def Meter(x: Double): Meter = x - -opaque type Second = Double -def Second(x: Double): Second = x -``` - -Here, universal `equals` will return true for - -```scala - Meter(10).equals(Second(10)) -``` - -even though this is clearly false mathematically. With [multiversal equality](../contextual/multiversal-equality.html) one can mitigate that problem somewhat by turning - -```scala - import scala.language.strictEquality - Meter(10) == Second(10) -``` - -into a type error. diff --git a/_scala3-reference/other-new-features/named-typeargs-spec.md b/_scala3-reference/other-new-features/named-typeargs-spec.md deleted file mode 100644 index ddd8085598..0000000000 --- a/_scala3-reference/other-new-features/named-typeargs-spec.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Named Type Arguments - More Details" ---- - -## Syntax - -The addition to the grammar is: - -``` -SimpleExpr1 ::= ... - | SimpleExpr (TypeArgs | NamedTypeArgs) -NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ -NamedTypeArg ::= id ‘=’ Type -``` - -Note in particular that named arguments cannot be passed to type constructors: - -``` scala -class C[T] - -val x: C[T = Int] = // error - new C[T = Int] // error - -class E extends C[T = Int] // error -``` - -## Compatibility considerations - -Named type arguments do not have an impact on binary compatibility, but they -have an impact on source compatibility: if the name of a method type parameter -is changed, any existing named reference to this parameter will break. This -means that the names of method type parameters are now part of the public API -of a library. - -(Unimplemented proposal: to mitigate this, -[`scala.deprecatedName`](https://www.scala-lang.org/api/current/scala/deprecatedName.html) -could be extended to also be applicable on method type parameters.) diff --git a/_scala3-reference/other-new-features/named-typeargs.md b/_scala3-reference/other-new-features/named-typeargs.md deleted file mode 100644 index 23881307b8..0000000000 --- a/_scala3-reference/other-new-features/named-typeargs.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Named Type Arguments" ---- - -**Note:** This feature is implemented in Scala 3, but is not expected to be part of Scala 3.0. - -Type arguments of methods can now be specified by name as well as by position. Example: - -``` scala -def construct[Elem, Coll[_]](xs: Elem*): Coll[Elem] = ??? - -val xs1 = construct[Coll = List, Elem = Int](1, 2, 3) -val xs2 = construct[Coll = List](1, 2, 3) -``` - -Similar to a named value argument `(x = e)`, a named type argument -`[X = T]` instantiates the type parameter `X` to the type `T`. -Named type arguments do not have to be in order (see `xs1` above) and -unspecified arguments are inferred by the compiler (see `xs2` above). -Type arguments must be all named or un-named, mixtures of named and -positional type arguments are not supported. - -## Motivation - -The main benefit of named type arguments is that unlike positional arguments, -you are allowed to omit passing arguments for some parameters, like in the -definition of `xs2` above. A missing type argument is inferred as usual by -local type inference. This is particularly useful in situations where some type -arguments can be easily inferred from others. - -[More details](./named-typeargs-spec.html) diff --git a/_scala3-reference/other-new-features/opaques-details.md b/_scala3-reference/other-new-features/opaques-details.md deleted file mode 100644 index b7891a86c0..0000000000 --- a/_scala3-reference/other-new-features/opaques-details.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Opaque Type Aliases: More Details" ---- - -### Syntax - -``` -Modifier ::= ... - | ‘opaque’ -``` - -`opaque` is a [soft modifier](../soft-modifier.html). It can still be used as a normal identifier when it is not in front of a definition keyword. - -Opaque type aliases must be members of classes, traits, or objects, or they are defined -at the top-level. They cannot be defined in local blocks. - -### Type Checking - -The general form of a (monomorphic) opaque type alias is - -```scala -opaque type T >: L <: U = R -``` - -where the lower bound `L` and the upper bound `U` may be missing, in which case they are assumed to be `scala.Nothing` and `scala.Any`, respectively. If bounds are given, it is checked that the right-hand side `R` conforms to them, i.e. `L <: R` and `R <: U`. F-bounds are not supported for opaque type aliases: `T` is not allowed to appear in `L` or `U`. - -Inside the scope of the alias definition, the alias is transparent: `T` is treated -as a normal alias of `R`. Outside its scope, the alias is treated as the abstract type -```scala -type T >: L <: U -``` -A special case arises if the opaque type alias is defined in an object. Example: - -```scala -object o: - opaque type T = R -``` - -In this case we have inside the object (also for non-opaque types) that `o.T` is equal to -`T` or its expanded form `o.this.T`. Equality is understood here as mutual subtyping, i.e. -`o.T <: o.this.T` and `o.this.T <: T`. Furthermore, we have by the rules of opaque type aliases -that `o.this.T` equals `R`. The two equalities compose. That is, inside `o`, it is -also known that `o.T` is equal to `R`. This means the following code type-checks: - -```scala -object o: - opaque type T = Int - val x: Int = id(2) -def id(x: o.T): o.T = x -``` - -### Type Parameters of Opaque Types - -Opaque type aliases can have a single type parameter list. The following aliases -are well-formed -```scala -opaque type F[T] = (T, T) -opaque type G = [T] =>> List[T] -``` -but the following are not: -```scala -opaque type BadF[T] = [U] =>> (T, U) -opaque type BadG = [T] =>> [U] => (T, U) -``` - -### Translation of Equality - -Comparing two values of opaque type with `==` or `!=` normally uses universal equality, -unless another overloaded `==` or `!=` operator is defined for the type. To avoid -boxing, the operation is mapped after type checking to the (in-)equality operator -defined on the underlying type. For instance, -```scala - opaque type T = Int - - ... - val x: T - val y: T - x == y // uses Int equality for the comparison. -``` - -### Top-level Opaque Types - -An opaque type alias on the top-level is transparent in all other top-level definitions in the sourcefile where it appears, but is opaque in nested -objects and classes and in all other source files. Example: -```scala -// in test1.scala -opaque type A = String -val x: A = "abc" - -object obj: - val y: A = "abc" // error: found: "abc", required: A - -// in test2.scala -def z: String = x // error: found: A, required: String -``` -This behavior becomes clear if one recalls that top-level definitions are placed in their own synthetic object. For instance, the code in `test1.scala` would expand to -```scala -object test1$package: - opaque type A = String - val x: A = "abc" - -object obj: - val y: A = "abc" // error: cannot assign "abc" to opaque type alias A -``` -The opaque type alias `A` is transparent in its scope, which includes the definition of `x`, but not the definitions of `obj` and `y`. - - -### Relationship to SIP 35 - -Opaque types in Scala 3 are an evolution from what is described in -[Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html). - -The differences compared to the state described in this SIP are: - - 1. Opaque type aliases cannot be defined anymore in local statement sequences. - 2. The scope where an opaque type alias is visible is now the whole scope where - it is defined, instead of just a companion object. - 3. The notion of a companion object for opaque type aliases has been dropped. - 4. Opaque type aliases can have bounds. - 5. The notion of type equality involving opaque type aliases has been clarified. It was - strengthened with respect to the previous implementation of SIP 35. diff --git a/_scala3-reference/other-new-features/opaques.md b/_scala3-reference/other-new-features/opaques.md deleted file mode 100644 index cb4cf63567..0000000000 --- a/_scala3-reference/other-new-features/opaques.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: "Opaque Type Aliases" -type: section -num: 39 -previous-page: /scala3/reference/other-new-features/export -next-page: /scala3/reference/other-new-features/open-classes ---- - -Opaque types aliases provide type abstraction without any overhead. Example: - -```scala -object MyMath: - - opaque type Logarithm = Double - - object Logarithm: - - // These are the two ways to lift to the Logarithm type - - def apply(d: Double): Logarithm = math.log(d) - - def safe(d: Double): Option[Logarithm] = - if d > 0.0 then Some(math.log(d)) else None - - end Logarithm - - // Extension methods define opaque types' public APIs - extension (x: Logarithm) - def toDouble: Double = math.exp(x) - def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) - def * (y: Logarithm): Logarithm = x + y - -end MyMath -``` - -This introduces `Logarithm` as a new abstract type, which is implemented as `Double`. -The fact that `Logarithm` is the same as `Double` is only known in the scope where -`Logarithm` is defined which in the above example corresponds to the object `MyMath`. -Or in other words, within the scope it is treated as type alias, but this is opaque to the outside world -where in consequence `Logarithm` is seen as an abstract type and has nothing to do with `Double`. - -The public API of `Logarithm` consists of the `apply` and `safe` methods defined in the companion object. -They convert from `Double`s to `Logarithm` values. Moreover, an operation `toDouble` that converts the other way, and operations `+` and `*` are defined as extension methods on `Logarithm` values. -The following operations would be valid because they use functionality implemented in the `MyMath` object. - -```scala -import MyMath.Logarithm - -val l = Logarithm(1.0) -val l2 = Logarithm(2.0) -val l3 = l * l2 -val l4 = l + l2 -``` - -But the following operations would lead to type errors: - -```scala -val d: Double = l // error: found: Logarithm, required: Double -val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm -l * 2 // error: found: Int(2), required: Logarithm -l / l2 // error: `/` is not a member of Logarithm -``` - -### Bounds For Opaque Type Aliases - -Opaque type aliases can also come with bounds. Example: - -```scala -object Access: - - opaque type Permissions = Int - opaque type PermissionChoice = Int - opaque type Permission <: Permissions & PermissionChoice = Int - - extension (x: Permissions) - def & (y: Permissions): Permissions = x | y - extension (x: PermissionChoice) - def | (y: PermissionChoice): PermissionChoice = x | y - extension (granted: Permissions) - def is(required: Permissions) = (granted & required) == required - extension (granted: Permissions) - def isOneOf(required: PermissionChoice) = (granted & required) != 0 - - val NoPermission: Permission = 0 - val Read: Permission = 1 - val Write: Permission = 2 - val ReadWrite: Permissions = Read | Write - val ReadOrWrite: PermissionChoice = Read | Write - -end Access -``` - -The `Access` object defines three opaque type aliases: - -- `Permission`, representing a single permission, -- `Permissions`, representing a set of permissions with the meaning "all of these permissions granted", -- `PermissionChoice`, representing a set of permissions with the meaning "at least one of these permissions granted". - -Outside the `Access` object, values of type `Permissions` may be combined using the `&` operator, -where `x & y` means "all permissions in `x` *and* in `y` granted". -Values of type `PermissionChoice` may be combined using the `|` operator, -where `x | y` means "a permission in `x` *or* in `y` granted". - -Note that inside the `Access` object, the `&` and `|` operators always resolve to the corresponding methods of `Int`, -because members always take precedence over extension methods. -Because of that, the `|` extension method in `Access` does not cause infinite recursion. -Also, the definition of `ReadWrite` must use `|`, -even though an equivalent definition outside `Access` would use `&`. - -All three opaque type aliases have the same underlying representation type `Int`. The -`Permission` type has an upper bound `Permissions & PermissionChoice`. This makes -it known outside the `Access` object that `Permission` is a subtype of the other -two types. Hence, the following usage scenario type-checks. - -```scala -object User: - import Access.* - - case class Item(rights: Permissions) - - val roItem = Item(Read) // OK, since Permission <: Permissions - val rwItem = Item(ReadWrite) - val noItem = Item(NoPermission) - - assert(!roItem.rights.is(ReadWrite)) - assert(roItem.rights.isOneOf(ReadOrWrite)) - - assert(rwItem.rights.is(ReadWrite)) - assert(rwItem.rights.isOneOf(ReadOrWrite)) - - assert(!noItem.rights.is(ReadWrite)) - assert(!noItem.rights.isOneOf(ReadOrWrite)) -end User -``` - -On the other hand, the call `roItem.rights.isOneOf(ReadWrite)` would give a type error -since `Permissions` and `PermissionChoice` are different, unrelated types outside `Access`. - - -### Opaque Type Members on Classes -While typically, opaque types are used together with objects to hide implementation details of a module, they can also be used with classes. - -For example, we can redefine the above example of Logarithms as a class. -```scala -class Logarithms: - - opaque type Logarithm = Double - - def apply(d: Double): Logarithm = math.log(d) - - def safe(d: Double): Option[Logarithm] = - if d > 0.0 then Some(math.log(d)) else None - - def mul(x: Logarithm, y: Logarithm) = x + y -``` - -Opaque type members of different instances are treated as different: -```scala -val l1 = new Logarithms -val l2 = new Logarithms -val x = l1(1.5) -val y = l1(2.6) -val z = l2(3.1) -l1.mul(x, y) // type checks -l1.mul(x, z) // error: found l2.Logarithm, required l1.Logarithm -``` -In general, one can think of an opaque type as being only transparent in the scope of `private[this]`. - -[More details](opaques-details.html) diff --git a/_scala3-reference/other-new-features/open-classes.md b/_scala3-reference/other-new-features/open-classes.md deleted file mode 100644 index 342faed61a..0000000000 --- a/_scala3-reference/other-new-features/open-classes.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: "Open Classes" -type: section -num: 40 -previous-page: /scala3/reference/other-new-features/opaques -next-page: /scala3/reference/other-new-features/parameter-untupling ---- - -An `open` modifier on a class signals that the class is planned for extensions. Example: -```scala -// File Writer.scala -package p - -open class Writer[T]: - - /** Sends to stdout, can be overridden */ - def send(x: T) = println(x) - - /** Sends all arguments using `send` */ - def sendAll(xs: T*) = xs.foreach(send) -end Writer - -// File EncryptedWriter.scala -package p - -class EncryptedWriter[T: Encryptable] extends Writer[T]: - override def send(x: T) = super.send(encrypt(x)) -``` -An open class typically comes with some documentation that describes -the internal calling patterns between methods of the class as well as hooks that can be overridden. We call this the _extension contract_ of the class. It is different from the _external contract_ between a class and its users. - -Classes that are not open can still be extended, but only if at least one of two alternative conditions is met: - - - The extending class is in the same source file as the extended class. In this case, the extension is usually an internal implementation matter. - - - The language feature `adhocExtensions` is enabled for the extending class. This is typically enabled by an import clause in the source file of the extension: - ```scala - import scala.language.adhocExtensions - ``` - Alternatively, the feature can be enabled by the compiler option `-language:adhocExtensions`. - If the feature is not enabled, the compiler will issue a "feature" warning. For instance, if the `open` modifier on class `Writer` is dropped, compiling `EncryptedWriter` would produce a warning: - ``` - -- Feature Warning: EncryptedWriter.scala:6:14 ---- - |class EncryptedWriter[T: Encryptable] extends Writer[T] - | ^ - |Unless class Writer is declared 'open', its extension - | in a separate file should be enabled - |by adding the import clause 'import scala.language.adhocExtensions' - |or by setting the compiler option -language:adhocExtensions. - ``` - -### Motivation - -When writing a class, there are three possible expectations of extensibility: - -1. The class is intended to allow extensions. This means one should expect -a carefully worked out and documented extension contract for the class. - -2. Extensions of the class are forbidden, for instance to make correctness or security guarantees. - -3. There is no firm decision either way. The class is not _a priori_ intended for extensions, but if others find it useful to extend on an _ad-hoc_ basis, let them go ahead. However, they are on their own in this case. There is no documented extension contract, and future versions of the class might break the extensions (by rearranging internal call patterns, for instance). - -The three cases are clearly distinguished by using `open` for (1), `final` for (2) and no modifier for (3). - -It is good practice to avoid _ad-hoc_ extensions in a code base, since they tend to lead to fragile systems that are hard to evolve. But there -are still some situations where these extensions are useful: for instance, -to mock classes in tests, or to apply temporary patches that add features or fix bugs in library classes. That's why _ad-hoc_ extensions are permitted, but only if there is an explicit opt-in via a language feature import. - -### Details - - - `open` is a soft modifier. It is treated as a normal identifier - unless it is in modifier position. - - An `open` class cannot be `final` or `sealed`. - - Traits or `abstract` classes are always `open`, so `open` is redundant for them. - -### Relationship with `sealed` - -A class that is neither `abstract` nor `open` is similar to a `sealed` class: it can still be extended, but only in the same source file. The difference is what happens if an extension of the class is attempted in another source file. For a `sealed` class, this is an error, whereas for a simple non-open class, this is still permitted provided the `adhocExtensions` feature is enabled, and it gives a warning otherwise. - -### Migration - -`open` is a new modifier in Scala 3. To allow cross compilation between Scala 2.13 and Scala 3.0 without warnings, the feature warning for ad-hoc extensions is produced only under `-source future`. It will be produced by default from Scala 3.1 on. diff --git a/_scala3-reference/other-new-features/parameter-untupling-spec.md b/_scala3-reference/other-new-features/parameter-untupling-spec.md deleted file mode 100644 index dbabf9d5d7..0000000000 --- a/_scala3-reference/other-new-features/parameter-untupling-spec.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: "Parameter Untupling - More Details" ---- - -## Motivation - -Say you have a list of pairs - -```scala -val xs: List[(Int, Int)] -``` - -and you want to map `xs` to a list of `Int`s so that each pair of numbers is mapped to their sum. -Previously, the best way to do this was with a pattern-matching decomposition: - -```scala -xs.map { - case (x, y) => x + y -} -``` -While correct, this is inconvenient. Instead, we propose to write it the following way: - -```scala -xs.map { - (x, y) => x + y -} -``` - -or, equivalently: - -```scala -xs.map(_ + _) -``` - -Generally, a function value with `n > 1` parameters can be converted to a function with tupled arguments if the expected type is a unary function type of the form `((T_1, ..., T_n)) => U`. - -## Type Checking - -The type checking happens in two steps: - -1. Check whether parameter untupling is feasible -2. Adapt the function and type check it - -### Feasibility Check - -Suppose a function `f` of the form `(p1, ..., pn) => e` (where `n > 1`), with `p1, ..., pn` as parameters and `e` as function body. - -If the expected type for checking `f` is a fully defined function type of the form `TupleN[T1, ..., Tn] => R` (or an equivalent SAM-type), where each type `Ti` fits the corresponding parameter `pi`. Then `f` is feasible for parameter untupling with the expected type `TupleN[T1, ..., Tn] => R`. - -A type `Ti` fits a parameter `pi` if one of the following two cases is `true`: - -* `pi` comes without a type, i.e. it is a simple identifier or `_`. -* `pi` is of the form `x: Ui` or `_: Ui` and `Ti <: Ui`. - -Parameter untupling composes with eta-expansion. That is, an n-ary function generated by eta-expansion can in turn be adapted to the expected type with parameter untupling. - -### Term adaptation - -If the function - -```scala -(p1, ..., pn) => e -``` - -is feasible for parameter untupling with the expected type `TupleN[T1, ..., Tn] => Te`, then continue to type check the following adapted function - -```scala -(x: TupleN[T1, ..., Tn]) => - def p1: T1 = x._1 - ... - def pn: Tn = x._n - e -``` - -with the same expected type. -## Migration - -Code like this could not be written before, hence the new notation would not be ambiguous after adoption. - -Though it is possible that someone has written an implicit conversion form `(T1, ..., Tn) => R` to `TupleN[T1, ..., Tn] => R` -for some `n`. This change could be detected and fixed by [`Scalafix`](https://scalacenter.github.io/scalafix/). Furthermore, such conversion would probably -be doing the same translation (semantically) but in a less efficient way. - -## Reference - -For more information, see [Issue #897](https://github.com/lampepfl/dotty/issues/897). diff --git a/_scala3-reference/other-new-features/parameter-untupling.md b/_scala3-reference/other-new-features/parameter-untupling.md deleted file mode 100644 index de58f8c61f..0000000000 --- a/_scala3-reference/other-new-features/parameter-untupling.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Parameter Untupling" -type: section -num: 41 -previous-page: /scala3/reference/other-new-features/open-classes -next-page: /scala3/reference/other-new-features/kind-polymorphism ---- - -Say you have a list of pairs - -```scala -val xs: List[(Int, Int)] -``` - -and you want to map `xs` to a list of `Int`s so that each pair of numbers is mapped to -their sum. Previously, the best way to do this was with a pattern-matching decomposition: - -```scala -xs map { - case (x, y) => x + y -} -``` - -While correct, this is also inconvenient and confusing, since the `case` -suggests that the pattern match could fail. As a shorter and clearer alternative Scala 3 now allows - -```scala -xs.map { - (x, y) => x + y -} -``` - -or, equivalently: - -```scala -xs.map(_ + _) -``` - -Generally, a function value with `n > 1` parameters is converted to a -pattern-matching closure using `case` if the expected type is a unary -function type of the form `((T_1, ..., T_n)) => U`. - -## Reference - -For more information see: - -* [More details](./parameter-untupling-spec.html) -* [Issue #897](https://github.com/lampepfl/dotty/issues/897). diff --git a/_scala3-reference/other-new-features/safe-initialization.md b/_scala3-reference/other-new-features/safe-initialization.md deleted file mode 100644 index 7ecafb9a28..0000000000 --- a/_scala3-reference/other-new-features/safe-initialization.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: "Safe Initialization" -type: section -num: 49 -previous-page: /scala3/reference/other-new-features/explicit-nulls -next-page: /scala3/reference/other-new-features/type-test ---- - -Scala 3 implements experimental safe initialization check, which can be enabled by the compiler option `-Ysafe-init`. - -## A Quick Glance - -To get a feel of how it works, we first show several examples below. - -### Parent-Child Interaction - -Given the following code snippet: - -``` scala -abstract class AbstractFile: - def name: String - val extension: String = name.substring(4) - -class RemoteFile(url: String) extends AbstractFile: - val localFile: String = s"${url.##}.tmp" // error: usage of `localFile` before it's initialized - def name: String = localFile -``` - -The checker will report: - -``` scala --- Warning: tests/init/neg/AbstractFile.scala:7:4 ------------------------------ -7 | val localFile: String = s"${url.##}.tmp" // error: usage of `localFile` before it's initialized - | ^ - | Access non-initialized field value localFile. Calling trace: - | -> val extension: String = name.substring(4) [ AbstractFile.scala:3 ] - | -> def name: String = localFile [ AbstractFile.scala:8 ] -``` - -### Inner-Outer Interaction - -Given the code below: - -``` scala -object Trees: - class ValDef { counter += 1 } - class EmptyValDef extends ValDef - val theEmptyValDef = new EmptyValDef - private var counter = 0 // error -``` - -The checker will report: - -``` scala --- Warning: tests/init/neg/trees.scala:5:14 ------------------------------------ -5 | private var counter = 0 // error - | ^ - | Access non-initialized field variable counter. Calling trace: - | -> val theEmptyValDef = new EmptyValDef [ trees.scala:4 ] - | -> class EmptyValDef extends ValDef [ trees.scala:3 ] - | -> class ValDef { counter += 1 } [ trees.scala:2 ] -``` - -### Functions - -Given the code below: - -``` scala -abstract class Parent: - val f: () => String = () => this.message - def message: String - -class Child extends Parent: - val a = f() - val b = "hello" // error - def message: String = b -``` - -The checker reports: - -``` scala --- Warning: tests/init/neg/features-high-order.scala:7:6 ----------------------- -7 | val b = "hello" // error - | ^ - |Access non-initialized field value b. Calling trace: - | -> val a = f() [ features-high-order.scala:6 ] - | -> val f: () => String = () => this.message [ features-high-order.scala:2 ] - | -> def message: String = b [ features-high-order.scala:8 ] -``` -## Design Goals - -We establish the following design goals: - -- __Sound__: checking always terminates, and is sound for common and reasonable usage (over-approximation) -- __Expressive__: support common and reasonable initialization patterns -- __Friendly__: simple rules, minimal syntactic overhead, informative error messages -- __Modular__: modular checking, no analysis beyond project boundary -- __Fast__: instant feedback -- __Simple__: no changes to core type system, explainable by a simple theory - -By _reasonable usage_, we include the following use cases (but not restricted to them): - -- Access fields on `this` and outer `this` during initialization -- Call methods on `this` and outer `this` during initialization -- Instantiate inner class and call methods on such instances during initialization -- Capture fields in functions - -## Principles - -To achieve the goals, we uphold three fundamental principles: -_stackability_, _monotonicity_ and _scopability_. - -Stackability means that all fields of a class are initialized at the end of the -class body. Scala enforces this property in syntax by demanding that all fields -are initialized at the end of the primary constructor, except for the language -feature below: - -``` scala -var x: T = _ -``` - -Control effects such as exceptions may break this property, as the -following example shows: - -``` scala -class MyException(val b: B) extends Exception("") -class A: - val b = try { new B } catch { case myEx: MyException => myEx.b } - println(b.a) - -class B: - throw new MyException(this) - val a: Int = 1 -``` - -In the code above, the control effect teleport the uninitialized value -wrapped in an exception. In the implementation, we avoid the problem -by ensuring that the values that are thrown must be transitively initialized. - -Monotonicity means that the initialization status of an object should -not go backward: initialized fields continue to be initialized, a -field points to an initialized object may not later point to an -object under initialization. As an example, the following code will be rejected: - -``` scala -trait Reporter: - def report(msg: String): Unit - -class FileReporter(ctx: Context) extends Reporter: - ctx.typer.reporter = this // ctx now reaches an uninitialized object - val file: File = new File("report.txt") - def report(msg: String) = file.write(msg) -``` - -In the code above, suppose `ctx` points to a transitively initialized -object. Now the assignment at line 3 makes `this`, which is not fully -initialized, reachable from `ctx`. This makes field usage dangerous, -as it may indirectly reach uninitialized fields. - -Monotonicity is based on a well-known technique called _heap monotonic -typestate_ to ensure soundness in the presence of aliasing -[1]. Roughly speaking, it means initialization state should not go backwards. - -Scopability means that there are no side channels to access to partially constructed objects. Control effects like coroutines, delimited -control, resumable exceptions may break the property, as they can transport a -value upper in the stack (not in scope) to be reachable from the current scope. -Static fields can also serve as a teleport thus breaks this property. In the -implementation, we need to enforce that teleported values are transitively -initialized. - -The principles enable _local reasoning_ of initialization, which means: - -> An initialized environment can only produce initialized values. - -For example, if the arguments to an `new`-expression are transitively -initialized, so is the result. If the receiver and arguments in a method call -are transitively initialized, so is the result. - -## Rules - -With the established principles and design goals, following rules are imposed: - -1. In an assignment `o.x = e`, the expression `e` may only point to transitively initialized objects. - - This is how monotonicity is enforced in the system. Note that in an - initialization `val f: T = e`, the expression `e` may point to an object - under initialization. This requires a distinction between mutation and - initialization in order to enforce different rules. Scala - has different syntax for them, it thus is not an issue. - -2. Objects under initialization may not be passed as arguments to method calls. - - Escape of `this` in the constructor is commonly regarded as an anti-pattern. - However, escape of `this` as constructor arguments are allowed, to support - creation of cyclic data structures. The checker will ensure that the escaped - non-initialized object is not used, i.e. calling methods or accessing fields - on the escaped object is not allowed. - -3. Local definitions may only refer to transitively initialized objects. - - It means that in a local definition `val x: T = e`, the expression `e` may - only evaluate to transitively initialized objects. The same goes for local - lazy variables and methods. This rule is again motivated for simplicity in - reasoning about initialization: programmers may safely assume that all local - definitions only point to transitively initialized objects. - -## Modularity - -The analysis takes the primary constructor of concrete classes as entry points. -It follows the constructors of super classes, which might be defined in another project. -The analysis takes advantage of TASTy for analyzing super classes defined in another project. - -The crossing of project boundary raises a concern about modularity. It is -well-known in object-oriented programming that superclass and subclass are -tightly coupled. For example, adding a method in the superclass requires -recompiling the child class for checking safe overriding. - -Initialization is no exception in this respect. The initialization of an object -essentially invovles close interaction between subclass and superclass. If the -superclass is defined in another project, the crossing of project boundary -cannot be avoided for soundness of the analysis. - -Meanwhile, inheritance across project boundary has been under scrutiny and the -introduction of [open classes](./open-classes.html) mitigate the concern here. -For example, the initialization check could enforce that the constructors of -open classes may not contain method calls on `this` or introduce annotations as -a contract. - -The feedback from the community on the topic is welcome. - -## Back Doors - -Occasionally you may want to suppress warnings reported by the -checker. You can either write `e: @unchecked` to tell the checker to -skip checking for the expression `e`, or you may use the old trick: -mark some fields as lazy. - -## Caveats - -- The system cannot provide safety guarantee when extending Java or Scala 2 classes. -- Safe initialization of global objects is only partially checked. - -## References - -1. Fähndrich, M. and Leino, K.R.M., 2003, July. [_Heap monotonic typestates_](https://www.microsoft.com/en-us/research/publication/heap-monotonic-typestate/). In International Workshop on Aliasing, Confinement and Ownership in object-oriented programming (IWACO). -2. Fengyun Liu, Ondřej Lhoták, Aggelos Biboudis, Paolo G. Giarrusso, and Martin Odersky. 2020. [_A type-and-effect system for object initialization_](https://dl.acm.org/doi/10.1145/3428243). OOPSLA, 2020. diff --git a/_scala3-reference/other-new-features/targetName.md b/_scala3-reference/other-new-features/targetName.md deleted file mode 100644 index 377bd2db10..0000000000 --- a/_scala3-reference/other-new-features/targetName.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: "The @targetName annotation" -type: section -num: 45 -previous-page: /scala3/reference/other-new-features/threadUnsafe-annotation -next-page: /scala3/reference/other-new-features/control-syntax ---- - -A `@targetName` annotation on a definition defines an alternate name for the implementation of that definition. Example: - -```scala -import scala.annotation.targetName - -object VecOps: - extension [T](xs: Vec[T]) - @targetName("append") - def ++= [T] (ys: Vec[T]): Vec[T] = ... -``` - -Here, the `++=` operation is implemented (in Byte code or native code) under the name `append`. The implementation name affects the code that is generated, and is the name under which code from other languages can call the method. For instance, `++=` could be invoked from Java like this: - -```java -VecOps.append(vec1, vec2) -``` - -The `@targetName` annotation has no bearing on Scala usages. Any application of that method in Scala has to use `++=`, not `append`. - -### Details - - 1. `@targetName` is defined in package `scala.annotation`. It takes a single argument - of type `String`. That string is called the _external name_ of the definition - that's annotated. - - 2. A `@targetName` annotation can be given for all kinds of definitions. - - 3. The name given in a `@targetName` annotation must be a legal name - for the defined entities on the host platform. - - 4. It is recommended that definitions with symbolic names have a `@targetName` annotation. This will establish an alternate name that is easier to search for and - will avoid cryptic encodings in runtime diagnostics. - - 5. Definitions with names in backticks that are not legal host platform names - should also have a `@targetName` annotation. - -### Relationship with Overriding - -`@targetName` annotations are significant for matching two method definitions to decide whether they conflict or override each other. Two method definitions match if they have the same name, signature, and erased name. Here, - -- The _signature_ of a definition consists of the names of the erased types of all (value-) parameters and the method's result type. -- The _erased name_ of a method definition is its target name if a `@targetName` - annotation is given and its defined name otherwise. - -This means that `@targetName` annotations can be used to disambiguate two method definitions that would otherwise clash. For instance. - -```scala -def f(x: => String): Int = x.length -def f(x: => Int): Int = x + 1 // error: double definition -``` - -The two definitions above clash since their erased parameter types are both `Function0`, which is the type of the translation of a by-name-parameter. Hence -they have the same names and signatures. But we can avoid the clash by adding a `@targetName` annotation to either method or to both of them. Example: - -```scala -@targetName("f_string") -def f(x: => String): Int = x.length -def f(x: => Int): Int = x + 1 // OK -``` - -This will produce methods `f_string` and `f` in the generated code. - -However, `@targetName` annotations are not allowed to break overriding relationships -between two definitions that have otherwise the same names and types. So the following would be in error: - -```scala -import annotation.targetName -class A: - def f(): Int = 1 -class B extends A: - @targetName("g") def f(): Int = 2 -``` - -The compiler reports here: - -``` --- Error: test.scala:6:23 ------------------------------------------------------ -6 | @targetName("g") def f(): Int = 2 - | ^ - |error overriding method f in class A of type (): Int; - | method f of type (): Int should not have a @targetName - | annotation since the overridden member hasn't one either -``` - -The relevant overriding rules can be summarized as follows: - -- Two members can override each other if their names and signatures are the same, - and they either have the same erased names or the same types. -- If two members override, then both their erased names and their types must be the same. - -As usual, any overriding relationship in the generated code must also -be present in the original code. So the following example would also be in error: - -```scala -import annotation.targetName -class A: - def f(): Int = 1 -class B extends A: - @targetName("f") def g(): Int = 2 -``` - -Here, the original methods `g` and `f` do not override each other since they have -different names. But once we switch to target names, there is a clash that is reported by the compiler: - -``` --- [E120] Naming Error: test.scala:4:6 ----------------------------------------- -4 |class B extends A: - | ^ - | Name clash between defined and inherited member: - | def f(): Int in class A at line 3 and - | def g(): Int in class B at line 5 - | have the same name and type after erasure. -1 error found -``` diff --git a/_scala3-reference/other-new-features/threadUnsafe-annotation.md b/_scala3-reference/other-new-features/threadUnsafe-annotation.md deleted file mode 100644 index 0916f6ff9b..0000000000 --- a/_scala3-reference/other-new-features/threadUnsafe-annotation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: "The @threadUnsafe annotation" -type: section -num: 44 -previous-page: /scala3/reference/other-new-features/matchable -next-page: /scala3/reference/other-new-features/targetName ---- - -A new annotation `@threadUnsafe` can be used on a field which defines -a `lazy val`. When this annotation is used, the initialization of the -`lazy val` will use a faster mechanism which is not thread-safe. - -### Example - -```scala -import scala.annotation.threadUnsafe - -class Hello: - @threadUnsafe lazy val x: Int = 1 -``` diff --git a/_scala3-reference/other-new-features/trait-parameters.md b/_scala3-reference/other-new-features/trait-parameters.md deleted file mode 100644 index a3a692f91c..0000000000 --- a/_scala3-reference/other-new-features/trait-parameters.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: "Trait Parameters" -type: section -num: 35 -previous-page: /scala3/reference/other-new-features -next-page: /scala3/reference/other-new-features/transparent-traits ---- - -Scala 3 allows traits to have parameters, just like classes have parameters. - -```scala -trait Greeting(val name: String): - def msg = s"How are you, $name" - -class C extends Greeting("Bob"): - println(msg) -``` - -Arguments to a trait are evaluated immediately before the trait is initialized. - -One potential issue with trait parameters is how to prevent -ambiguities. For instance, you might try to extend `Greeting` twice, -with different parameters. - -```scala -class D extends C, Greeting("Bill") // error: parameter passed twice -``` - -Should this print "Bob" or "Bill"? In fact this program is illegal, -because it violates the second rule of the following for trait parameters: - - 1. If a class `C` extends a parameterized trait `T`, and its superclass does not, `C` _must_ pass arguments to `T`. - - 2. If a class `C` extends a parameterized trait `T`, and its superclass does as well, `C` _must not_ pass arguments to `T`. - - 3. Traits must never pass arguments to parent traits. - -Here's a trait extending the parameterized trait `Greeting`. - -```scala -trait FormalGreeting extends Greeting: - override def msg = s"How do you do, $name" -``` -As is required, no arguments are passed to `Greeting`. However, this poses an issue -when defining a class that extends `FormalGreeting`: - -```scala -class E extends FormalGreeting // error: missing arguments for `Greeting`. -``` - -The correct way to write `E` is to extend both `Greeting` and -`FormalGreeting` (in either order): - -```scala -class E extends Greeting("Bob"), FormalGreeting -``` - -### Traits With Context Parameters - -This "explicit extension required" rule is relaxed if the missing trait contains only -[context parameters]({% link _scala3-reference/contextual/using-clauses.md %}). In that case the trait reference is -implicitly inserted as an additional parent with inferred arguments. For instance, -here's a variant of greetings where the addressee is a context parameter of type -`ImpliedName`: - -```scala -case class ImpliedName(name: String): - override def toString = name - -trait ImpliedGreeting(using val iname: ImpliedName): - def msg = s"How are you, $iname" - -trait ImpliedFormalGreeting extends ImpliedGreeting: - override def msg = s"How do you do, $iname" - -class F(using iname: ImpliedName) extends ImpliedFormalGreeting -``` - -The definition of `F` in the last line is implicitly expanded to -```scala -class F(using iname: ImpliedName) extends - Object, - ImpliedGreeting(using iname), - ImpliedFormalGreeting(using iname) -``` -Note the inserted reference to the super trait `ImpliedGreeting`, which was not mentioned explicitly. - -## Reference - -For more information, see [Scala SIP 25](http://docs.scala-lang.org/sips/pending/trait-parameters.html). diff --git a/_scala3-reference/other-new-features/transparent-traits.md b/_scala3-reference/other-new-features/transparent-traits.md deleted file mode 100644 index 5621ca5914..0000000000 --- a/_scala3-reference/other-new-features/transparent-traits.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: "Transparent Traits" -type: section -num: 36 -previous-page: /scala3/reference/other-new-features/trait-parameters -next-page: /scala3/reference/other-new-features/creator-applications ---- - -Traits are used in two roles: - - 1. As mixins for other classes and traits - 2. As types of vals, defs, or parameters - -Some traits are used primarily in the first role, and we usually do not want to see them in inferred types. An example is the `Product` trait that the compiler adds as a mixin trait to every case class or case object. In Scala 2, this parent trait sometimes makes inferred types more complicated than they should be. Example: - -```scala -trait Kind -case object Var extends Kind -case object Val extends Kind -val x = Set(if condition then Val else Var) -``` - -Here, the inferred type of `x` is `Set[Kind & Product & Serializable]` whereas one would have hoped it to be `Set[Kind]`. The reasoning for this particular type to be inferred is as follows: - -- The type of the conditional above is the [union type](../new-types/union-types.html) `Val | Var`. -- A union type is widened in type inference to the least supertype that is not a union type. - In the example, this type is `Kind & Product & Serializable` since all three traits are traits of both `Val` and `Var`. - So that type becomes the inferred element type of the set. - -Scala 3 allows one to mark a mixin trait as `transparent`, which means that it can be suppressed in type inference. Here's an example that follows the lines of the code above, but now with a new transparent trait `S` instead of `Product`: - -```scala -transparent trait S -trait Kind -object Var extends Kind, S -object Val extends Kind, S -val x = Set(if condition then Val else Var) -``` - -Now `x` has inferred type `Set[Kind]`. The common transparent trait `S` does not -appear in the inferred type. - -## Transparent Traits - -The traits `scala.Product`, `java.lang.Serializable` and `java.lang.Comparable` -are treated automatically as transparent. Other traits are turned into transparent traits using the modifier `transparent`. Scala 2 traits can also be made transparent -by adding a [`@transparentTrait` annotation](https://scala-lang.org/api/3.x/scala/annotation/transparentTrait.html). This annotation is defined in `scala.annotation`. It will be deprecated and phased out once Scala 2/3 interoperability is no longer needed. - -Typically, transparent traits are traits -that influence the implementation of inheriting classes and traits that are not usually used as types by themselves. Two examples from the standard collection library are: - -- `IterableOps`, which provides method implementations for an `Iterable`. -- `StrictOptimizedSeqOps`, which optimises some of these implementations for sequences with efficient indexing. - -Generally, any trait that is extended recursively is a good candidate to be -declared transparent. - -## Rules for Inference - -Transparent traits can be given as explicit types as usual. But they are often elided when types are inferred. Roughly, the rules for type inference say that transparent traits are dropped from intersections where possible. - -The precise rules are as follows: - -- When inferring a type of a type variable, or the type of a val, or the return type of a def, -- where that type is not higher-kinded, -- and where `B` is its known upper bound or `Any` if none exists: -- If the type inferred so far is of the form `T1 & ... & Tn` where - `n >= 1`, replace the maximal number of transparent `Ti`s by `Any`, while ensuring that - the resulting type is still a subtype of the bound `B`. -- However, do not perform this widening if all transparent traits `Ti` can get replaced in that way. - -The last clause ensures that a single transparent trait instance such as `Product` is not widened to `Any`. Transparent trait instances are only dropped when they appear in conjunction with some other type. diff --git a/_scala3-reference/other-new-features/type-test.md b/_scala3-reference/other-new-features/type-test.md deleted file mode 100644 index 1fab03490e..0000000000 --- a/_scala3-reference/other-new-features/type-test.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: "TypeTest" -type: section -num: 50 -previous-page: /scala3/reference/other-new-features/safe-initialization -next-page: /scala3/reference/changed-features ---- - -## TypeTest - -When pattern matching there are two situations where a runtime type test must be performed. -The first case is an explicit type test using the ascription pattern notation. - -```scala -(x: X) match - case y: Y => -``` - -The second case is when an extractor takes an argument that is not a subtype of the scrutinee type. - -```scala -(x: X) match - case y @ Y(n) => - -object Y: - def unapply(x: Y): Some[Int] = ... -``` - -In both cases, a class test will be performed at runtime. -But when the type test is on an abstract type (type parameter or type member), the test cannot be performed because the type is erased at runtime. - -A `TypeTest` can be provided to make this test possible. - -```scala -package scala.reflect - -trait TypeTest[-S, T]: - def unapply(s: S): Option[s.type & T] -``` - -It provides an extractor that returns its argument typed as a `T` if the argument is a `T`. -It can be used to encode a type test. - -```scala -def f[X, Y](x: X)(using tt: TypeTest[X, Y]): Option[Y] = x match - case tt(x @ Y(1)) => Some(x) - case tt(x) => Some(x) - case _ => None -``` - -To avoid the syntactic overhead the compiler will look for a type test automatically if it detects that the type test is on abstract types. -This means that `x: Y` is transformed to `tt(x)` and `x @ Y(_)` to `tt(x @ Y(_))` if there is a contextual `TypeTest[X, Y]` in scope. -The previous code is equivalent to - -```scala -def f[X, Y](x: X)(using TypeTest[X, Y]): Option[Y] = x match - case x @ Y(1) => Some(x) - case x: Y => Some(x) - case _ => None -``` - -We could create a type test at call site where the type test can be performed with runtime class tests directly as follows - -```scala -val tt: TypeTest[Any, String] = - new TypeTest[Any, String]: - def unapply(s: Any): Option[s.type & String] = s match - case s: String => Some(s) - case _ => None - -f[AnyRef, String]("acb")(using tt) -``` - -The compiler will synthesize a new instance of a type test if none is found in scope as: - -```scala -new TypeTest[A, B]: - def unapply(s: A): Option[s.type & B] = s match - case s: B => Some(s) - case _ => None -``` - -If the type tests cannot be done there will be an unchecked warning that will be raised on the `case s: B =>` test. - -The most common `TypeTest` instances are the ones that take any parameters (i.e. `TypeTest[Any, T]`). -To make it possible to use such instances directly in context bounds we provide the alias - -```scala -package scala.reflect - -type Typeable[T] = TypeTest[Any, T] -``` - -This alias can be used as - -```scala -def f[T: Typeable]: Boolean = - "abc" match - case x: T => true - case _ => false - -f[String] // true -f[Int] // false -``` - -## TypeTest and ClassTag - -`TypeTest` is a replacement for functionality provided previously by `ClassTag.unapply`. -Using `ClassTag` instances was unsound since classtags can check only the class component of a type. -`TypeTest` fixes that unsoundness. -`ClassTag` type tests are still supported but a warning will be emitted after 3.0. - - -## Example - -Given the following abstract definition of Peano numbers that provides two given instances of types `TypeTest[Nat, Zero]` and `TypeTest[Nat, Succ]` - -```scala -import scala.reflect.* - -trait Peano: - type Nat - type Zero <: Nat - type Succ <: Nat - - def safeDiv(m: Nat, n: Succ): (Nat, Nat) - - val Zero: Zero - - val Succ: SuccExtractor - trait SuccExtractor: - def apply(nat: Nat): Succ - def unapply(succ: Succ): Some[Nat] - - given typeTestOfZero: TypeTest[Nat, Zero] - given typeTestOfSucc: TypeTest[Nat, Succ] -``` - -together with an implementation of Peano numbers based on type `Int` - -```scala -object PeanoInt extends Peano: - type Nat = Int - type Zero = Int - type Succ = Int - - def safeDiv(m: Nat, n: Succ): (Nat, Nat) = (m / n, m % n) - - val Zero: Zero = 0 - - val Succ: SuccExtractor = new: - def apply(nat: Nat): Succ = nat + 1 - def unapply(succ: Succ) = Some(succ - 1) - - def typeTestOfZero: TypeTest[Nat, Zero] = new: - def unapply(x: Nat): Option[x.type & Zero] = - if x == 0 then Some(x) else None - - def typeTestOfSucc: TypeTest[Nat, Succ] = new: - def unapply(x: Nat): Option[x.type & Succ] = - if x > 0 then Some(x) else None -``` - -it is possible to write the following program - -```scala -@main def test = - import PeanoInt.* - - def divOpt(m: Nat, n: Nat): Option[(Nat, Nat)] = - n match - case Zero => None - case s @ Succ(_) => Some(safeDiv(m, s)) - - val two = Succ(Succ(Zero)) - val five = Succ(Succ(Succ(two))) - - println(divOpt(five, two)) // prints "Some((2,1))" - println(divOpt(two, five)) // prints "Some((0,2))" - println(divOpt(two, Zero)) // prints "None" -``` - -Note that without the `TypeTest[Nat, Succ]` the pattern `Succ.unapply(nat: Succ)` would be unchecked. diff --git a/_scala3-reference/overview.md b/_scala3-reference/overview.md deleted file mode 100644 index 20995b4567..0000000000 --- a/_scala3-reference/overview.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Overview -type: chapter -num: 1 -next-page: /scala3/reference/new-types ---- - -Scala 3 implements many language changes and improvements over Scala 2. -In this reference, we discuss design decisions and present important differences compared to Scala 2. - -## Goals - -The language redesign was guided by three main goals: - -- Strengthen Scala's foundations. - Make the full programming language compatible with the foundational work on the - [DOT calculus](https://infoscience.epfl.ch/record/227176/files/soundness_oopsla16.pdf) - and apply the lessons learned from that work. -- Make Scala easier and safer to use. - Tame powerful constructs such as implicits to provide a gentler learning curve. Remove warts and puzzlers. -- Further improve the consistency and expressiveness of Scala's language constructs. - -Corresponding to these goals, the language changes fall into seven categories: -(1) Core constructs to strengthen foundations, (2) simplifications and (3) [restrictions](#restrictions), to make the language easier and safer to use, (4) [dropped constructs](#dropped-constructs) to make the language smaller and more regular, (5) [changed constructs](#changes) to remove warts, and increase consistency and usability, (6) [new constructs](#new-constructs) to fill gaps and increase expressiveness, (7) a new, principled approach to metaprogramming that replaces [Scala 2 experimental macros](https://docs.scala-lang.org/overviews/macros/overview.html). - -## Essential Foundations - -These new constructs directly model core features of DOT, higher-kinded types, and the [SI calculus for implicit resolution](https://infoscience.epfl.ch/record/229878/files/simplicitly_1.pdf). - -- [Intersection types](new-types/intersection-types.html), replacing compound types, -- [Union types](new-types/union-types.html), -- [Type lambdas](new-types/type-lambdas.html), replacing encodings using structural types and type projection. -- [Context functions](contextual/context-functions.html), offering abstraction over given parameters. - -## Simplifications - -These constructs replace existing constructs with the aim of making the language safer and simpler to use, and to promote uniformity in code style. - -- [Trait parameters](other-new-features/trait-parameters.html) - replace [early initializers](dropped-features/early-initializers.html) with a more generally useful construct. -- [Given instances](contextual/givens.html) - replace implicit objects and defs, focussing on intent over mechanism. -- [Using clauses](contextual/using-clauses.html) - replace implicit parameters, avoiding their ambiguities. -- [Extension methods](contextual/extension-methods.html) - replace implicit classes with a clearer and simpler mechanism. -- [Opaque type aliases](other-new-features/opaques.html) - replace most uses of value classes while guaranteeing absence of boxing. -- [Top-level definitions](dropped-features/package-objects.html) - replace package objects, dropping syntactic boilerplate. -- [Export clauses](other-new-features/export.html) - provide a simple and general way to express aggregation, which can replace - the previous facade pattern of package objects inheriting from classes. -- [Vararg splices](changed-features/vararg-splices.html) - now use the form `xs*` in function arguments and patterns instead of `xs: _*` and `xs @ _*`, -- [Universal apply methods](other-new-features/creator-applications.html) - allow using simple function call syntax instead of `new` expressions. `new` expressions stay around - as a fallback for the cases where creator applications cannot be used. - -With the exception of [early initializers](dropped-features/early-initializers.html) and old-style vararg patterns, all superseded constructs continue to be available in Scala 3.0. The plan is to deprecate and phase them out later. - -Value classes (superseded by opaque type aliases) are a special case. There are currently no deprecation plans for value classes, since we might bring them back in a more general form if they are supported natively by the JVM as is planned by [project Valhalla](https://openjdk.java.net/projects/valhalla/). - -## Restrictions - -These constructs are restricted to make the language safer. - -- [Implicit Conversions](contextual/conversions.html): - there is only one way to define implicit conversions instead of many, and potentially surprising implicit conversions require a language import. -- [Given Imports](contextual/given-imports.html): - implicits now require a special form of import, to make the import clearly visible. -- [Type Projection](dropped-features/type-projection.html): - only classes can be used as prefix `C` of a type projection `C#A`. Type projection on abstract types is no longer supported since it is unsound. -- [Multiversal Equality](contextual/multiversal-equality.html): - implement an "opt-in" scheme to rule out nonsensical comparisons with `==` and `!=`. -- [infix](changed-features/operators.html): - make method application syntax uniform across code bases. - -Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-source 3.0-migration`. - -## Dropped Constructs - -These constructs are proposed to be dropped without a new construct replacing them. The motivation for dropping these constructs is to simplify the language and its implementation. - -- [DelayedInit](dropped-features/delayed-init.html), -- [Existential types](dropped-features/existential-types.html), -- [Procedure syntax](dropped-features/procedure-syntax.html), -- [Class shadowing](dropped-features/class-shadowing.html), -- [XML literals](dropped-features/xml.html), -- [Symbol literals](dropped-features/symlits.html), -- [Auto application](dropped-features/auto-apply.html), -- [Weak conformance](dropped-features/weak-conformance.html), -- Compound types (replaced by [Intersection types](new-types/intersection-types.html)), -- [Auto tupling](https://github.com/lampepfl/dotty/pull/4311) (implemented, but not merged). - -The date when these constructs are dropped varies. The current status is: - -- Not implemented at all: - - DelayedInit, existential types, weak conformance. -- Supported under `-source 3.0-migration`: - - procedure syntax, class shadowing, symbol literals, auto application, auto tupling in a restricted form. -- Supported in 3.0, to be deprecated and phased out later: - - [XML literals](dropped-features/xml.html), compound types. - -## Changes - -These constructs have undergone changes to make them more regular and useful. - -- [Structural Types](changed-features/structural-types.html): - They now allow pluggable implementations, which greatly increases their usefulness. Some usage patterns are restricted compared to the status quo. -- [Name-based pattern matching](changed-features/pattern-matching.html): - The existing undocumented Scala 2 implementation has been codified in a slightly simplified form. -- [Automatic Eta expansion](changed-features/eta-expansion.html): - Eta expansion is now performed universally also in the absence of an expected type. The postfix `_` operator is thus made redundant. It will be deprecated and dropped after Scala 3.0. -- [Implicit Resolution](changed-features/implicit-resolution.html): - The implicit resolution rules have been cleaned up to make them more useful and less surprising. Implicit scope is restricted to no longer include package prefixes. - -Most aspects of old-style implicit resolution are still available under `-source 3.0-migration`. The other changes in this list are applied unconditionally. - -## New Constructs - -These are additions to the language that make it more powerful or pleasant to use. - -- [Enums](enums/enums.html) provide concise syntax for enumerations and [algebraic data types](enums/adts.html). -- [Parameter untupling](other-new-features/parameter-untupling.html) avoids having to use `case` for tupled parameter destructuring. -- [Dependent function types](new-types/dependent-function-types.html) generalize dependent methods to dependent function values and types. -- [Polymorphic function types](new-types/polymorphic-function-types.html) generalize polymorphic methods to polymorphic function values and types. - _Current status_: There is a proposal and a merged prototype implementation, but the implementation has not been finalized (it is notably missing type inference support). -- [Kind polymorphism](other-new-features/kind-polymorphism.html) allows the definition of operators working equally on types and type constructors. -- [`@targetName` annotations](other-new-features/targetName.html) make it easier to interoperate with code written in other languages and give more flexibility for avoiding name clashes. - -## Metaprogramming - -The following constructs together aim to put metaprogramming in Scala on a new basis. So far, metaprogramming was achieved by a combination of macros and libraries such as [Shapeless](https://github.com/milessabin/shapeless) that were in turn based on some key macros. Current Scala 2 macro mechanisms are a thin veneer on top the current Scala 2 compiler, which makes them fragile and in many cases impossible to port to Scala 3. - -It's worth noting that macros were never included in the [Scala 2 language specification](https://scala-lang.org/files/archive/spec/2.13/) and were so far made available only under an `-experimental` flag. This has not prevented their widespread usage. - -To enable porting most uses of macros, we are experimenting with the advanced language constructs listed below. These designs are more provisional than the rest of the proposed language constructs for Scala 3.0. There might still be some changes until the final release. Stabilizing the feature set needed for metaprogramming is our first priority. - -- [Match Types](new-types/match-types.html) - allow computation on types. -- [Inline](metaprogramming/inline.html) - provides by itself a straightforward implementation of some simple macros and is at the same time an essential building block for the implementation of complex macros. -- [Quotes and Splices](metaprogramming/macros.html) - provide a principled way to express macros and staging with a unified set of abstractions. -- [Type class derivation](contextual/derivation.html) - provides an in-language implementation of the `Gen` macro in Shapeless and other foundational libraries. The new implementation is more robust, efficient and easier to use than the macro. -- [By-name context parameters](contextual/by-name-context-parameters.html) - provide a more robust in-language implementation of the `Lazy` macro in [Shapeless](https://github.com/milessabin/shapeless). - -## See Also - -[A classification of proposed language features](./features-classification.html) is -an expanded version of this page that adds the status (i.e. relative importance to be a part of Scala 3, and relative urgency when to decide this) and expected migration cost -of each language construct. diff --git a/_scala3-reference/soft-modifier.md b/_scala3-reference/soft-modifier.md deleted file mode 100644 index 23ac18640c..0000000000 --- a/_scala3-reference/soft-modifier.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: singlepage-overview -scala3: true -title: Soft Keywords ---- - -A soft modifier is one of the identifiers `opaque`, `inline`, `open`, `transparent`, and `infix`. - -A soft keyword is a soft modifier, or one of `as`, `derives`, `end`, `extension`, `using`, `|`, `+`, `-`, `*` - -A soft modifier is treated as potential modifier of a definition if it is followed by a hard modifier or a keyword combination starting a definition (`def`, `val`, `var`, `type`, `given`, `class`, `trait`, `object`, `enum`, `case class`, `case object`). Between the two words there may be a sequence of newline tokens and soft modifiers. - -Otherwise, soft keywords are treated specially in the following situations: - - - `inline`, if it is followed by any token that can start an expression. - - `derives`, if it appears after an extension clause or after - the name and possibly parameters of a class, trait, object, or enum definition. - - `end`, if it appears at the start of a line following a statement (i.e. definition or toplevel expression) - - `extension`, if it appears at the start of a statement and is followed by `(` or `[`. - - `using`, if it appears at the start of a parameter or argument list. - - `as`, in a renaming import clause - - `|`, if it separates two patterns in an alternative. - - `+`, `-`, if they appear in front of a type parameter. - - `*`, in a wildcard import, or it follows the type of a parameter, or if it appears in - a vararg splice `x*`. - -Everywhere else a soft keyword is treated as a normal identifier. diff --git a/_scala3-reference/syntax.md b/_scala3-reference/syntax.md deleted file mode 100644 index 74d5537674..0000000000 --- a/_scala3-reference/syntax.md +++ /dev/null @@ -1,447 +0,0 @@ ---- -title: "Scala 3 Syntax Summary" -type: chapter -num: 87 -previous-page: /scala3/reference/dropped-features/wildcard-init ---- - -The following description of Scala tokens uses literal characters `‘c’` when -referring to the ASCII fragment `\u0000` – `\u007F`. - -_Unicode escapes_ are used to represent the [Unicode character](https://www.w3.org/International/articles/definitions-characters/) with the given -hexadecimal code: - -``` -UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit -hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’ -``` - -Informal descriptions are typeset as `“some comment”`. - -### Lexical Syntax - -The lexical syntax of Scala is given by the following grammar in EBNF -form. - -``` -whiteSpace ::= ‘\u0020’ | ‘\u0009’ | ‘\u000D’ | ‘\u000A’ -upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and Unicode category Lu” -lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” -letter ::= upper | lower “… and Unicode categories Lo, Lt, Nl” -digit ::= ‘0’ | … | ‘9’ -paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ -delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ -opchar ::= “printableChar not matched by (whiteSpace | upper | - lower | letter | digit | paren | delim | opchar | - Unicode_Sm | Unicode_So)” -printableChar ::= “all characters in [\u0020, \u007F] inclusive” -charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) - -op ::= opchar {opchar} -varid ::= lower idrest -alphaid ::= upper idrest - | varid -plainid ::= alphaid - | op -id ::= plainid - | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ -idrest ::= {letter | digit} [‘_’ op] -quoteId ::= ‘'’ alphaid - -integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] -decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] -hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit] -nonZeroDigit ::= ‘1’ | … | ‘9’ - -floatingPointLiteral - ::= [decimalNumeral] ‘.’ digit [{digit | ‘_’} digit] [exponentPart] [floatType] - | decimalNumeral exponentPart [floatType] - | decimalNumeral floatType -exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit] -floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’ - -booleanLiteral ::= ‘true’ | ‘false’ - -characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’ - -stringLiteral ::= ‘"’ {stringElement} ‘"’ - | ‘"""’ multiLineChars ‘"""’ -stringElement ::= printableChar \ (‘"’ | ‘\’) - | UnicodeEscape - | charEscapeSeq -multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’} -processedStringLiteral - ::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’ - | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’ -processedStringPart - ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape -escape ::= ‘$$’ - | ‘$’ letter { letter | digit } - | ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’ -stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)} - -symbolLiteral ::= ‘'’ plainid // until 2.13 - -comment ::= ‘/*’ “any sequence of characters; nested comments are allowed” ‘*/’ - | ‘//’ “any sequence of characters up to end of line” - -nl ::= “new line character” -semi ::= ‘;’ | nl {nl} -``` - -## Optional Braces - -The lexical analyzer also inserts `indent` and `outdent` tokens that represent regions of indented code [at certain points](./other-new-features/indentation.html). - -In the context-free productions below we use the notation `<<< ts >>>` -to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent`. Analogously, the -notation `:<<< ts >>>` indicates a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent` that follows -a `:` at the end of a line. - -``` - <<< ts >>> ::= ‘{’ ts ‘}’ - | indent ts outdent -:<<< ts >>> ::= [nl] ‘{’ ts ‘}’ - | `:` indent ts outdent -``` - -## Keywords - -### Regular keywords - -``` -abstract case catch class def do else -enum export extends false final finally for -given if implicit import lazy match new -null object override package private protected return -sealed super then throw trait true try -type val var while with yield -: = <- => <: >: # -@ =>> ?=> -``` - -### Soft keywords - -``` -as derives end extension infix inline opaque open transparent using | * + - -``` - -See the [separate section on soft keywords](./soft-modifier.html) for additional -details on where a soft keyword is recognized. - -## Context-free Syntax - -The context-free syntax of Scala is given by the following EBNF -grammar: - -### Literals and Paths -``` -SimpleLiteral ::= [‘-’] integerLiteral - | [‘-’] floatingPointLiteral - | booleanLiteral - | characterLiteral - | stringLiteral -Literal ::= SimpleLiteral - | processedStringLiteral - | symbolLiteral - | ‘null’ - -QualId ::= id {‘.’ id} -ids ::= id {‘,’ id} - -SimpleRef ::= id - | [id ‘.’] ‘this’ - | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id - -ClassQualifier ::= ‘[’ id ‘]’ -``` - -### Types -``` -Type ::= FunType - | HkTypeParamClause ‘=>>’ Type - | FunParamClause ‘=>>’ Type - | MatchType - | InfixType -FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - | HKTypeParamClause '=>' Type -FunTypeArgs ::= InfixType - | ‘(’ [ FunArgTypes ] ‘)’ - | FunParamClause -FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ -TypedFunParam ::= id ‘:’ Type -MatchType ::= InfixType `match` <<< TypeCaseClauses >>> -InfixType ::= RefinedType {id [nl] RefinedType} -RefinedType ::= AnnotType {[nl] Refinement} -AnnotType ::= SimpleType {Annotation} - -SimpleType ::= SimpleLiteral - | ‘?’ TypeBounds - | id - | Singleton ‘.’ id - | Singleton ‘.’ ‘type’ - | ‘(’ Types ‘)’ - | Refinement - | ‘$’ ‘{’ Block ‘}’ - | SimpleType1 TypeArgs - | SimpleType1 ‘#’ id -Singleton ::= SimpleRef - | SimpleLiteral - | Singleton ‘.’ id - -FunArgType ::= Type - | ‘=>’ Type -FunArgTypes ::= FunArgType { ‘,’ FunArgType } -ParamType ::= [‘=>’] ParamValueType -ParamValueType ::= Type [‘*’] -TypeArgs ::= ‘[’ Types ‘]’ -Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] -TypeParamBounds ::= TypeBounds {‘:’ Type} -Types ::= Type {‘,’ Type} -``` - -### Expressions -``` -Expr ::= FunParams (‘=>’ | ‘?=>’) Expr - | HkTypeParamClause ‘=>’ Expr - | Expr1 -BlockResult ::= FunParams (‘=>’ | ‘?=>’) Block - | HkTypeParamClause ‘=>’ Block - | Expr1 -FunParams ::= Bindings - | id - | ‘_’ -Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] - | [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] - | ‘while’ ‘(’ Expr ‘)’ {nl} Expr - | ‘while’ Expr ‘do’ Expr - | ‘try’ Expr Catches [‘finally’ Expr] - | ‘try’ Expr [‘finally’ Expr] - | ‘throw’ Expr - | ‘return’ [Expr] - | ForExpr - | [SimpleExpr ‘.’] id ‘=’ Expr - | SimpleExpr1 ArgumentExprs ‘=’ Expr - | PostfixExpr [Ascription] - | ‘inline’ InfixExpr MatchClause -Ascription ::= ‘:’ InfixType - | ‘:’ Annotation {Annotation} -Catches ::= ‘catch’ (Expr | ExprCaseClause) -PostfixExpr ::= InfixExpr [id] -- only if language.postfixOperators is enabled -InfixExpr ::= PrefixExpr - | InfixExpr id [nl] InfixExpr - | InfixExpr MatchClause -MatchClause ::= ‘match’ <<< CaseClauses >>> -PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr -SimpleExpr ::= SimpleRef - | Literal - | ‘_’ - | BlockExpr - | ‘$’ ‘{’ Block ‘}’ - | Quoted - | quoteId -- only inside splices - | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] - | ‘new’ TemplateBody - | ‘(’ ExprsInParens ‘)’ - | SimpleExpr ‘.’ id - | SimpleExpr ‘.’ MatchClause - | SimpleExpr TypeArgs - | SimpleExpr ArgumentExprs -Quoted ::= ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ -ExprsInParens ::= ExprInParens {‘,’ ExprInParens} -ExprInParens ::= PostfixExpr ‘:’ Type - | Expr -ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ -ArgumentExprs ::= ParArgumentExprs - | BlockExpr -BlockExpr ::= <<< (CaseClauses | Block) >>> -Block ::= {BlockStat semi} [BlockResult] -BlockStat ::= Import - | {Annotation {nl}} {LocalModifier} Def - | Extension - | Expr1 - | EndMarker - -ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr - | ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr - | ‘for’ Enumerators0 (‘do‘ | ‘yield’) Expr -Enumerators0 ::= {nl} Enumerators [semi] -Enumerators ::= Generator {semi Enumerator | Guard} -Enumerator ::= Generator - | Guard - | Pattern1 ‘=’ Expr -Generator ::= [‘case’] Pattern1 ‘<-’ Expr -Guard ::= ‘if’ PostfixExpr - -CaseClauses ::= CaseClause { CaseClause } -CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block -ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr -TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } -TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] - -Pattern ::= Pattern1 { ‘|’ Pattern1 } -Pattern1 ::= Pattern2 [‘:’ RefinedType] -Pattern2 ::= [id ‘@’] InfixPattern [‘*’] -InfixPattern ::= SimplePattern { id [nl] SimplePattern } -SimplePattern ::= PatVar - | Literal - | ‘(’ [Patterns] ‘)’ - | Quoted - | SimplePattern1 [TypeArgs] [ArgumentPatterns] - | ‘given’ RefinedType -SimplePattern1 ::= SimpleRef - | SimplePattern1 ‘.’ id -PatVar ::= varid - | ‘_’ -Patterns ::= Pattern {‘,’ Pattern} -ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ - | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ -``` - -### Type and Value Parameters -``` -ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ -ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] id [HkTypeParamClause] TypeParamBounds - -DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds - -TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ -TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds - -HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ -HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypeParamClause] | ‘_’) TypeBounds - -ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] -ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ - | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ -ClsParams ::= ClsParam {‘,’ ClsParam} -ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param -Param ::= id ‘:’ ParamType [‘=’ Expr] - -DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ DefParams ‘)’ | UsingParamClause -UsingParamClause ::= [nl] ‘(’ ‘using’ (DefParams | FunArgTypes) ‘)’ -DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘inline’] Param -``` - -### Bindings and Imports -``` -Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’ -Binding ::= (id | ‘_’) [‘:’ Type] - -Modifier ::= LocalModifier - | AccessModifier - | ‘override’ - | ‘opaque’ -LocalModifier ::= ‘abstract’ - | ‘final’ - | ‘sealed’ - | ‘open’ - | ‘implicit’ - | ‘lazy’ - | ‘inline’ -AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] -AccessQualifier ::= ‘[’ id ‘]’ - -Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} - -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec - | SimpleRef ‘as’ id -ImportSpec ::= NamedSelector - | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ -NamedSelector ::= id [‘as’ (id | ‘_’)] -WildCardSelector ::= ‘*' | ‘given’ [InfixType] -ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} - -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL -EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ -``` - -### Declarations and Definitions -``` -RefineDcl ::= ‘val’ ValDcl - | ‘def’ DefDcl - | ‘type’ {nl} TypeDcl -Dcl ::= RefineDcl - | ‘var’ VarDcl -ValDcl ::= ids ‘:’ Type -VarDcl ::= ids ‘:’ Type -DefDcl ::= DefSig ‘:’ Type -DefSig ::= id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type] - -Def ::= ‘val’ PatDef - | ‘var’ PatDef - | ‘def’ DefDef - | ‘type’ {nl} TypeDcl - | TmplDef -PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type] ‘=’ Expr -DefDef ::= DefSig [‘:’ Type] ‘=’ Expr - | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr - -TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef - | [‘case’] ‘object’ ObjectDef - | ‘enum’ EnumDef - | ‘given’ GivenDef -ClassDef ::= id ClassConstr [Template] -ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses -ConstrMods ::= {Annotation} [AccessModifier] -ObjectDef ::= id [Template] -EnumDef ::= id ClassConstr InheritClauses EnumBody -GivenDef ::= [GivenSig] (AnnotType [‘=’ Expr] | StructuralInstance) -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefParamClause`, `UsingParamClause` must be present -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody] -Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef -Template ::= InheritClauses [TemplateBody] -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] -ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp}) -ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} -ConstrExpr ::= SelfInvocation - | <<< SelfInvocation {semi BlockStat} >>> -SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} - -TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> -TemplateStat ::= Import - | Export - | {Annotation [nl]} {Modifier} Def - | {Annotation [nl]} {Modifier} Dcl - | Extension - | Expr1 - | EndMarker - | -SelfType ::= id [‘:’ InfixType] ‘=>’ - | ‘this’ ‘:’ InfixType ‘=>’ - -EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> -EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase -EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) - -TopStats ::= TopStat {semi TopStat} -TopStat ::= Import - | Export - | {Annotation [nl]} {Modifier} Def - | Extension - | Packaging - | PackageObject - | EndMarker - | -Packaging ::= ‘package’ QualId :<<< TopStats >>> -PackageObject ::= ‘package’ ‘object’ ObjectDef - -CompilationUnit ::= {‘package’ QualId semi} TopStats -``` diff --git a/_sips/all.md b/_sips/all.md index 8133c137af..befff8ec61 100644 --- a/_sips/all.md +++ b/_sips/all.md @@ -7,65 +7,92 @@ redirect_from: - "/sips/pending/index.html" --- +{% assign sips = site.sips | sort: title %} +{% assign sipData = site.data.sip-data %} + +## Pre-SIP Discussions + +You can find so-called “pre-SIP discussions” in the Scala Contributors forum, under +the category [Scala Improvement Process](https://contributors.scala-lang.org/c/sip/13). +The goal of pre-SIP discussions is to gather initial community feedback and support. ## Pending SIPs -
+Proposals that are at the design or implementation stage, and that are actively +discussed by the committee and the proposals’ authors. Click on a proposal to +read its content, or the corresponding discussions on GitHub if its design has +not been accepted yet. + +
    - {% 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 == "under-revision" %} -
  • - {{ sip.title }} -
    {{ sip.date | date: '%B %Y' }}
    -
    {{ site.data.sip-data[sip.vote-status].text }}
    + {% if sip.stage == "design" or sip.stage == "implementation" %} +
  • + + + {{ sip.title }} + + +
    Stage: {{ sipData[sip.stage].text }}
    +
    Status: {{ sipData[sip.status].text }}
    + {% if sip.recommendation %} +
    Recommendation: {{ sipData[sip.recommendation].text }}
    + {% endif %}
  • {% 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 %} -
-
-
-

Dormant

-
    - {% for sip in sips %} - {% if sip.vote-status == "dormant" %} -
  • - {{ 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 %} -
-
+## Completed SIPs + +Proposals that have been implemented in the compiler and that are available as a stable +feature of the compiler (shipped), or that will be available in the next minor release +of the compiler (accepted). Click on a proposal to read its content. + +
+
    + {% for sip in sips %} + {% if sip.stage == "completed" %} +
  • + {{ sip.title }} +
    {{ sipData[sip.status].text }}
    +
  • + {% endif %} + {% endfor %} +
+
+ +## Rejected SIPs + +Proposals that have been rejected by the committee. Click on a proposal to read the +corresponding discussions on GitHub. + +
+
    + {% for sip in sips %} + {% if sip.status == "rejected" %} +
  • + {{ sip.title }} +
  • + {% endif %} + {% endfor %} +
+
+ +## Withdrawn SIPs + +Proposals that have been withdrawn by their authors. Click on a proposal to read the +corresponding discussions on GitHub. + +
+
    + {% for sip in sips %} + {% if sip.status == "withdrawn" %} +
  • + {{ sip.title }} +
  • + {% endif %} + {% endfor %} +
diff --git a/_sips/index.md b/_sips/index.md index 99c3a5264a..87dc975d0c 100644 --- a/_sips/index.md +++ b/_sips/index.md @@ -3,24 +3,17 @@ layout: sips title: Scala Improvement Process --- - -Two separate processes govern changes to Scala: - -1. The **Scala Improvement Process** (SIP) covers changes to the Scala +The **Scala Improvement Process** covers changes to the Scala language, the Scala compiler, and the core of the Scala standard library. -2. The **Scala Platform Process** (SPP) aims to establish a stable -collection of libraries suitable for widespread use, with a low barrier -to entry for newcomers. - -## Scala Improvement Process (SIP) +## Scala Improvement Process -The **SIP** (_Scala Improvement Process_) is a process for submitting +The _Scala Improvement Process_ is a process for submitting changes to the Scala language. This process aims to evolve Scala openly and collaboratively. -The SIP process covers the Scala language and compiler and the core of +The process covers the Scala language and compiler and the core of the Scala standard library. (The core is anything that is unlikely to be spun off into a separate module.) @@ -28,14 +21,20 @@ A proposed change requires a design document, called a Scala Improvement Proposal (SIP). The SIP committee meets monthly to discuss, and eventually vote upon, proposals. -A SIP is subject to a [review process](./sip-submission.html). +A SIP is subject to a [review process]({% link _sips/process-specification.md %}). Proposals normally include proposed changes to the -[Scala language specification](https://www.scala-lang.org/files/archive/spec/2.12/). +[Scala language specification](https://www.scala-lang.org/files/archive/spec/2.13/). Before reaching the committee, a proposal normally receives community discussion and review on the [Scala Contributors](https://contributors.scala-lang.org/) forum. -Please read [Submitting a SIP](./sip-submission.html) and our -[SIP tutorial](./sip-tutorial.html) for more information. +Please read the [SIP tutorial]({% link _sips/sip-tutorial.md %}) or +[the process specification]({% link _sips/process-specification.md %}) for more +information. + +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. The linked documents capture our guidelines, +commitments and expectations regarding this process. > Historical note: The SIP replaces the older SID (Scala Improvement Document) process. > Completed SID documents remain available in the diff --git a/_sips/meeting-results.md b/_sips/meeting-results.md new file mode 100644 index 0000000000..4a44ee7c1b --- /dev/null +++ b/_sips/meeting-results.md @@ -0,0 +1,32 @@ +--- +layout: sips +title: SIP Meeting Results +redirect_from: /sips/minutes-list.html +--- + +This page lists the results of every SIP meeting, starting from July 2016. + +### Meetings ### + +
    + {% assign sips = site.sips | sort: 'date' | reverse %} + {% for page in sips %} + {% if page.partof == 'results' %} +
  • {{ page.date | date_to_long_string }}
  • + {% endif %} + {% endfor %} +
+ +### Meeting Minutes ### + +Before 2022, we hosted the complete meeting notes (minutes). Some +meetings were also [recorded on YouTube](https://www.youtube.com/channel/UCn_8OeZlf5S6sqCqntAvaIw/videos?view=2&sort=dd&shelf_id=1&live_view=502). + +
    + {% assign sips = site.sips | sort: 'date' | reverse %} + {% for pg in sips %} + {% if pg.partof == 'minutes' %} +
  • {{ pg.title }}
  • + {% endif %} + {% endfor %} +
diff --git a/_sips/minutes-list.md b/_sips/minutes-list.md deleted file mode 100644 index ff651a8a46..0000000000 --- a/_sips/minutes-list.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -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/2016-07-15-sip-minutes.md b/_sips/minutes/2016-07-15-sip-minutes.md index e329e46aa6..1f29d94957 100644 --- a/_sips/minutes/2016-07-15-sip-minutes.md +++ b/_sips/minutes/2016-07-15-sip-minutes.md @@ -14,8 +14,8 @@ The following agenda was distributed to attendees: | [Discussion of the new SIP process](https://docs.scala-lang.org/sips/sip-submission.html) | Jorge Vicente Cantero | | [SIP 25 - Trait parameters](https://docs.scala-lang.org/sips/trait-parameters.html) | Adriaan Moors | | [SIP 26 - Unsigned Integer Data Types](https://github.com/scala/slip/pull/30) | Martin Odersky | -| [SIP 22 - Async](https://docs.scala-lang.org/sips/async.html) | Eugene Burmako | -| [SIP 20 - Improved lazy val initialization](https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html) | Sébastien Doeraene | +| [SIP 22 - Async](https://github.com/scala/improvement-proposals/pull/21) | Eugene Burmako | +| [SIP 20 - Improved lazy val initialization](https://github.com/scala/improvement-proposals/pull/19) | Sébastien Doeraene | | [Trailing commas SIP](https://github.com/scala/docs.scala-lang/pull/533) | Eugene Burmako | Quick iteration through all the SLIPs: diff --git a/_sips/minutes/2016-08-16-sip-10th-august-minutes.md b/_sips/minutes/2016-08-16-sip-10th-august-minutes.md index 74a790f9f2..b0009109a0 100644 --- a/_sips/minutes/2016-08-16-sip-10th-august-minutes.md +++ b/_sips/minutes/2016-08-16-sip-10th-august-minutes.md @@ -11,11 +11,11 @@ The following agenda was distributed to attendees: | Topic | Reviewer | | --- | --- | -| [SIP-12: Uncluttering Scala's syntax for control structures](https://docs.scala-lang.org/sips/uncluttering-control.html) | Seth Tisue | -| [SIP-16: Self-cleaning macros](https://docs.scala-lang.org/sips/self-cleaning-macros.html) | Eugene Burmako | -| [SIP-21: Spores](https://docs.scala-lang.org/sips/spores.html) | Martin Odersky | +| [SIP-12: Uncluttering Scala's syntax for control structures](https://github.com/scala/improvement-proposals/pull/12) | Seth Tisue | +| [SIP-16: Self-cleaning macros](https://github.com/scala/improvement-proposals/pull/15) | Eugene Burmako | +| [SIP-21: Spores](https://github.com/scala/improvement-proposals/pull/20) | Martin Odersky | | [SIP-23: Literal-based singleton types](https://docs.scala-lang.org/sips/42.type.html) | Adriaan Moors | -| [SIP-24: Repeated by-name parameters](https://docs.scala-lang.org/sips/repeated-byname.html) | Andrew Marki | +| [SIP-24: Repeated by-name parameters](https://github.com/scala/improvement-proposals/pull/23) | Andrew Marki | | [SIP-27: Trailing commas](https://github.com/scala/docs.scala-lang/pull/533#issuecomment-232959066) | Eugene Burmako | Jorge Vicente Cantero was the Process Lead and acting secretary of the meeting. @@ -164,7 +164,7 @@ proposal or study it further. **Conclusion**: The Committee asks Dale to explicitly summarize the potential conflicts with tuple syntax, review the initial [HList proposal in -Dotty](https://github.com/lampepfl/dotty/issues/964) to figure out potential +Dotty](https://github.com/scala/scala3/issues/964) to figure out potential conflicts with his proposal. Eugene also proposes Dale to consider whether the Committee can salvage non-controversial parts of this proposal and reduce this SIP just to them, as well as discussing the utility of having two ways of doing diff --git a/_sips/minutes/2016-09-20-sip-20th-september-minutes.md b/_sips/minutes/2016-09-20-sip-20th-september-minutes.md index ca1d7a43ed..2d65bd1856 100644 --- a/_sips/minutes/2016-09-20-sip-20th-september-minutes.md +++ b/_sips/minutes/2016-09-20-sip-20th-september-minutes.md @@ -12,7 +12,7 @@ The following agenda was distributed to attendees: | Topic | Reviewer | | --- | --- | | [SIP-NN: Scala Meta SIP](https://github.com/scala/docs.scala-lang/pull/567) | Iulian Dragos and Josh Suereth | -| [SIP-21: Spores](https://docs.scala-lang.org/sips/spores.html) | Martin Odersky | +| [SIP-21: Spores](https://github.com/scala/improvement-proposals/pull/20) | Martin Odersky | | [SIP-26: Unsigned Integer Data Types](https://github.com/scala/slip/pull/30) | Martin Odersky | | [SIP-27: Trailing commas](https://github.com/scala/docs.scala-lang/pull/533#issuecomment-232959066) | Eugene Burmako | diff --git a/_sips/minutes/2016-10-25-sip-minutes.md b/_sips/minutes/2016-10-25-sip-minutes.md index 8acd0d22c6..7c2d98abe2 100644 --- a/_sips/minutes/2016-10-25-sip-minutes.md +++ b/_sips/minutes/2016-10-25-sip-minutes.md @@ -12,7 +12,7 @@ The following agenda was distributed to attendees: | Topic | Reviewer | | --- | --- | | Discussion of the voting system | N/A | -| [SIP-20: Improved Lazy Val Initialization](https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html) | Sébastien Doeraene | +| [SIP-20: Improved Lazy Val Initialization](https://github.com/scala/improvement-proposals/pull/19) | Sébastien Doeraene | | [SIP-27: Trailing commas](https://github.com/scala/docs.scala-lang/pull/533#issuecomment-232959066) | Eugene Burmako | Jorge Vicente Cantero was the Process Lead and acting secretary of the meeting. @@ -145,9 +145,9 @@ Dotty, in which the championed scheme is already implemented. There are no clear winner in the case of local lazy vals, but there seems to be two clear candidates for the non-local lazy vals -([V4](https://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html) +([V4](https://github.com/scala/improvement-proposals/pull/19) and -[V6](https://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html)). +[V6](https://github.com/scala/improvement-proposals/pull/19)). Both are faster than the existing implementation in the contended case but V4 general is 4x and V6 is only 2x. However, for the uncontended case V4 general is 30% slower than the existing implementation, while V6 is on par, up to +-5%. diff --git a/_sips/minutes/2016-11-29-sip-minutes.md b/_sips/minutes/2016-11-29-sip-minutes.md index 26bd0c9987..bc1d3c3452 100644 --- a/_sips/minutes/2016-11-29-sip-minutes.md +++ b/_sips/minutes/2016-11-29-sip-minutes.md @@ -11,8 +11,8 @@ The following agenda was distributed to attendees: |Topic|Reviewers| Accepted/Rejected | | --- | --- | --- | -| [SIP-28 and SIP-29 - Inline and meta](https://docs.scala-lang.org/sips/inline-meta.html) | Josh Suereth and Iulian Dragos | Pending | -| [SIP-24 - Repeated By Name Parameters](https://docs.scala-lang.org/sips/repeated-byname.html) | Heather Miller | Pending | +| [SIP-28 and SIP-29 - Inline and meta](https://github.com/scala/improvement-proposals/pull/28) | Josh Suereth and Iulian Dragos | Pending | +| [SIP-24 - Repeated By Name Parameters](https://github.com/scala/improvement-proposals/pull/23) | Heather Miller | Pending | | [SIP-30 - Static members](https://github.com/scala/docs.scala-lang/pull/491/files) | Adriaan Moors | Pending | | [SIP-27 - Trailing commas](https://docs.scala-lang.org/sips/trailing-commas.html) |Eugene Burkamo | Accepted | @@ -42,7 +42,7 @@ Minutes were taken by Travis Lee. **Jorge** We'll talk about the SIPS for Scala Meta. Eugene will start. -### [SIP-28 and SIP-29 - Inline and meta](https://docs.scala-lang.org/sips/inline-meta.html) +### [SIP-28 and SIP-29 - Inline and meta](https://github.com/scala/improvement-proposals/pull/28) Eugene and co have been working hard for two months on inline and Scala Meta. Previously discussed new macro system with new inline and meta features. Inline provides a facility to declare methods with inline right hand side into call side (0:01:24) and meta implements compile-time function execution to do meta-programming. Martin implemented inline mechanism in Dotty. Eugene worked on macro notations. New style macros will integrate with tools. Eugene shows how it works in IntelliJ. For example, you can print the value of the parameters. Meta blocks supported by IntelliJ. So are quasi-quotes. You can also expand macros. Will greatly help debugability. @@ -52,7 +52,7 @@ The spec needs to be updated based on Martin's Dotty implementation. We need to **Conclusion** This proposal needs at least another iteration to shape up and provide concrete implementation and specification details. This proposal is therefore under revision -- Eugene, the author, will gather and address more feedback and will resubmit the proposal to analysis when it's ready. -### [SIP-24 - Repeated By Name Parameters](https://docs.scala-lang.org/sips/repeated-byname.html) +### [SIP-24 - Repeated By Name Parameters](https://github.com/scala/improvement-proposals/pull/23) Heather says the debate is about the semantics or translation rules. All arguments are evaluated each time the parameter is referenced in the method. This is implemented in Dotty. Should this be implemented in Scalac? diff --git a/_sips/minutes/2017-02-14-sip-minutes.md b/_sips/minutes/2017-02-14-sip-minutes.md index a71b61f022..1738cdc5ca 100644 --- a/_sips/minutes/2017-02-14-sip-minutes.md +++ b/_sips/minutes/2017-02-14-sip-minutes.md @@ -11,9 +11,9 @@ The following agenda was distributed to attendees: | Topic | Reviewer | | --- | --- | -| [SIP-XX - Improving binary compatibility with @stableABI](https://docs.scala-lang.org/sips/binary-compatibility.html) | Dmitry Petrashko | -| [SIP-NN - Allow referring to other arguments in default parameters](https://docs.scala-lang.org/sips/refer-other-arguments-in-args.html) | Pathikrit Bhowmick | -| [SIP-30 - @static fields and methods in Scala objects(SI-4581)](https://docs.scala-lang.org/sips/static-members.html) | Dmitry Petrashko, Sébastien Doeraene and Martin Odersky | +| [SIP-XX - Improving binary compatibility with @stableABI](https://github.com/scala/improvement-proposals/pull/30) | Dmitry Petrashko | +| [SIP-NN - Allow referring to other arguments in default parameters](https://github.com/scala/improvement-proposals/pull/29) | Pathikrit Bhowmick | +| [SIP-30 - @static fields and methods in Scala objects(SI-4581)](https://github.com/scala/improvement-proposals/pull/25) | Dmitry Petrashko, Sébastien Doeraene and Martin Odersky | | [SIP-33 - Match infix & prefix types to meet expression rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html) | Oron Port | Jorge Vicente Cantero was the Process Lead. Travis (@travissarles) was acting secretary of the meeting. diff --git a/_sips/minutes/2017-05-08-sip-minutes.md b/_sips/minutes/2017-05-08-sip-minutes.md index 5648993695..f2da50701b 100644 --- a/_sips/minutes/2017-05-08-sip-minutes.md +++ b/_sips/minutes/2017-05-08-sip-minutes.md @@ -11,7 +11,7 @@ The following agenda was distributed to attendees: |Topic|Reviewers| Accepted/Rejected | | --- | --- | --- | -| [SIP-NN - comonadic-comprehensions](https://docs.scala-lang.org/sips/comonadic-comprehensions.html) | Shimi Bandiel | Rejected | +| [SIP-NN - comonadic-comprehensions](https://github.com/scala/improvement-proposals/pull/32) | Shimi Bandiel | Rejected | | [SIP-33 - Match infix & prefix types to meet expression rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html)| Oron Port | Pending | |[Scala Library Changes](https://github.com/scala/scala-dev/issues/323)|Adriaan Moors| Scala-dev proposal | @@ -41,7 +41,7 @@ Minutes were taken by Darja Jovanovic. **Jorge** going over the agenda, points out that the second item will be skipped because they are waiting for the prototype from the author of the proposal. -### [SIP-NN - comonadic-comprehensions](https://docs.scala-lang.org/sips/comonadic-comprehensions.html) +### [SIP-NN - comonadic-comprehensions](https://github.com/scala/improvement-proposals/pull/32) [YouTube time: 1:39](https://youtu.be/6rKa4OV7GfM?t=99) Proposal aims to introduce new syntax from comprehension for monads to comonads. diff --git a/_sips/minutes/2017-09-21-sip-minutes.md b/_sips/minutes/2017-09-21-sip-minutes.md index 69cd7e28fb..8d32e2421f 100644 --- a/_sips/minutes/2017-09-21-sip-minutes.md +++ b/_sips/minutes/2017-09-21-sip-minutes.md @@ -14,7 +14,7 @@ The following agenda was distributed to attendees: | [SIP-NN: Right-Associative By-Name Operators](https://docs.scala-lang.org/sips/right-associative-by-name-operators.html) | Adriaan Moors | Pending | | [SIP-ZZ: Opaque types](https://docs.scala-lang.org/sips/opaque-types.html) | Sébastien Doeraene | Pending | | [SIP-33: Match infix and prefix types to meet expression rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html)| Josh Suereth | Pending | -|[SIP-28 and SIP-29: Inline meta](https://docs.scala-lang.org/sips/inline-meta.html)|Josh Suereth and Iulian Dragos| Pending | +|[SIP-28 and SIP-29: Inline meta](https://github.com/scala/improvement-proposals/pull/28)|Josh Suereth and Iulian Dragos| Pending | Jorge Vicente Cantero was the Process Lead and Darja Jovanovic as secretary. @@ -87,7 +87,7 @@ Syntax "new" "type" Still waiting on the implementation updates, therefore this item will be discussed in the next SIP Meeting. -### [SIP-28 and SIP-29: Inline and meta](https://docs.scala-lang.org/sips/inline-meta.html) +### [SIP-28 and SIP-29: Inline and meta](https://github.com/scala/improvement-proposals/pull/28) [YouTube time: 51'40'' until the end](https://youtu.be/yzTpVbTUj18?t=3100) **Eugene** gives a brief history of this SIP development, shares the good news and suggests how to proceed. diff --git a/_sips/minutes/2017-10-24-sip-minutes.md b/_sips/minutes/2017-10-24-sip-minutes.md index 7cbe327e61..4d65067a48 100644 --- a/_sips/minutes/2017-10-24-sip-minutes.md +++ b/_sips/minutes/2017-10-24-sip-minutes.md @@ -14,7 +14,7 @@ The following agenda was distributed to attendees: | [SIP-34: Right-Associative By-Name Operators](https://docs.scala-lang.org/sips/right-associative-by-name-operators.html) | Adriaan Moors | Accepted | | [SIP-35: Opaque types](https://docs.scala-lang.org/sips/opaque-types.html) | Sébastien Doeraene | Pending | | [SIP-33: Match infix and prefix types to meet expression rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html)| Josh Suereth | Pending | -|[SIP-28 and SIP-29: Inline meta](https://docs.scala-lang.org/sips/inline-meta.html)|Josh Suereth and Iulian Dragos| Pending | +|[SIP-28 and SIP-29: Inline meta](https://github.com/scala/improvement-proposals/pull/28)|Josh Suereth and Iulian Dragos| Pending | Jorge Vicente Cantero was the Process Lead and Darja Jovanovic as secretary. @@ -75,7 +75,7 @@ The feedback will be given to the author. -### [SIP-28 and SIP-29: Inline and meta](https://docs.scala-lang.org/sips/inline-meta.html) +### [SIP-28 and SIP-29: Inline and meta](https://github.com/scala/improvement-proposals/pull/28) [YouTube time: 10'05'' until the end](https://youtu.be/aIc-o1pcRhw?t=605) **Jorge** introduces **Olaf** as a new Team Lead of SIP-28 and SIP-29. diff --git a/_sips/minutes/2017-12-06-sip-minutes.md b/_sips/minutes/2017-12-06-sip-minutes.md index f639631b4a..29f2aaafbd 100644 --- a/_sips/minutes/2017-12-06-sip-minutes.md +++ b/_sips/minutes/2017-12-06-sip-minutes.md @@ -14,7 +14,7 @@ The following agenda was distributed to attendees: |Discussion and voting on Miles Sabin (Typelevel representative) joining the Committee | | Accepted |[SIP 23: Literal-based singleton types](https://docs.scala-lang.org/sips/42.type.html) | Adriaan Moors | Accepted |[SIP-33: Priority-based infix type precedence rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html) | Josh Suereth | Accepted | -|[SIP-NN: Adding prefix types](https://docs.scala-lang.org/sips/adding-prefix-types.html) | Josh Suereth | Pending | +|[SIP-NN: Adding prefix types](https://github.com/scala/improvement-proposals/pull/35) | Josh Suereth | Pending | |[SIP-35: Opaque types](https://docs.scala-lang.org/sips/opaque-types.html) | Sébastien Doeraene | Not discussed | |Discussion about the future of Scala 2.13 and 2.14 | | Not discussed | @@ -73,7 +73,7 @@ Brief explanation about the "The presence of an upper bound of Singleton on a fo a) [SIP-33: Priority-based infix type precedence rules](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html) -b) [SIP-NN: Adding prefix types](https://docs.scala-lang.org/sips/adding-prefix-types.html) +b) [SIP-NN: Adding prefix types](https://github.com/scala/improvement-proposals/pull/35) **Seth** asks about the implementation status in Dotty and if there are any crucial differences in Scala 2 and Dotty? **Martin** and **Sebastien** agree there are none in regards to this SIP. @@ -82,7 +82,7 @@ The members are all in favour for this change and proceed to voting. **Conclusion** : The SIP-33 is accepted by unanimity. -### [SIP-NN: Adding prefix types](https://docs.scala-lang.org/sips/adding-prefix-types.html) +### [SIP-NN: Adding prefix types](https://github.com/scala/improvement-proposals/pull/35) [YouTube time: 25'00 until the end](https://youtu.be/Mhwf15gjL9s?t=1503) **Jorge** introduces the SIP's development, based on the idea diff --git a/_sips/minutes/2018-08-30-sip-minutes.md b/_sips/minutes/2018-08-30-sip-minutes.md index 48bc21c12f..218877a1db 100644 --- a/_sips/minutes/2018-08-30-sip-minutes.md +++ b/_sips/minutes/2018-08-30-sip-minutes.md @@ -68,7 +68,7 @@ Decision / voting / postponing the discussion **Miles Sabin** summarised discussion on Contributors thread. - Focuses on the Contributors discussion rather than on the proposal itself -- Underlines a lack of motivation in the light of a separate issue from [Dotty: Weak eta-expansion](https://github.com/lampepfl/dotty/issues/2570) +- Underlines a lack of motivation in the light of a separate issue from [Dotty: Weak eta-expansion](https://github.com/scala/scala3/issues/2570) - Concludes that this proposal should be aligned with eta-extension issue Summary: @@ -77,9 +77,9 @@ References + [Scala Contributors thread](https://contributors.scala-lang.org/t/proposal-to-remove-auto-application-from-the-language/2145). -+ [Dotty issue: Weak eta-expansion](https://github.com/lampepfl/dotty/issues/2570). ++ [Dotty issue: Weak eta-expansion](https://github.com/scala/scala3/issues/2570). -+ [Martin's comment on the issue above](https://github.com/lampepfl/dotty/issues/2570#issuecomment-306202339). ++ [Martin's comment on the issue above](https://github.com/scala/scala3/issues/2570#issuecomment-306202339). Comments @@ -200,7 +200,7 @@ Related links - One in scalaz-deriving: https://gitlab.com/fommil/scalaz-deriving/tree/master/examples/xmlformat/src/main/scala/xmlformat (link behind a login wall, it seems) - JSX-style libraries for Scala: - https://github.com/OlivierBlanvillain/monadic-html - - Binding.scala, TODO app: https://scalafiddle.io/sf/dGkVqlV/9 + - Binding.scala, - Ammonite script to convert HTML to the VDOM DSL of scalajs-react (a ScalaTags flavor): https://gist.github.com/nafg/112bf83e5676ed316f17cea505ea5d93 diff --git a/_sips/minutes/2018-11-26-sip-minutes.md b/_sips/minutes/2018-11-26-sip-minutes.md index a177b78833..776257508a 100644 --- a/_sips/minutes/2018-11-26-sip-minutes.md +++ b/_sips/minutes/2018-11-26-sip-minutes.md @@ -49,7 +49,7 @@ The SIP Committee gathered for the first time face-to-face, for an extensive 3-d - Outline an action plan within a set time-frame; - Other: Unanimously voted for Guillaume Martres to join the Committee. -*Better understanding* was enabled by in depth presentations and Q&As with the EPFL Dotty team; *the Approach* was agreed upon the first day which resulted in creating “FAQs about Scala 3” (see below) and *the Action Plan* was outlined and is still under construction; several issues were opened (please see the list at the end of this document) and a project plan ["Meta-programming in Scala 3"](https://github.com/lampepfl/dotty/issues/5489) has been developed. +*Better understanding* was enabled by in depth presentations and Q&As with the EPFL Dotty team; *the Approach* was agreed upon the first day which resulted in creating “FAQs about Scala 3” (see below) and *the Action Plan* was outlined and is still under construction; several issues were opened (please see the list at the end of this document) and a project plan ["Meta-programming in Scala 3"](https://github.com/scala/scala3/issues/5489) has been developed. As the most important points and summary is reflected in “FAQs about Scala 3”, it will stand as an official “minutes” for this unique SIP meeting. @@ -105,7 +105,7 @@ Implicit Function Types (https://dotty.epfl.ch/docs/reference/instances/implicit [Type Lambdas](https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html) -[Type Checking](https://dotty.epfl.ch/docs/reference/changed-features/type-checking.html) +Type Checking [Type Inference](https://dotty.epfl.ch/docs/reference/changed-features/type-inference.html) @@ -144,20 +144,20 @@ The dependency of Scala 2 macros and reflection on internal implementation detai We recognize that important parts of the Scala ecosystem have made essential use of the Scala 2 facilities and that it is vital that as many as possible of these use cases be accommodated in Scala 3 in some form or another. This will be disruptive, but we hope to mitigate the disruption by providing facilities which make the more straightforward and important scenarios simpler while still leaving others possible. Our direction is still evolving; however we believe that replacing the current excessively general and expressive macro system with a suite of less powerful but complementary tools is the way forward. Currently we are exploring options which range from improved support for type level programming in the language itself (eg. specialized inline, match types, stable definitions, GADT improvements); intrinsifying certain features currently supported by macros (eg. by-name implicits, generic programming primitives); through to less general forms of metaprogramming (quote/splice and staging) and portable reflection via Tasty (which we [recommend](https://github.com/scalacenter/advisoryboard/pull/40)) to support in both Scala 2/3 and via compiler-independent libraries and tools. We recommend that most current uses of Scala macros and reflection can be accommodated by some combination of these tools. -For more about the project's progress, please see https://github.com/lampepfl/dotty/issues/5489 +For more about the project's progress, please see https://github.com/scala/scala3/issues/5489 ### How do we plan to address language experimentation? We acknowledge that language experimentation is necessary for improving the language. We also believe it requires a different vehicle than stable Scala releases. We don’t have a concrete solution for now, but we’re working on one. ### Other “documents” created during the meetings: -[SIP: Structural Types](https://github.com/lampepfl/dotty/issues/5372) +[SIP: Structural Types](https://github.com/scala/scala3/issues/5372) -[SIP: TASTY changes](https://github.com/lampepfl/dotty/issues/5378) +[SIP: TASTY changes](https://github.com/scala/scala3/issues/5378) -[SIP: Underscore Syntax for Type Lambdas](https://github.com/lampepfl/dotty/issues/5379) +[SIP: Underscore Syntax for Type Lambdas](https://github.com/scala/scala3/issues/5379) -[Should we bring back rewrite methods?](https://github.com/lampepfl/dotty/issues/5381) +[Should we bring back rewrite methods?](https://github.com/scala/scala3/issues/5381) [Features work progress overview](https://docs.google.com/spreadsheets/d/1GWJUo0U3JbBtrfg5vqgb6H5S6wlU5HnTxebLcHwD1zw/edit?usp=sharing) diff --git a/_sips/minutes/2019-03-13-sip-minutes.md b/_sips/minutes/2019-03-13-sip-minutes.md index 7668b77208..80854b32e3 100644 --- a/_sips/minutes/2019-03-13-sip-minutes.md +++ b/_sips/minutes/2019-03-13-sip-minutes.md @@ -404,7 +404,7 @@ import implied Martin: there's a PR, not yet merged, that refines this rule considerably compared to what's on the Dotty feature page? -It's [https://github.com/lampepfl/dotty/pull/6041](https://github.com/lampepfl/dotty/pull/6041) -- it has the new impl as well as the changes to the web page. +It's [https://github.com/scala/scala3/pull/6041](https://github.com/scala/scala3/pull/6041) -- it has the new impl as well as the changes to the web page. Martin: we now have a custom error message so if something fails but changing import to import implied would fix it, the compiler will tell you. @@ -508,7 +508,7 @@ What's the metaprogramming status? Martin: quote and splice are stable, but don't support pattern matching yet. TASTY reflection is not complete yet and is still undergoing revisions based on use cases that are being tried. -No one has done any meaningful whitebox macros yet in the new system, but the door is now open. (<-- not sure if I should include this in the notes without understanding in what context we want to allow whitebox macros?) This only got merged "last week". The relevant PR is [https://github.com/lampepfl/dotty/pull/5846](https://github.com/lampepfl/dotty/pull/5846) "The main motivation for moving staging to typer is to support whitebox macros" but it's still very early days, no one has tried to actually use this. +No one has done any meaningful whitebox macros yet in the new system, but the door is now open. (<-- not sure if I should include this in the notes without understanding in what context we want to allow whitebox macros?) This only got merged "last week". The relevant PR is [https://github.com/scala/scala3/pull/5846](https://github.com/scala/scala3/pull/5846) "The main motivation for moving staging to typer is to support whitebox macros" but it's still very early days, no one has tried to actually use this. # **Kind polymorphism** @@ -554,7 +554,7 @@ Questions about the magic automatic extends clauses, how does the compiler know, "Generally, all covariant type parameters of the enum class are minimized in a compiler-generated extends clause whereas all contravariant type parameters are maximized", says the doc -There was some discussion of the details of the rules for type parameters -- this then erupted into a full-on debate. e.g. isn't it weird that if you add an extends clause, suddenly T isn't in scope any more? see [https://github.com/lampepfl/dotty/pull/6095](https://github.com/lampepfl/dotty/pull/6095) +There was some discussion of the details of the rules for type parameters -- this then erupted into a full-on debate. e.g. isn't it weird that if you add an extends clause, suddenly T isn't in scope any more? see [https://github.com/scala/scala3/pull/6095](https://github.com/scala/scala3/pull/6095) conclusion: Martin: I'll try to update the rules to reflect the behavior of the compiler. diff --git a/_sips/minutes/2019-06-08-sip-minutes.md b/_sips/minutes/2019-06-08-sip-minutes.md index 30072129bd..247472ece1 100644 --- a/_sips/minutes/2019-06-08-sip-minutes.md +++ b/_sips/minutes/2019-06-08-sip-minutes.md @@ -28,7 +28,7 @@ apart from the other implicits changes ### Auto-tupling -Implemented but not merged: [https://github.com/lampepfl/dotty/pull/4311](https://github.com/lampepfl/dotty/pull/4311) +Implemented but not merged: [https://github.com/scala/scala3/pull/4311](https://github.com/scala/scala3/pull/4311) The problem is when using infix (e.g. an operator method) there's confusion between a 1-argument tuple2 method and a 2-argument method, of which there are a few in the standard library. @@ -96,7 +96,7 @@ Adriaan: Also let's not forget the big Scaladoc impact, can't copy because of th ### Polymorphic function types -Merged but not documented: [https://github.com/lampepfl/dotty/pull/4672](https://github.com/lampepfl/dotty/pull/4672) +Merged but not documented: [https://github.com/scala/scala3/pull/4672](https://github.com/scala/scala3/pull/4672) Presented by Guillaume. diff --git a/_sips/minutes/2019-12-18-sip-minutes.md b/_sips/minutes/2019-12-18-sip-minutes.md index fe4181354b..f3ea7d1a23 100644 --- a/_sips/minutes/2019-12-18-sip-minutes.md +++ b/_sips/minutes/2019-12-18-sip-minutes.md @@ -161,7 +161,7 @@ New thread: [SIP public review: Open classes](https://contributors.scala-lang.or ### Review the "Explicit nulls" SIP -Thread: +Thread: <{{ site.scala3ref }}/experimental/explicit-nulls.html> Sébastien championing and presenting. diff --git a/_sips/minutes/2020-03-12-minutes.md b/_sips/minutes/2020-03-12-minutes.md index c83443462d..4e40af5e32 100644 --- a/_sips/minutes/2020-03-12-minutes.md +++ b/_sips/minutes/2020-03-12-minutes.md @@ -128,7 +128,7 @@ Run through https://dotty.epfl.ch/docs/reference/metaprogramming/inline.html * Gui: inline interacts with overriding * Nic: if in a subclass (say Range) you override a method (say foreach) with `inline` it only inlines if the type is cast * Seb: I need both, inline if statically the type is Range, and use the optimised override if it's a Seq that's a Range at runtime -* (After the meeting, this led to [lampepfl/dotty#8543](https://github.com/lampepfl/dotty/pull/8543) and [lampepfl/dotty#8564](https://github.com/lampepfl/dotty/issues/8564).) +* (After the meeting, this led to [scala/scala3#8543](https://github.com/scala/scala3/pull/8543) and [scala/scala3#8564](https://github.com/scala/scala3/issues/8564).) * Lukas: `inline` is perfect for macros, but it shouldn't exist for performance. The runtime is where performance should be fixed (and it's in a better position to do it) * Lukas: inlining isn't on by default (in Scala 2) because it interacts with binary compatibility in non-obvious ways, and `inline` in Dotty does the same @@ -144,7 +144,7 @@ Run through https://dotty.epfl.ch/docs/reference/metaprogramming/reflection.html ### Review match types -Review https://github.com/dotty-staging/dotty/blob/fix-6709/docs/docs/reference/new-types/match-types.md (part of https://github.com/lampepfl/dotty/pull/8024) +Review https://github.com/dotty-staging/dotty/blob/fix-6709/docs/docs/reference/new-types/match-types.md (part of https://github.com/scala/scala3/pull/8024) ```scala type LeafElem[X] = X match { @@ -157,7 +157,7 @@ type LeafElem[X] = X match { * Olivier: The order of match type definitions is significant * Olivier: The disjointness is between `X` and `String`, not between `String` and `Array` -* Olivier: See https://github.com/lampepfl/dotty/issues/8493 as an example of disjointness requirement (can't use traits) +* Olivier: See https://github.com/scala/scala3/issues/8493 as an example of disjointness requirement (can't use traits) * Martin: Currently the use-cases can be implemented like Generic * Olivier: Put at a very high level, the intent is to remove some of the computation that happens in implicits and do it (better) in match types * Olivier: another problem is that when you type-check a type you can stackoverflow, which has bad UX, particularly given we have recursive types diff --git a/_sips/minutes/2020-03-13-sip-minutes.md b/_sips/minutes/2020-03-13-sip-minutes.md index 405755918d..7e394ea557 100644 --- a/_sips/minutes/2020-03-13-sip-minutes.md +++ b/_sips/minutes/2020-03-13-sip-minutes.md @@ -285,8 +285,8 @@ Martin: - Used widely in compiler codebase for 6 months - Does find is necessary to have vertical bars in editor for indentation - Examples at: - - https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala - - https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Nullables.scala + - https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala + - https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/typer/Nullables.scala - `then` in if expression was harder to get used to Guillaume: @@ -424,7 +424,7 @@ Sébastien: - Asks if the new rules allow to add an implicit definition of a priority in-between two other definitions while preserving binary compatability? Martin, in response: -- Shows a [demo](https://github.com/lampepfl/dotty/blob/master/tests/run/implied-priority.scala) of how to insert a new implicit in between existing definitions. +- Shows a [demo](https://github.com/scala/scala3/blob/main/tests/run/implied-priority.scala) of how to insert a new implicit in between existing definitions. - The new scheme of priorities is more simple to grow than before because inheritance is fragile Sébastien is satisfied with the demo, is fine either way to add to 3.0 or later. Someone should champion it. diff --git a/_sips/process-specification.md b/_sips/process-specification.md new file mode 100644 index 0000000000..93a15ce812 --- /dev/null +++ b/_sips/process-specification.md @@ -0,0 +1,342 @@ +--- +layout: sips +title: Process Specification +redirect_from: /sips/sip-submission.html +--- + +The Scala Improvement Process (sometimes called SIP process) is a process for +submitting changes to the Scala language. This process aims to evolve Scala +openly and collaboratively. + +The SIP process covers the Scala language (syntax, type system and semantics) +and the core of the Scala standard library. The core is anything that is +referenced from the language spec (such as primitive types or the definition +of `Seq`). The SIP process is not concerned with compiler changes that do not +affect the language (including but not limited to linting warnings, +optimizations, quality of error messages). + +A proposed change requires a design document, called a Scala Improvement +Proposal (SIP). The committee meets monthly to discuss, and eventually vote +upon, proposals. + +The committee follows the following process when evaluating SIP documents, +from an idea to the inclusion in the language. + +## Definitions + +- SIP (Scala Improvement Proposal): a particular proposal for changing the Scala + language (additions, changes, and/or removals). +- Committee: a group of experienced Scala practitioners and language designers, + who evaluate changes to the Scala programming language. It consists of a + Secretary, a Chairperson, and Members. +- Chairperson: person in charge of executing the process. They organize and + chair the meetings of the Committee, and generally make sure the process is + followed, but do not vote on proposals. The Chairperson is an appointed + employee of the Scala Center. +- Committee Member: member of the Committee with voting rights. The Chairperson + cannot be a Member at the same time. +- Secretary: person attending the regular meetings and responsible for writing + notes of the discussions. +- SIP Author: any individual or group of people who writes a SIP for submission + to the Committee. The Chairperson and Committee Members may also be SIP Authors. + Authors are responsible for building consensus within the community and + documenting dissenting opinions before the SIP is officially discussed by the + SIP Committee. Their goal is to convince the committee that their proposal is + useful and addresses pertinent problems in the language as well as interactions + with already existing features. Authors can change over the life-cycle of the + SIP. +- SIP Reviewers: a subset of Committee Members assigned by the Chairperson to + review in detail a particular SIP. The same person cannot be both a SIP Author + and a SIP Reviewer for the same SIP. +- SIP Manager: one of the SIP Reviewers who is responsible for all the + communications related to the SIP, throughout its entire life-cycle. This includes requesting a vote on the SIP from the whole Committee, presenting the SIP to the Committee at the plenary meetings, merging or closing the corresponding PR, reporting to the community on the vote outcome, and announcing when it is available for testing. + +## Stages + +From being an idea to being part of the language, a SIP goes through several +Stages that indicate the "maturity" level of the SIP. The following table +summarizes the intent of the various stages. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StagePurposeEntry criteriaPost-entry changes expected
Pre-SIPGather initial community feedback and support.N/A. Opening a "Pre-SIP" post on the Scala Contributors forum can be done by anyone at any timeN/A
DesignMake the case for the proposal. Make the design of the feature precise. Evaluate the solution among other possible solutions. Identify potential challenges.Community support was demonstrated in the Pre-SIP forum post. This is loosely defined.Major changes expected.
ImplementationProvide an Experimental implementation of the changes in the compiler. Evaluate how they hold up in practice. Get feedback from implementers and users.The SIP contains a precise specification for the changes and how they should interact with the rest of the language.
+ The Committee votes in favor of the SIP to be "Accepted for implementation".
Minor changes based on feedback from implementers and early users.
CompletedShip the feature. Once accepted, the feature will ship as stable in the next Minor release of the Scala language.A complete implementation, with tests, was merged in the mainline compiler and shipped as Experimental. Implementers do not have any concerns left wrt. the implementation of the feature and its interactions.
+ The Committee votes in favor of the SIP to be "Accepted".
No changes allowed.
+ +### The process in one graph + +![](/resources/images/sip/process-chart.png) + +### Pre-SIP Stage + +To initiate a discussion, any individual opens a "Pre-SIP" post in the Scala +Contributors forum. The post should be in the +[Scala Improvement Process](https://contributors.scala-lang.org/c/sip/13) +category, and its title should start with "Pre-SIP:". The purpose of the Pre-SIP +post is to present an idea to the Scala community: a Pre-SIP should present a +problem that needs solving (i.e., motivate the changes) and present possible +solutions with pros and cons. The community is encouraged to engage in the +discussions by voicing support, comments and concerns. + +During the Pre-SIP stage, the Committee is not required to be involved. +Committee Members and the Chairperson are expected to follow Pre-SIP +discussions, but not required to engage. + +Once at least one month has passed and the author has built some community +support for their Pre-SIP, they can Submit a SIP for discussion by the +Committee. "Community support" is loosely defined. It involves a mix of positive +comments, likes, etc. Generally, the Chairperson or a Committee member will post +a comment on the thread suggesting to submit a SIP when they see that a Pre-SIP +is ready to enter the process. + +### Entry into the process (SIP Submission) + +To submit a SIP, a SIP Author writes a pull request to the +[scala/improvement-proposals](https://github.com/scala/improvement-proposals) +repository, following the [tutorial]({% link _sips/sip-tutorial.md %}), and +pointing to the Pre-SIP discussion. If the proposal correctly follows the +template, and the Pre-SIP discussion seems to show some community support, +the Chairperson will accept the SIP for review, assign a SIP number, assign +3 reviewers to the SIP among the Committee Members, and assign one of the reviewers +to be the Manager of that SIP. Since "community support" is +loosely defined, any Committee Member can also comment on the PR to accept the +SIP for review (this is meant mostly as an escape hatch to prevent the +Chairperson from unilaterally blocking a SIP from entering the process). From +that point onwards, the SIP has entered the Design Stage. + +If the template has not been correctly followed, or if none of the Committee +Members nor the Chairperson think that the Pre-SIP has gathered enough support, +the PR may be closed after 2 weeks. + +### PR states and GitHub labels + +As soon as a SIP PR is opened, the GitHub labels `stage:pre-sip` and +`status:submitted` are applied to it. At any given moment, a SIP PR will have as +labels one of the following possibilities: + +| | | | +|------------------------|-------------------------------------|-------------------------| +| `stage:pre-sip` | `status:submitted` | | +| `stage:design` | `status:under-review` | | +| `stage:design` | `status:vote-requested` | `recommendation:accept` | +| `stage:design` | `status:vote-requested` | `recommendation:reject` | +| `stage:implementation` | `status:waiting-for-implementation` | | +| `stage:implementation` | `status:under-review ` | | +| `stage:implementation` | `status:vote-requested` | `recommendation:accept` | +| `stage:implementation` | `status:vote-requested` | `recommendation:reject` | +| `stage:completed` | `status:accepted` | | +| `stage:completed` | `status:shipped` | | +| | `status:rejected` | | +| | `status:withdrawn` | | + +### Design Stage -- Review + +Once a SIP has entered the Design Stage, the assigned reviewers will review (as +a GitHub Review on the SIP PR) the proposal within 3 weeks. The authors may then +address the concerns by pushing additional commits and ask for a new review. +This phase is iterative, like any pull request to an implementation repository. +After each request for a new review, the reviewers have 3 weeks to do another +round. + +When the reviewers are confident that the SIP is in good shape to be discussed +with the full Committee, the Manager sets its status to "Vote Requested" and decide on a +Vote Recommendation that they will bring to the Committee. A Vote Recommendation +is either "Recommend Accept" or "Recommend Reject". The proposal is then +scheduled on the agenda of the next Committee meeting (which happens once a +month). + +At any time, the SIP Author may voluntarily Withdraw their SIP, in which case it +exits the process. It is possible for someone else (or the same person) to +become the new SIP Author for that SIP, and therefore bring it back to the +process. If a SIP Author does not follow up on Reviewers' comments for 2 months, +the SIP will automatically be considered to be Withdrawn. + +### Design Stage -- Vote + +During the Committee meeting, the Managers of any scheduled SIP present the SIP +to the Committee, their recommendation, and explain why they made that +recommendation. After discussion, the Committee votes for advancing the SIP to +the Implementation Stage. There are three possible outcomes: + +- Accept for implementation: the proposal then advances to the Implementation + Stage, and therefore becomes a formal recommendation to be implemented as an + Experimental feature into the compiler. +- Reject: the proposal is rejected and the PR closed. It exits the process at + this point. The reviewers will communicate on the PR the reason(s) for the + rejection. +- Keep: the proposal remains in the Design Stage for further iterations. The + reviewers will communicate on the PR the current concerns raised by the + Committee. + +In order to be accepted for implementation and advance to the next stage, a SIP +must gather strictly more than 50% of "Advance" votes among the whole Committee. This means that an abstention is equivalent to "Do not advance" for this purpose, biasing the process in favor of the status quo. Furthermore, if more than half of the Committee members are absent at the meeting, the vote is cancelled. + +For instance, if the Committee is made of 11 members, at least 6 members have to vote "Advance" for the SIP to move to the next stage. + +If there was a strict majority in favor of "Advance", the PR for the SIP is Merged at this point by its Manager. +Otherwise, a second vote between +Reject and Keep will be used. A proposal needs more than 50% "Reject" votes to +be rejected in that case. Otherwise, it is kept. + +The SIP Manager shares the outcome of the vote with the community by posting a comment to proposal’s Pre-SIP discussion. + +### Implementation Stage + +Once in the implementation stage, the Committee is not concerned with the SIP +anymore, until new concerns are discovered or until the implementation is ready. +The SIP is now a recommendation for the compiler team or any other individual or +group of people to provide an implementation of the proposal, as a pull request +to the Scala 3 compiler repository. There is no set timeline for this phase. + +Often, proposals not only need to be implemented in the compiler, but also in +several other tools (IDEs, syntax highlighters, code formatters, etc.). As soon +as a proposal reaches the implementation stage, the Chairperson notifies the +impacted tools that they should start implementing support for it. A list of +tools of the ecosystem is maintained in [this document][tooling ecosystem]. + +An implementation will be reviewed by the compiler team, and once the +implementation is deemed good enough, it can ship as an Experimental feature in +the next release of the compiler where it's practical to do so. At that point, the SIP Manager posts a follow-up comment on the Pre-SIP discussion to invite the community to test the feature and provide feedback. + +The implementers may hit challenges that were not foreseen by the Committee. +Early users may also provide feedback based on practical experience with the +Experimental feature. This feedback can be sent back to the Committee by +implementers. This is done with a PR to the SIP repository, amending the +previously merged SIP document or raising questions for challenges. In that +case, the SIP Author and Reviewers will work together with the implementers to +address the feedback. This is again an iterative process. Reviewers may merge +changes to the proposal at their discretion during this phase. + +Once the implementation is deemed stable, including appropriate tests and +sufficient support by the tooling ecosystem, the implementers and reviewers can +schedule the SIP to the next Committee meeting for final approval. Once again, a +SIP needs to gather strictly more than 50% "Accept" votes to be Completed. If +that is not achieved, it may likewise be sent back for refinements, or be +rejected, with the same rules as in the "Design Stage -- Vote" section. + +### Completed Stage + +Once a SIP is accepted for shipping, it will be enabled by default (as +non-Experimental) in the next practical Minor release of the language. + +From this point onwards, the feature is stable and cannot be changed anymore. +Any further changes would have to come as an entirely new SIP. + +## The SIP Committee + +The current committee members are: + +- Björn Regnell ([@bjornregnell](https://github.com/bjornregnell)), Lund University +- Chris Andrews ([@chrisandrews-ms](https://github.com/chrisandrews-ms)), Morgan Stanley +- Guillaume Martres ([@smarter](https://github.com/smarter)), EPFL +- Haoyi Li ([@lihaoyi](https://github.com/lihaoyi)), Databricks +- Lukas Rytz ([@lrytz](https://github.com/lrytz)), Lightbend +- Martin Odersky ([@odersky](https://github.com/odersky)), EPFL +- Oron Port ([@soronpo](https://github.com/soronpo)), DFiant Inc +- Sébastien Doeraene ([@sjrd](https://github.com/sjrd)), Scala Center + +The current Chairperson is: + +- Dimi Racordon ([@kyouko-taiga](https://github.com/kyouko-taiga)), EPFL + +The current Secretary is: + +- Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend + +### Committee Meetings + +The plenary Committee Meetings are scheduled monthly by the Chairperson. They +have the following purposes: + +- Vote to accept, keep or reject SIPs that are in a "Vote Requested" state +- Be aware of the list of SIPs that are "Under review". If a SIP stays too long + under review, Committee Members may request for it to be put to discussion + and/or vote in a subsequent plenary meeting, even if the Reviewers do not + think it is ready. This is meant primarily as an escape hatch, preventing + Reviewers from blocking a SIP by infinitely stalling it. +- Make any exception to the process that they judge necessary to unblock a + situation. + +If a Committee Member cannot attend a meeting, they are welcome to share their feedback about the proposals listed in the agenda of the meeting with the Chairperson, who will relate it during the meeting. A Committee Member cannot give their voting power to someone else. If a Committee Member misses more than 2 meetings within a year, they lose their seat. + +### Responsibilities of the Committee Members + +- Review the proposals they are assigned to: + 1. Discuss unclear points with the authors, + 2. Help them address their issues and questions, + 3. Provide them feedback from the discussions in the meetings, and + 4. Explain the latest progress in every meeting. +- Play a role in the discussions, learn in advance about the topic if needed, + and make up their mind in the voting process. +- Establish communication channels with the community to share updates about the evolution of proposals and collect feedback. + +### Guests + +Experts in some fields of the compiler may be invited to concrete meetings as +guests when discussing related SIPs. Their input would be important to discuss +the current state of the proposal, both its design and implementation. + +### On what basis are proposals evaluated? + +The Committee ultimately decides how to evaluate proposals, and on what grounds. +The Committee does not need to justify its decisions, although it is highly +encouraged to provide reasons. + +Nevertheless, here is a non-exhaustive list of things that the Reviewers and +Committee are encouraged to take into account: + +- The proposal follows the "spirit" of Scala +- The proposal is well motivated; it solves a recurring problem +- The proposal evaluates the pros and cons of its solution; the solution put + forward is believed to be the best one +- The proposal can be implemented in a way that does not break backward binary + nor TASTy compatibility +- The proposal makes an effort not to break backward source compatibility +- The proposal does not change the meaning of existing programs +- The proposal can be implemented on all major platforms (JVM, JS and Native) + +## Exceptions and changes + +The present document tries to account for most situations that could occur in +the lifetime of SIPs. However, it does not pretend to be an ultimate solution to +all cases. At any time, the Committee can decide, by consensus, to make +exceptions to the process, or to refine the process. + +## How do I submit? + +Follow the [submission tutorial]({% link _sips/sip-tutorial.md %}). + +[tooling ecosystem]: https://github.com/scala/improvement-proposals/blob/main/tooling-ecosystem.md diff --git a/_sips/results/2022-08-26-meeting.md b/_sips/results/2022-08-26-meeting.md new file mode 100644 index 0000000000..efec513d6c --- /dev/null +++ b/_sips/results/2022-08-26-meeting.md @@ -0,0 +1,25 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 26th August 2022 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/29 + name: SIP-32 - Allow referring to other arguments in default parameters + result: rejected + - url: https://github.com/scala/improvement-proposals/pull/37 + name: SIP-39 - Uncluttering Abuse of Match + result: rejected + - url: https://docs.scala-lang.org/sips/binary-integer-literals.html + name: SIP-42 - Binary Integer Literals + result: accepted + - url: https://github.com/scala/improvement-proposals/pull/42 + name: SIP-40 - Name Based XML Literals + result: rejected + - url: https://github.com/scala/improvement-proposals/pull/41 + name: SIP-45 - Curried varargs + result: rejected + - url: https://docs.scala-lang.org/sips/fewer-braces.html + name: SIP-44 - Fewer braces + result: accepted +--- + diff --git a/_sips/results/2022-09-16-meeting.md b/_sips/results/2022-09-16-meeting.md new file mode 100644 index 0000000000..e18fd2a2da --- /dev/null +++ b/_sips/results/2022-09-16-meeting.md @@ -0,0 +1,13 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 16th September 2022 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/47 + name: SIP-47 - Clause Interleaving + result: under-review + - url: https://github.com/scala/improvement-proposals/pull/46 + name: SIP-46 - Use Scala CLI to implement the 'scala' command + result: under-review +--- + diff --git a/_sips/results/2022-10-21-meeting.md b/_sips/results/2022-10-21-meeting.md new file mode 100644 index 0000000000..41347c6274 --- /dev/null +++ b/_sips/results/2022-10-21-meeting.md @@ -0,0 +1,16 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 21st October 2022 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/fewer-braces.html + name: SIP-44 - Fewer braces + result: accepted + - url: https://docs.scala-lang.org/sips/scala-cli.html + name: SIP-46 - Use Scala CLI to implement the 'scala' command + result: accepted + - url: https://docs.scala-lang.org/sips/clause-interleaving.html + name: SIP-47 - Clause Interleaving + result: accepted +--- + diff --git a/_sips/results/2022-11-18-meeting.md b/_sips/results/2022-11-18-meeting.md new file mode 100644 index 0000000000..035164ad8b --- /dev/null +++ b/_sips/results/2022-11-18-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 18th November 2022 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/polymorphic-eta-expansion.html + name: SIP-49 - Polymorphic Eta-Expansion + result: accepted +--- diff --git a/_sips/results/2023-02-17-meeting.md b/_sips/results/2023-02-17-meeting.md new file mode 100644 index 0000000000..ede0dfc5bf --- /dev/null +++ b/_sips/results/2023-02-17-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 17th February 2023 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/54 + name: SIP-51 - Drop Forwards Binary Compatibility of the Scala 2.13 Standard Library + result: accepted +--- diff --git a/_sips/results/2023-03-17-meeting.md b/_sips/results/2023-03-17-meeting.md new file mode 100644 index 0000000000..7c1ef1e50f --- /dev/null +++ b/_sips/results/2023-03-17-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 17th March 2023 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/scala-cli.html + name: SIP-46 - Scala CLI as default Scala command + result: accepted +--- diff --git a/_sips/results/2023-04-21-meeting.md b/_sips/results/2023-04-21-meeting.md new file mode 100644 index 0000000000..b522759d88 --- /dev/null +++ b/_sips/results/2023-04-21-meeting.md @@ -0,0 +1,12 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 21st April 2023 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html + name: SIP-53 - Quote pattern explicit type variable syntax + result: accepted + - url: https://docs.scala-lang.org/sips/multi-source-extension-overloads.html + name: SIP-54 - Multi-Source Extension Overloads + result: accepted +--- diff --git a/_sips/results/2023-06-16-meeting.md b/_sips/results/2023-06-16-meeting.md new file mode 100644 index 0000000000..0ad96e5ffb --- /dev/null +++ b/_sips/results/2023-06-16-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 16th June 2023 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/multi-source-extension-overloads.html + name: SIP-54 - Multi-Source Extension Overloads + result: accepted +--- diff --git a/_sips/results/2023-07-21-meeting.md b/_sips/results/2023-07-21-meeting.md new file mode 100644 index 0000000000..6d873e6717 --- /dev/null +++ b/_sips/results/2023-07-21-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 21st July 2023 +partof: results +proposals: [] +--- +No voting was held on this meeting. \ No newline at end of file diff --git a/_sips/results/2023-09-11-meeting.md b/_sips/results/2023-09-11-meeting.md new file mode 100644 index 0000000000..c7231e6d9b --- /dev/null +++ b/_sips/results/2023-09-11-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 11th September 2023 +partof: results +proposals: [] +--- +No voting was held on this meeting. \ No newline at end of file diff --git a/_sips/results/2023-10-20-meeting.md b/_sips/results/2023-10-20-meeting.md new file mode 100644 index 0000000000..33abe5c90a --- /dev/null +++ b/_sips/results/2023-10-20-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 20th October 2023 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/binary-api.html + name: SIP-52 - Binary APIs + result: accepted +--- diff --git a/_sips/results/2023-11-17-meeting.md b/_sips/results/2023-11-17-meeting.md new file mode 100644 index 0000000000..fcb63c9e26 --- /dev/null +++ b/_sips/results/2023-11-17-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 17th November 2023 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/match-types-spec.html + name: SIP-56 - Proper Specification for Match Types + result: accepted +--- diff --git a/_sips/results/2023-12-15-meeting.md b/_sips/results/2023-12-15-meeting.md new file mode 100644 index 0000000000..647b689123 --- /dev/null +++ b/_sips/results/2023-12-15-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 12th December 2023 +partof: results +proposals: [] +--- +No voting was held on this meeting. \ No newline at end of file diff --git a/_sips/results/2024-01-19-meeting.md b/_sips/results/2024-01-19-meeting.md new file mode 100644 index 0000000000..23f2f49c6a --- /dev/null +++ b/_sips/results/2024-01-19-meeting.md @@ -0,0 +1,9 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 19th January 2024 +partof: results +proposals: + - url: https://docs.scala-lang.org/sips/match-types-spec.html + name: SIP-56 - Proper Specification for Match Types + result: accepted as completed +--- diff --git a/_sips/results/2024-04-19-meeting.md b/_sips/results/2024-04-19-meeting.md new file mode 100644 index 0000000000..b5db00bc7a --- /dev/null +++ b/_sips/results/2024-04-19-meeting.md @@ -0,0 +1,16 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 19th April 2024 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/72 + name: SIP-58 - Named Tuples + result: accepted + - url: https://github.com/scala/improvement-proposals/pull/79 + name: SIP-62 - For comprehension improvements + result: accepted + - url: https://github.com/scala/improvement-proposals/pull/81 + name: SIP-64 - Improve the syntax of context bounds and givens + result: accepted +--- +SIP-61 and SIP-63 were discussed but no vote was held. diff --git a/_sips/results/2024-05-24-meeting.md b/_sips/results/2024-05-24-meeting.md new file mode 100644 index 0000000000..51bf1e0a34 --- /dev/null +++ b/_sips/results/2024-05-24-meeting.md @@ -0,0 +1,10 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 24th May 2024 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/78 + name: SIP-61 - Unroll default arguments for binary compatibility + result: accepted +--- +SIP-64 was discussed but no vote was held. diff --git a/_sips/results/2024-06-21-meeting.md b/_sips/results/2024-06-21-meeting.md new file mode 100644 index 0000000000..9ebb1ca250 --- /dev/null +++ b/_sips/results/2024-06-21-meeting.md @@ -0,0 +1,10 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 21st June 2024 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/47 + name: SIP-47 - Clause interleaving + result: accepted +--- +SIP-55, SIP-58, SIP-60, SIP-62, and SIP-64 were discussed but no vote was held. diff --git a/_sips/results/2024-08-16-meeting.md b/_sips/results/2024-08-16-meeting.md new file mode 100644 index 0000000000..73361aa923 --- /dev/null +++ b/_sips/results/2024-08-16-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 16th August 2024 +partof: results +proposals: [] +--- +SIP-49, SIP-52, SIP-57, SIP-58, SIP-61, SIP-62, SIP-64, and SIP-66 were discussed but no vote was held. diff --git a/_sips/results/2024-09-27-meeting.md b/_sips/results/2024-09-27-meeting.md new file mode 100644 index 0000000000..bbb0bb8b81 --- /dev/null +++ b/_sips/results/2024-09-27-meeting.md @@ -0,0 +1,13 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 27th September 2024 +partof: results +proposals: + - url: https://github.com/scala/improvement-proposals/pull/72 + name: SIP-58 - Named Tuples + result: accepted + - url: https://github.com/scala/improvement-proposals/pull/81 + name: SIP-64 - Improve the syntax of context bounds and givens + result: accepted +--- +SIP-62 was discussed but no vote was held. diff --git a/_sips/results/2024-11-15-meeting.md b/_sips/results/2024-11-15-meeting.md new file mode 100644 index 0000000000..1a5da82052 --- /dev/null +++ b/_sips/results/2024-11-15-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 15th November 2024 +partof: results +proposals: [] +--- +SIP-66 was discussed but no vote was held. diff --git a/_sips/results/2024-12-20-meeting.md b/_sips/results/2024-12-20-meeting.md new file mode 100644 index 0000000000..a9454f3250 --- /dev/null +++ b/_sips/results/2024-12-20-meeting.md @@ -0,0 +1,7 @@ +--- +layout: sip-meeting-results +title: SIP Meeting Results - 20th December 2024 +partof: results +proposals: [] +--- +SIP-62 and SIP-67 were discussed but no vote was held. diff --git a/_sips/sip-submission.md b/_sips/sip-submission.md deleted file mode 100644 index 4d9ae53a40..0000000000 --- a/_sips/sip-submission.md +++ /dev/null @@ -1,331 +0,0 @@ ---- -layout: sips -title: SIP Specification and Submission ---- - -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. This document captures our guidelines, -commitments and expectations regarding this process. - -## Why write a SIP? - -SIPs are key to making Scala better for the good of everyone. If you decide to -invest the time and effort of putting a SIP forward and seeing it through, your -efforts and time will shape and improve the language, which means that your -proposal may impact the life of a myriad of developers all over the world, -including those on your own team. For many, this aspect alone can be quite -worthwhile. - -However, it's important to note that seeing a SIP through to its conclusion is -an involved task. On the one hand, it takes time to convince people that your -suggestions are a worthwhile change for hundreds of thousands of developers to -accept. Particularly given the sheer volume of developers that could be affected -by your SIP, SIP acceptance is conservative and carefully thought through. -Typically, this includes many rounds of discussion with core Scala maintainers -and the overall community, several iterations on the design of the SIP, and some -effort at prototyping the proposed change. Often, it takes months of discussion, -re-design, and prototyping for a SIP to be accepted and included in the Scala -compiler. It is, therefore important to note that seeing a SIP through to its -conclusion can be time-consuming and not all SIPs may end up in the Scala -compiler, although they may teach us all something! - -If you’re motivated enough to go through this involved but rewarding process, go -on with writing and keep on reading. - -## What's the process for submitting a SIP? - -There are four major steps in the SIP process: - -1. Initial informal discussion (2 weeks) -2. Submission -3. Formal presentation (up to 1 month) -4. Formal evaluation (up to 6 months) - -### Initial informal discussion (2 weeks) - -Before submitting a SIP, it is required that you perform necessary preparations: - -Discuss your idea on the [Scala Contributors Page](https://contributors.scala-lang.org/) (we suggest -posting in the category -[Scala Improvement Process](https://contributors.scala-lang.org/c/sip)). -Create a topic that starts with “Pre-SIP” and briefly -describe what you would like to change and why you think it’s a good idea. - -Proposing your ideas on the mailing list is not an optional step. For every -change to the language, it is important to gauge interest from the compiler -maintainers and the community. Use this step to promote your idea and gather -early feedback on your informal proposal. It may happen that experts and -community members may have tried something similar in the past and may offer -valuable advice. - -Within two weeks after your submission of the pre-SIP to the mailing list, the -Process Lead will intervene and advise you whether your idea can be submitted as -a SIP or needs more work. - -### Submission - -After receiving the green light from the Process Lead, you can write up your -idea and submit it as a SIP. - -A SIP is a Markdown document written in conformance with the [process template](https://github.com/scala/docs.scala-lang/blob/main/_sips/sip-template.md). -It ought to contain a clear specification of the proposed changes. When such -changes significantly alter the compiler internals, the author is invited to -provide a proof of concept. Delivering a basic implementation can speed up the -process dramatically. Even compiler hackers find very difficult to predict the -interaction between the design and the implementation, so the sooner we have an -evidence of a working prototype that interacts with all the features in Scala, -the better. Otherwise, committee members may feel that the proposed changes are -impossible and automatically dismiss them. If your changes are big or somewhat -controversial, don’t let people hypothesize about them and show results upfront. - -A SIP is submitted as a pull request against [the official Scala website -repo](https://github.com/scala/docs.scala-lang). Within a week of receiving the -pull request, the Process Lead will acknowledge your submission, validate it and -engage into some discussions with the author to improve the overall quality of -the document (if necessary). - -When you and the Process Lead agree on the final document, it is formally -accepted for review: assigned a reviewer and scheduled for formal presentation. - -### Formal presentation (up to 1 month) - -During the next available SIP Committee meeting, the appointed reviewer presents -the SIP to the committee and kick starts the initial discussion. - -If the committee agrees that following through the SIP is a good idea, then the -following happens: - -1. The SIP is assigned a number. -2. The SIP pull request is merged into the official Scala website repo, and the -merged document becomes the official webpage of the proposal. -3. An issue to discuss the SIP is opened at the official Scala website repo. Then, -the reviewer submits the initial feedback from the committee. -4. An implementation is requested (if not already present). - -Otherwise, the SIP is rejected. The reviewer submits the collected feedback as a -comment to the pull request, and the pull request is closed. - -### Formal evaluation (up to 6 iterations) - -Evaluation of a SIP is done in iterations. The maximum number of iterations is -six. These iterations take place in the SIP meetings and are usually monthly. -However, they can last longer, in which case the author has more time to -implement all the required changes. - -The committee decides the duration of the next iteration depending upon the -feedback and complexity of the SIP. Consequently, authors have more time to -prepare all the changes. If they finish their revision before the scheduled -iteration, the Process Lead will reschedule it for the next available meeting. - -During every iteration, the appointed reviewer presents the changes (updated -design document, progress with the implementation, etc) to the SIP Committee. -Based on the feedback, the SIP is either: - -1. Accepted, in which case the committee asks the compiler maintainers to - indicate the earliest version of Scala that can include the language change. -2. Rejected, in which case the SIP is closed and no longer evaluated in the - future. -3. Under revision, in which case the author needs to continue the formal - evaluation and address all the committee's feedback. Thus, the follow-up - discussion is scheduled for the next iteration. -3. Postponed, in which case the committee does not evaluate the proposal anymore - and sets it aside under some conditions are met. Then, the SIP will be - resubmitted. This situation happens when proposals entirely depend on another - pending proposals and need their admission. In such cases, the dependent - proposal is postponed until the Committee votes on the other one. - -If no changes have been made to a SIP in two iterations, it’s marked as dormant -and both the PR and issue are closed. Dormant SIPs can be reopened by any -person, be it the same or different authors, at which point it will start from -the formal evaluation phase. - -### Merging the proposal - -If the SIP is accepted, the committee will propose a release date to -the compiler maintainers, where the role of the committee ends. -(Compiler maintainers may choose to merge changes under a flag -initially, for testing, or directly, as they deem appropriate, -taking committee and community feedback into consideration.) - -## Structure of the process - -The SIP process involves the following parties: - -1. The SIP Author(s) -2. The Process Lead -3. The SIP Committee - -### The SIP Author(s) - -Authors are responsible for building consensus within the community and -documenting dissenting opinions before the SIP is officially discussed by the -SIP Committee. Their goal is to convince the committee that their proposal is -useful and addresses pertinent problems in the language as well as interactions -with already existing features. Authors can change over the life-cycle of the -SIP. - -### The Process Lead - -The Process Lead is the responsible of the smooth running of SIPs and SLIPs. He -or she appoints the committee members, calls the meetings monthly, assigns new -proposals to the members, and ensures that all of them are discussed within a -short period of time. - -### The SIP Committee - -The SIP Committee is an experienced group of people with knowledge of the -compiler internals, responsible for the strategic direction of Scala. Members -are tasked with (a) communicating with the community, (b) weighing in pros and -cons of every proposal, and (c) accepting, postponing or rejecting the proposal. - -Committee members should be either individuals responsible for a specific part -of the Scala codebase, committers or contributors of the Scala compiler. -Exceptionally, members may also be important representatives of the community -with a high technical knowledge to understand the implications of every proposal -and participate into the discussions. New members are elected by existing -members of the SIP Committee, based on their expertise and involvement in the -community. - -The current committee members are: - -- Martin Odersky ([@odersky](https://github.com/odersky)), EPFL -- Adriaan Moors ([@adriaanm](https://github.com/adriaanm)), Lightbend -- Guillaume Martres ([@smarter](https://github.com/smarter)), EPFL -- Heather Miller ([@heathermiller](https://github.com/heathermiller)), Carnegie Mellon University -- Iulian Dragos ([@dragos](https://github.com/dragos)), Triplequote -- Josh Suereth ([@jsuereth](https://github.com/jsuereth)), Google -- Miles Sabin ([@milessabin](https://github.com/milessabin)), independent -- Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend -- Sébastien Doeraene ([@sjrd](https://github.com/sjrd)), Scala Center - -The current Process Lead is: - -- Darja Jovanovic ([@darjutak](https://github.com/darjutak)), Scala Center - -### Reviewers - -The Process Lead assigns every proposal to a member of the committee, who -becomes the reviewer. The main tasks of the reviewer are the following: - -1. Discuss unclear points with the authors, -2. Help them address their issues and questions, -3. Provide them feedback from the discussions in the meetings, and -4. Explain the latest progress in every meeting. - -### SIP meetings - -SIP meetings are scheduled monthly by the Process Lead, and require a quorum of -two-thirds (2/3) of the committee members. If the quorum is not reached the -meeting is rescheduled. - -### Voting - -All SIP Committee members vote. They can either vote in the meeting or by -casting their vote via email to the SIP Process Lead before the set deadline. - -SIP Committee members can vote "in favor", "against" or "abstain". - -For a SIP to be accepted, the following three requirements must be met: - -- At least 50% of the committee members vote in favor. -- There are at least two-thirds "in favor" versus "against" votes. -- Martin Odersky does not veto it. - -An alternative way to think of the two-thirds requirement is that the number of -votes in favor must be at least twice the number of votes against. Abstentions -are excluded in calculating a two-thirds vote. - -Note that, when calculating the lower bound, numbers round up. Therefore for a -committee of 9 members, at least 50% means at least 5 members. - -The deadline to vote a proposal is decided on a case-by-case basis. The deadline -will be decided by the committee members present at the last meeting and the SIP -Process Lead, and will be made public right after the meeting. - -#### Examples - -Several voting situations are explained next. - -All of them assume that there are 9 committee members. The 50% requirement -requires at least 5 members to vote in favor. We also assume that Martin does -not veto them. - -| In favor | Against | Abstentions | Voting members | Result | -| -------- | ------- | ----------- | -------------- | ------------ | -| 6 | 2 | 1 | 8 | Accepted | -| 5 | 3 | 1 | 8 | Not Accepted | -| 5 | 2 | 2 | 7 | Accepted | - -In the first situation, the proposal is accepted because 6 is both greater than -the 50% (5) and more than twice the votes against (2). In the second situation, -the proposal meets the 50% requirement but fails the two-thirds requirement -since 5 is less than twice the number of votes against, 3, therefore the -proposal is not accepted. In the third situation, the proposal is accepted -because 5 is equal to 50% of all the committee members and is greater than twice -the number of votes against (2). - -### Responsibilities of the members - -- Review the proposals they are assigned to. The Process Lead will always notify -them two weeks in advance, at minimum. -- Play a role in the discussions, learn in advance about the topic if needed, and -make up their mind in the voting process. -- Decide which utilities should be inside the core module and are required by the -compiler. The goal is to shrink it over time, and, where possible, move modules -to the platform, that will be managed by the SLIP process. - -### Guests - -Experts in some fields of the compiler may be invited to concrete meetings as -guests when discussing related SIPs. Their input would be important to discuss -the current state of the proposal, both its design and implementation. - -## Proposal states - -The state of a proposal changes over time depending on the phase of the process -and the decisions taken by the committee. A given proposal can be in one of -several states: - -1. **Validated:** The Process Lead has validated the proposal and checked that -meets all the formal requirements. -2. **Numbered:** The committee agrees that the proposal is a valid document and -it’s worth considering it. Then, the Process Lead gives it a number. -3. **Awaiting review:** The proposal has been scheduled to be reviewed for a -concrete date. -4. **Under review:** Once the author has delivered a new version, the proposal will -be under review until the next available SIP meeting takes place. -5. **Under revision:** Authors are addressing the issues pinpointed by the -committee or working on the implementation. -6. **Dormant:** When a SIP has been under revision for more than two iterations - and no progress has been made, the Process Lead will mark it as dormant (note - that the committee does not have such privilege). This status means that the - Process Lead will not assign its review in future meetings until authors - provide the requested feedback or progress. Also, authors can also mark their - proposals as dormant, and they are encouraged to do so if they think they - will not have time for their update. -7. **Postponed:** The SIP has been postponed under some concrete conditions. When these -are met, the SIP can be resubmitted. -8. **Rejected:** The SIP has been rejected with a clear and full explanation. -9. **Accepted:** The SIP has been accepted and it’s waiting for the merge into the -Scala compiler. - -## How do I submit? ## - -The process to submit is simple: - -* Fork the Scala documentation repository, [https://github.com/scala/docs.scala-lang](https://github.com/scala/docs.scala-lang). -* Create a new SIP file in the `_sips/sips/`. Use the [S(L)IP template](https://github.com/scala/docs.scala-lang/blob/main/_sips/sip-template.md) - * Make sure the new file follows the format: `YYYY-MM-dd-{title}.md`. Use the proposal date for `YYYY-MM-dd`. - * Use the [Markdown Syntax](https://daringfireball.net/projects/markdown/syntax) to write your SIP. - * Follow the instructions in the [README](https://github.com/scala/docs.scala-lang/) to build your SIP locally so you can ensure that it looks correct on the website. -* Create a link to your SIP in the "pending sips" section of `index.md` -* Commit your changes to your forked repository -* Create a new [pull request](https://github.com/scala/docs.scala-lang/pull/new/gh-pages). This will notify the Scala SIP team. diff --git a/_sips/sip-template.md b/_sips/sip-template.md deleted file mode 100644 index 07668523a8..0000000000 --- a/_sips/sip-template.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -layout: sip -title: SIP-NN - SIP Title -#vote-status: pending <- uncomment this line -permalink: /sips/:title.html -redirect_from: /sips/pending/{filename-without-md}.html ---- - -**By: Author 1 and Author 2 and Author 3** - -This document is provided as a template to get you started. Feel free to add, augment, -remove, restructure and otherwise adapt the structure for what you need after cloning it. -The cloned document should be placed in sips/pending/_posts according to the naming -conventions described in the [SIP tutorial](./sip-tutorial.html). - -## History - -| Date | Version | -|---------------|---------------| -| Feb 19th 2015 | Initial Draft | -| Feb 20th 2015 | Alteration Details | - -## Introduction/Motivation/Abstract - -(feel free to rename this section to Introduction, Motivation, Abstract, whatever best suits - your library proposal.) - -A high level overview of the library with: - -* Description of the scope/features of the library. -* An explanation of the reason the library is needed, what's missing in the core libs - that is supplied. -* Simple code examples if they help clarify, note that there will be ample opportunity - for more detailed code examples later in the SIP - -Think of the introduction as serving to describe the library addition in a way -that lets a reader decide if this is what they are looking for, whether they think -this is the right approach to supply the extra functionality, and whether they might -want to get involved. - -## Motivating Examples - -### Examples - -Code heavy description of how the library functionality can be used. - -```scala -// here is some example scala code, highlighted nicely -import scala.concurrent._ - -class Foo extends Bar { - val x = 10 - def yo(name: String): String = s"hello $name" -} -``` - -### Comparison Examples - -Code demonstrating why the library is needed, how equivalent functionality -might be provided without it. - -### Counter-Examples - -What the library is not intended to be used for. Examples of what to avoid and -why. - -## Design - -Discuss design decisions (including, as examples): - -* Reason about correctness of the implementation. -* "Feel and fit" with existing core libraries. -* Performance and threading considerations. -* Naming of classes, traits, methods. -* Footprint of API. -* Potential conflicts with existing libraries, e.g. name clashes, IDE auto-import friction, etc. - -## Implementation - -Include consideration of the following: - -* Is this library intended to be bundled with the current core libs, or (recommended) live in -a separate module distributed with the core distribution of Scala -(e.g. parser combinators, reflection, etc.) -* **Existing implementation(s)** (e.g. donor library, github project, etc.). Note that -having an existing implementation library from which this will be drawn, and that people -can download and try now, is highly recommended. -* Time frame, target Scala version for inclusion. -* Roll-out plan (start with external module, include in std distribution, move to core). -* Other volunteers/contributors (with areas of expertise, github contact info, etc.) - -## Counter-Examples - -What the library is not intended to be used for. Examples of what to avoid and -why. - -## Drawbacks - -Why should we *not* do this. Be honest, these questions will come out during the -process anyway so it's better to get them out up front. - -## Alternatives - -* What other possibilities have been examined? -* What is the impact of not implementing this proposal? - -## References - -1. [Existing (Donor) Project][1] -2. [API Documentation][2] -3. [Academic/Research papers/supporting material][3] -4. [Alternative Libraries/Implementations][4] - -[1]: https://github.com "GitHub" -[2]: https://www.scala-lang.org/api/ "Scaladoc" -[3]: https://en.wikipedia.org/wiki/Academic_publishing "Academic/Research" -[4]: https://github.com/dogescript/dogescript "Alternatives" diff --git a/_sips/sip-tutorial.md b/_sips/sip-tutorial.md index 6103c16d94..95bb45b053 100644 --- a/_sips/sip-tutorial.md +++ b/_sips/sip-tutorial.md @@ -1,51 +1,90 @@ --- layout: sips title: Writing a new SIP +redirect_from: /sips/sip-template.html --- -This tutorial details of how to write a new SIP and adding it to the website. +This tutorial details of how to write a new Scala Improvement Proposal (SIP) and how to submit it. + +## Why write a SIP? + +SIPs are key to making Scala better for the good of everyone. If you decide to +invest the time and effort of putting a SIP forward and seeing it through, your +efforts and time will shape and improve the language, which means that your +proposal may impact the life of a myriad of developers all over the world, +including those on your own team. For many, this aspect alone can be quite +worthwhile. + +However, it's important to note that seeing a SIP through to its conclusion is +an involved task. On the one hand, it takes time to convince people that your +suggestions are a worthwhile change for hundreds of thousands of developers to +accept. Particularly given the sheer volume of developers that could be affected +by your SIP, SIP acceptance is conservative and carefully thought through. + +It is important to note that seeing a SIP through to its +conclusion can be time-consuming and not all SIPs may end up in the Scala +compiler, although they may teach us all something! + +Last, but not least, delivering a basic implementation can speed up the +process dramatically. Even compiler hackers find very difficult to predict the +interaction between the design and the implementation, so the sooner we have an +evidence of a working prototype that interacts with all the features in Scala, +the better. + +If you’re motivated enough to go through this involved but rewarding process, go +on with writing and keep on reading. + +The following sections provide an overview of the process, and guidelines on +how to submit a proposal. The detailed process specification is available +[here]({% link _sips/process-specification.md %}). + +## Overview of the process + +From being an idea to being part of the language, a SIP goes through several +*stages* that indicate the “maturity” level of the SIP. The following diagram +summarizes the purpose of each stage: + +![](/resources/images/sip/sip-stages.png) + +0. **Pre-SIP** You should start by creating a discussion thread in the + [Scala Improvement Process](https://contributors.scala-lang.org/c/sip/13) + category of the Scala Contributors forum to gather initial community feedback + and support. The title of your discussion should start with "Pre-SIP". In + this discussion, the community might bring you alternative solutions to + the problem you want to solve, or possible bad interactions with other + features of the language. Eventually, these discussions may help you polish + your proposal. +1. **Design** The next stage consists of submitting a detailed description of + your proposal for approval to the SIP Committee. See the section + [How do I submit?](#how-do-i-submit) to know the procedure and expected + format. Your proposal will first be reviewed by a small group of reviewers, + and eventually the full Committee will either approve it or reject it. +2. **Implementation** If the Committee approved your proposal, you are + welcome to provide an implementation of it in the compiler so that it can + be shipped as an experimental feature. You may hit new challenges during the + implementation of the feature, or when testing it in practice. In such a + case, you should amend the proposal, which will be reviewed again by the + reviewers from the SIP Committee. Once the implementation is deemed stable, + the full Committee will vote again on the final state of the proposal. +3. **Completed** If the Committee accepted the proposal, it will be shipped as + a stable feature in the next minor release of the compiler. The content of + the proposal may not change anymore. ## How do I submit? ## -The process to submit is simple: +The process to submit is the following: -* Fork the [Scala documentation repository](https://github.com/scala/docs.scala-lang) and clone it. -* Create a new SIP file in the `_sips/sips`. Use the [SIP template](https://github.com/scala/docs.scala-lang/blob/main/_sips/sip-template.md) - * Make sure the new file follows the format: `YYYY-MM-dd-{title}.md`. Use the proposal date for `YYYY-MM-dd`. +* Fork the [Scala improvement proposals repository](https://github.com/scala/improvement-proposals) and clone it. +* Create a new branch off the `main` branch. +* Copy the [SIP template](https://github.com/scala/improvement-proposals/blob/main/sip-template.md) into the directory `content/` + * Give a meaningful name to the file (e.g., `eta-expansion-of-polymorphic-functions.md`). * Use the [Markdown Syntax](https://daringfireball.net/projects/markdown/syntax) to write your SIP. - * Follow the instructions in the [README](https://github.com/scala/docs.scala-lang/blob/main/README.md) to build your SIP locally so you can ensure that it looks correct on the website. -* Create a link to your SIP in the "pending sips" section of `index.md`. + * The template is provided to help you get started. Feel free to add, augment, remove, restructure and otherwise adapt the structure for what you need. * Commit your changes and push them to your forked repository. -* Create a new pull request. This will notify the Scala SIP team. - - -## SIP Post Format ## - -First, create a new SIP file in the `_sips/sips` directory. Make sure the new file follows the format: `YYYY-MM-dd-{title}.md`. Where: -* `YYYY` is the current year when the proposal originated. -* `MM` is the current month (`01` = January, `12` = December) when the proposal originated. -* `dd` is the day of the month when the proposal originated. -* `{title}` is the title for the SIP. +* Create a new pull request. This will notify the Scala SIP team, which will get back to you within a couple of weeks. ### Markdown formatting ### Use the [Markdown Syntax](https://daringfireball.net/projects/markdown/syntax) to write your SIP. -If you would like a starting point, clone the [SIP Template](./sip-template.html) in -`_sips/sip-template.md` and use that. - -See the [source](https://github.com/scala/docs.scala-lang/blob/main/_sips/sip-template.md) for this document (`sip-tutorial.md`) for how to do syntax highlighting. - -```scala -class Foo -``` - - -## Testing changes ## - -Testing changes requires installing [Jekyll](https://jekyllrb.com/docs/installation/). Since this site is hosted on github pages, make sure you have [whatever version of Jekyll that github is running](https://help.github.com/articles/using-jekyll-with-pages#troubleshooting). As of the writing of this README, that is version >= 1.0.x. - -After the installation, you need to start up the local server. The -[README](https://github.com/scala/docs.scala-lang/blob/main/README.md) gives -a concise explanation on how to do it. When the server is running, view your -changes at [https://localhost:4000/sips](https://localhost:4000/sips). +See the content of the [SIP template](https://github.com/scala/improvement-proposals/blob/main/sip-template.md) as a starting point, and for various examples, including syntax highlighting of code snippets. diff --git a/_sips/sips/2011-10-13-uncluttering-control.md b/_sips/sips/2011-10-13-uncluttering-control.md deleted file mode 100644 index e1ceb5d790..0000000000 --- a/_sips/sips/2011-10-13-uncluttering-control.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: sip -title: SIP-12 - Uncluttering Scala’s syntax for control structures. - -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 -redirect_from: /sips/pending/uncluttering-control.html ---- - -**By: Martin Odersky** - -## Motivation ## - -The more Scala code I write the more I get tripped up by the need to write conditions in if-then-else expressions and other control constructs in parentheses. I normally would not advocate syntax changes at this level, except that this has been the single syntax decision that feels worse for me the longer I use it. - -## Part 1: if ## - -Having to write parentheses is an unfortunate inheritance from C via Java. It makes code more cluttered than it could be. In C/C++/Java this was no big deal, but because Scala is much cleaner syntactically than these languages it starts to stick out like a sore thumb. This in particular because only one form of `if` comes with parentheses; if you use an if as a filter in a for expression or as a guard in a pattern, no parentheses are required. - -So, here is the proposal (for Scala 2.10): - -1. Introduce a new keyword, `then`. - -2. Allow the following alternative syntax form: - - if expression then expression [else expression] - -3. At some point in the future (there’s no rush) we could deprecate the form - - if (expression) expression else expression - - and then remove it. - - -Once we have dealt with if, we should do the same thing with while, do-while and for. - -## Part 2: do-while ## - -do-while is easy. Simply do the following: - -1. Allow - - do expression while expression - - as syntax (i.e. drop the required parentheses around the condition). - -While loops and for loops are more tricky. - -## Part 3: while ## - -For while loops: - -1. Allow - - while expression do expression - - as syntax.We then have to deal with an ambiguity: What should we do with - - while (expression1) do expression2 while (expression3) - - ? I.e. a `do-while` loop inside an old-style `while` loop? Here’s a possible migration strategy. - -2. In Scala 2.10: Introduce - - while expression1 do expression2 - - where `expression1` is not allowed to have parentheses at the outermost level (there’s no need to have them anyway). Also, emit a deprecation warning if the compiler comes across a do-while nested directly in an old-style while: - - 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 expression3 } - -3. In Scala 2.11: Disallow - - while (expression1) do expression2 while expression3 - -4. In Scala 2.12: Drop the restriction introduced in 2.10. Conditions in a `while-do` can now be arbitrary expressions including with parentheses at the outside. - -## Part 4: for ## - -For-loops and for expressions can be handled similarly: - -1. Allow - - for enumerators yield expression - - as syntax. Enumerators are treated as if they were in braces, i.e. newlines can separate generators without the need for additional semicolons. - -2. Allow - - for enumerators do expression - - as syntax. Treat `do-while` ambiguities as in the case for `while`. - -3. At some point in the future: deprecate, and then drop the syntax - - for (enumerators) expression - for {enumerators} expression - for (enumerators) yield expression - for {enumerators} yield expression - -## Examples ## - -Here are some examples of expressions enabled by the changes. - - if x < y then x else y - - while x >= y do x /= 2 - - for x <- 1 to 10; y <- 1 to 10 do println(x * y) - - for - x <- 0 until N - y <- 0 until N - if isPrime(x + y) - yield (x, y) - -## 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/sips/2012-03-09-self-cleaning-macros.md b/_sips/sips/2012-03-09-self-cleaning-macros.md deleted file mode 100644 index 32d50df49f..0000000000 --- a/_sips/sips/2012-03-09-self-cleaning-macros.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: sip -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 -redirect_from: /sips/pending/self-cleaning-macros.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). - - diff --git a/_sips/sips/2012-03-30-source-locations.md b/_sips/sips/2012-03-30-source-locations.md deleted file mode 100644 index b80c95b23b..0000000000 --- a/_sips/sips/2012-03-30-source-locations.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: sip -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 -redirect_from: /sips/pending/source-locations.html ---- - -**Philipp Haller** - -**30 March 2012** - -## Motivation ## - -The Scala compiler's error messages would be nearly useless if they wouldn't provide the corresponding source location, that is, file name, line number, and (some representation of the) character offset, of each error. However, source locations are also very useful outside of the compiler. Libraries and frameworks routinely deal with application- or system-level errors (usually in the form of exceptions), logging, debugging support through tracing, etc. All of these aspects greatly benefit from source location information. - -Moreover, embedded domain-specific languages often depend upon source location information for providing useful error messages. Domain-specific languages that employ staging for code generation can use source locations for debug information in the generated code. - -This proposal discusses a minimal extension of Scala's implicit parameters to provide access to the source location of method invocations. The design is analogous to the way manifests are generated by the compiler, and should therefore be natural to anyone familiar with manifests in Scala. - -## Example ## - -To obtain the source location for each invocation of a method `debug`, say, one adds an implicit parameter of type `SourceLocation`: - - 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"`: - - 34: if (debugEnabled) debug("debug message") - -Assuming `debugEnabled` evaluates to `true`, the above expression would lead to the following output: - - @MyApp.scala:34: debug message - -## The SourceLocation Trait ## - -The `SourceLocation` trait is a new type member of the `scala.reflect` package. It has the following members: - - trait SourceLocation { - /** The name of the source file */ - def fileName: String - - /** The line number */ - def line: Int - - /** The character offset */ - def charOffset: Int - } - -## Specification ## - -Implicit source locations are supported through the following small addition to Scala's implicit rules. - -If an implicit parameter of a method or constructor is of type `SourceLocation`, a source location object is determined according to the following rules. - -First, if there is already an implicit argument of type `SourceLocation`, this argument is selected. Otherwise, an instance of `SourceLocation` is generated by invoking the `apply` method of the object `scala.reflect.SourceLocation`, passing the components of the source location as arguments. - -## Implementation ## - -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/sips/2013-05-31-improved-lazy-val-initialization.md b/_sips/sips/2013-05-31-improved-lazy-val-initialization.md deleted file mode 100644 index 1efe9e9eab..0000000000 --- a/_sips/sips/2013-05-31-improved-lazy-val-initialization.md +++ /dev/null @@ -1,749 +0,0 @@ ---- -layout: sip -title: SIP-20 - Improved Lazy Vals Initialization -vote-status: dormant -vote-text: This proposal lacks an implementation for Scalac and is looking for a new owner. -permalink: /sips/:title.html -redirect_from: /sips/pending/improved-lazy-val-initialization.html ---- - -**By: Aleksandar Prokopec, Dmitry Petrashko, Miguel Garcia, Jason Zaugg, Hubert Plociniczak, Viktor Klang, Martin Odersky** - - -## Abstract ## - -This SIP describes the changes in the lazy vals initialization mechanism that address some of the unnecessary deadlock scenarios. The newly proposed lazy val initialization mechanism aims to eliminate the acquisition of resources during the execution of the lazy val initializer block, thus reducing the possibility of a deadlock. The concrete deadlock scenarios that the new lazy val initialization scheme eliminates are summarized below. - -The changes in this SIP have previously been discussed in depth on the mailing lists \[[1][1]\] \[[2][2]\] \[[9][9]\] \[[10][10]\]. - - -## Description ## - -The current lazy val initialization scheme uses double-checked locking to initialize the lazy val only once. A separate volatile bitmap field is used to store the state of the lazy val - a single bit in this bitmap denotes whether the lazy val is initialized or not. -Assume we have the following declaration. - - final class LazyCell { - lazy val value = - } - -Here is an example of a manually written implementation equivalent to what the compiler currently does: - - final class LazyCell { - @volatile var bitmap_0: Boolean = false - var value_0: Int = _ - private def value_lzycompute(): Int = { - this.synchronized { - if (!bitmap_0) { - value_0 = - bitmap_0 = true - } - } - value_0 - } - def value = if (bitmap_0) value_0 else value_lzycompute() - } - -We now describe several deadlock scenarios in an attempt to classify deadlocks related to the current lazy val initialization implementation. - -### No circular dependencies ### - -Assume there are two objects A and B with lazy vals `a0` and `a1`, and `b`, respectively: - - object A { - lazy val a0 = B.b - lazy val a1 = 17 - } - - object B { - lazy val b = A.a1 - } - -The initialization block of `a0` above refers to `b` in B, and the initialization of `B.b` refers to `A.a1`. While a circular dependency exists between these two objects, there is no circular dependency **between specific lazy vals** `a0`, `a1` and `b`. - -In the scheme, there exists a possibility of a deadlock if thread Ta attempts to initialize `A.a0` and thread Tb attempts to initialize `B.b`. Assume that both Ta and Tb start their synchronized blocks simultaneously. A deadlock can occur due to each thread trying to grab the lock of the other object, while holding their own lock until the initialization completes. - -This SIP attempts to address this issue. - -### Circular dependencies ### - -Assume there are two objects A and B with lazy vals `a` and `b` respectively, where `a` needs `b` for initialization and vice versa. The current implementation scheme can cause a deadlock in this situation. Furthermore: in a single threaded scenario, circular dependencies between lazy vals lead to stack overflows with the current implementation: - - scala> object Test { - | object A { lazy val a: Int = B.b } - | object B { lazy val b: Int = A.a } - | } - defined object Test - - scala> Test - res0: Test.type = Test$@6fd1046d - - scala> Test.A.a - java.lang.StackOverflowError - -This is considered erroneous code, and this SIP does not attempt to address this. - -### No circular dependencies with other synchronization constructs ### - -Consider the declaration of the following lazy val: - - class A { self => - lazy val x: Int = { - val t = new Thread() { - override def run() { self.synchronized {} } - } - t.start() - t.join() - 1 - } - } - -In the source there appears to be no circular dependency between the lazy val initialization block and the other synchronization construct, so there should be no reason for deadlock. As long as thread `t` completes a condition that the caller depends on and eventually lets go of the current object `self`, the lazy val should be able to initialize itself. - -With the current initialization scheme, however, initializing `x` causes a deadlock. The calling thread initializing `x`, holds the monitor of the current object `self`. The same monitor is needed by thread `t`, since it also synchronizes on `self`, thus causing a deadlock. - -This SIP attempts to address this issue. - -The thread that fulfills the condition that the lazy val initializer block suspends on could on the other hand permanently grab a hold of the monitor of the `self` object. Although this is not strictly speaking a deadlock, it is worth mentioning that the current lazy val initialization implementation might not complete in the following scenario case. - - class A { self => - val latch = new java.util.concurrent.CountDownLatch(1) - val t = new Thread() { - override def run() { - latch.countDown() - self.synchronized { while (true) {} } - } - } - t.start() - lazy val x: Int = { - latch.await() - 1 - } - x - } - -This SIP does not attempt to solve this issue - lazy val initialization semantics assume that the `self` object monitor is available or can be made available throughout the lazy val initialization. - -### Circular dependencies with other synchronization constructs ### - -Consider the declaration of the following lazy val: - - lazy val x: Int = { - val t = new Thread() { - override def run() { println(x) } - } - t.start() - t.join() - 1 - } - -In this code, the lazy val initialization block suspends until a condition is fulfilled. This condition can only be fulfilled by reading the value of lazy val `x` - another thread that is supposed to complete this condition cannot do so. Thus, a circular dependency is formed, and the lazy val cannot be initialized. Similar problems can arise with Scala singleton object initialization \[[3][3]\] when creating threads from the singleton object constructor and waiting for their completion, and with static initializer blocks in Java \[[4][4]\] \[[5][5]\]. - -Note that this problem can also happen if two separate threads try to initialize two singleton objects that refer to each other, which is somewhat more severe. The rule of the thumb for programmers should be - singleton objects should not refer to each other during initialization. - -This is considered an anti-pattern, and this SIP does not attempt to address these issues. - -## Implementation ## - -The solution to the problems that this SIP attempts to address is based on avoiding the acquisition of the current object monitor during the time that the lazy val initializer block executes. Instead of executing the initializer block from within a synchronized block, the synchronized block is run twice, once at the beginning to publicize the information that the initializer block is being run and once after the initializer block to publicize the information that the lazy val field has been computed and assigned. The new scheme will require maintaining 2 bits per lazy field instead of 1, as in the current implementation. -We will refer to the existing, current lazy val initialization implementation as V1. - -### Version V2 ### - -Here is an example of manually written implementation for the `LazyCell` class from the previous section: - - class LazyCell { - @volatile var bitmap_0: Int = 0 - var value_0: Int = _ - private def value_lzycompute(): Int = { - this.synchronized { - if (bitmap_0 == 0) { - bitmap_0 = 1 - } else { - while (bitmap_0 == 1) { - this.wait() - } - return value_0 - } - } - val result = - this.synchronized { - value_0 = result - bitmap_0 = 3 - this.notifyAll() - } - value_0 - } - def value = if (bitmap_0 == 3) value_0 else value_lzycompute() - } - -The state of the lazy val is represented with 3 values: 0, 1 and 3. Note that only 2 bits are sufficient to represent them but we use an entire integer here for purposes of clarity. The state 0 represents a non-initialized lazy val. The state 1 represents the lazy val that is currently being initialized by some thread. The state 3 represents the lazy val that has been initialized. - -The first-arriving thread sets the state 1 from the synchronized block, leaves the synchronized block and proceeds by executing the initializer block. Subsequently arriving threads enter the synchronized block, see state 1 and `wait` until they are notified that the lazy val has been assigned. The first-arriving thread sets the state to 3 and notifies them after it computes the result and enters the second synchronized block. - -### Version V3 - the `notifyAll` improvement ### - -As noted in the previous discussions \[[1][1]\] \[[2][2]\] \[[6][6]\] \[[7][7]\], the `notifyAll` call in the proposed implementation bears a significant cost - in the uncontended case it slows down the initialization by a factor of at least 4x measured on a 3.4 GHz i7-2600 and JDK 7 update 4. - -Therefore, we propose the following implementation that avoids calling `notifyAll` unless the first-arriving thread knows that there are concurrent readers of the lazy val. We introduce the fourth state corresponding to the bitmap value 2, that denotes that there are concurrent readers of the lazy val. The first-arriving thread only calls the `notifyAll` if it finds state 2 in the second synchronized block. - - class LazyCell { - @volatile var bitmap_0 = 0 - var value_0: Int = _ - private def value_lzycompute(): Int = { - this.synchronized { - (bitmap_0: @annotation.switch) match { - case 0 => - bitmap_0 = 1 - case 1 => - bitmap_0 = 2 - do this.wait() while (bitmap_0 == 2.toByte) - return value_0 - case 2 => - do this.wait() while (bitmap_0 == 2.toByte) - return value_0 - case 3 => - return value_0 - } - } - val result = - this.synchronized { - val oldstate = bitmap_0 - value_0 = result - bitmap_0 = 3 - if (oldstate == 2) this.notifyAll() - } - value_0 - } - def value = if (bitmap_0 == 3) value_0 else value_lzycompute() - } - -Measured on the same machine, this change seems to be 50% slower than the current lazy val implementation for the uncontended case. - -### Version V4 - the CAS improvement ### - -Each exit or entrance to a synchronized block in principle requires one atomic instruction, amounting to roughly 4 atomic instructions per lazy val initialization. This can be reduced by using the CAS instruction to switch between lazy val states. -Here is an example of a simple implementation obtained by extending the `AtomicInteger` class: - - class LazyCell - extends java.util.concurrent.atomic.AtomicInteger { - var value_0: Int = _ - @tailrec final def value(): Int = (get: @switch) match { - case 0 => - if (compareAndSet(0, 1)) { - val result = - value_0 = result - if (getAndSet(3) != 1) synchronized { notify() } - result - } else value() - case 1 => - compareAndSet(1, 2) - synchronized { - while (get != 3) wait() - notify() - } - value_0 - case 2 => - synchronized { - while (get != 3) wait() - notify() - } - value_0 - case 3 => value_0 - } - } - -This implementation has the following advantages: -- it seems to have the same initialization performance as the current implementation, in the uncontended case -- it relies less on synchronized blocks, needing them only in case of contention, thus being less prone to deadlocks - -Some disadvantages: -- we cannot always extend `AtomicInteger`, some classes already inherit something else -- in the contended worst-case, this scheme is equally prone to deadlocks, because it needs to acquire the synchronized block - -However, in the more general setting where there are two or more lazy val fields in an object: -- the overall memory footprint could possibly increase - we would spend a minimum of 4 bytes per bitmap on first lazy val, where this was previously 1 byte -- in a setting with multiple bitmaps or an existing base class, we cannot extend `AtomicInteger` (which internally uses `Unsafe` directly), and instead need to use `AtomicIntegerFieldUpdater`s that are slower due to extra checks -- in a setting with multiple lazy val fields, we can no longer use `getAndSet` in the initialization (concurrent accesses to other lazy fields may modify the bitmap - we have to read and recompute the expected bitmap state) - we need a `compareAndSet` and have some retry-logic (see the `complete` method below), which is slower -- due to the restrictions on the `AtomicIntegerFieldUpdater`s, we would need to make the `bitmap_0` field publicly visible on the byte-code level, which might be an issue for Java code interfacing with Scala code -- it is much more complicated than the 2 synchronized blocks implementation - -Here is a more general implementation(V4-general), that is slower in the uncontended case than both the current implementation (V1) and the proposed implementation with synchronized blocks (V3). This implementation is, however, in the contended case twice as fast than the current implementation (V1). -See the evaluation section for more information. - - class LazyCellBase { // in a Java file - we need a public bitmap_0 - public static AtomicIntegerFieldUpdater arfu_0 = - AtomicIntegerFieldUpdater.newUpdater(LazyCellBase.class, "bitmap_0"); - public volatile int bitmap_0 = 0; - } - - final class LazyCell extends LazyCellBase { - import LazyCellBase._ - var value_0: Int = _ - @tailrec final def value(): Int = (arfu_0.get(this): @switch) match { - case 0 => - if (arfu_0.compareAndSet(this, 0, 1)) { - val result = - value_0 = result - - @tailrec def complete(): Unit = (arfu_0.get(this): @switch) match { - case 1 => - if (!arfu_0.compareAndSet(this, 1, 3)) complete() - case 2 => - if (arfu_0.compareAndSet(this, 2, 3)) { - synchronized { notifyAll() } - } else complete() - } - - complete() - result - } else value() - case 1 => - arfu_0.compareAndSet(this, 1, 2) - synchronized { - while (arfu_0.get(this) != 3) wait() - } - value_0 - case 2 => - synchronized { - while (arfu_0.get(this) != 3) wait() - } - value_0 - case 3 => value_0 - } - } - - -### Version 5 - retry in case of failure ### - -The current Scala semantics demand retrying the initialization in case of failure. -The four versions presented above provide good performance characteristics in benchmarks, but may leave threads waiting forever due to failed initializations, thus leaking threads. Consider this example: - - class LazyCell { - private var counter = -1 - lazy val value = { - counter = counter + 1 - if(counter < 42) - throw null - else 0 - } - } - -In this case, the first attempt to initialize the cell would fail. In version 4 this will leave the bitmap with a value still indicating that there's a thread currently computing the value. All the threads trying to access the value would wait for this (non-existent) thread to finish computation, causing the application to leak threads. - -In order to maintain current Scala semantics, we need to correctly handle failed initializations. Version 5 presented below, does this correctly: - - - class LazyCellBase { // in a Java file - we need a public bitmap_0 - public static AtomicIntegerFieldUpdater arfu_0 = - AtomicIntegerFieldUpdater.newUpdater(LazyCellBase.class, "bitmap_0"); - public volatile int bitmap_0 = 0; - } - - final class LazyCell extends LazyCellBase { - import LazyCellBase._ - var value_0: Int = _ - @tailrec final def value(): Int = (arfu_0.get(this): @switch) match { - case 0 => - if (arfu_0.compareAndSet(this, 0, 1)) { - val result = - try {} catch { - case x: Throwable => - complete(0); - throw x - } - value_0 = result - - @tailrec def complete(newState: Int): Unit = (arfu_0.get(this): @switch) match { - case 1 => - if (!arfu_0.compareAndSet(this, 1, newState)) complete() - case 2 => - if (arfu_0.compareAndSet(this, 2, newState)) { - synchronized { notifyAll() } - } else complete() - } - - complete(3) - result - } else value() - case 1 => - arfu_0.compareAndSet(this, 1, 2) - synchronized { - while (arfu_0.get(this) != 3) wait() - } - value_0 - case 2 => - synchronized { - while (arfu_0.get(this) != 3) wait() - } - value_0 - case 3 => value_0 - } - } - -This version is basically the same as Version 4, but it handles retries in accordance with current Scala specification. -Unfortunately, this comes with a slight performance slowdown. See the evaluation section for more information. - - -### Version 6 - No synchronization on `this` and concurrent initialization of fields ### - -Note that the current lazy val initialization implementation is robust against the following scenario, in which the initialization block starts an asynchronous computation attempting to indefinitely grab the monitor of the current object. - - class A { self => - lazy val x: Int = { - (new Thread() { - override def run() = self.synchronized { while (true) {} } - }).start() - 1 - } - } - -In the current implementation, the monitor is held throughout the lazy val initialization and released once the initialization block completes. -All versions proposed above, release the monitor during the initialization block execution and re-acquire it back, so the code above could stall indefinitely. - -Additionally, usage of `this` as synchronization point may disallow concurrent initialization of different lazy vals in the same object. Consider the example below: - - class TwoLazies { - lazy val slow = { Thread.sleep(100000); 0} - lazy val fast = 1 - lazy val bad: Int = { - (new Thread() { - override def run() = self.synchronized { while (true) {} } - }).start() - 1 - } - } - -Although the two `slow` and `fast` vals are independent, `fast` is required to -wait for `slow` to be computed. This is due to the fact that they both -synchronize on `this` for the entire initialization in the current -implementation. - -In the versions presented above, a single call to `bad` may lead to the monitor -for `this` being held forever, making calls to `slow` and `fast` also block -forever. - -Note that these interactions are very surprising to users as they leak the -internal limitations of the lazy val implementation. - -To overcome these limitations we propose a version that does not synchronize on -`this` and instead uses external monitor-objects that are used solely for -synchronization. - - final class LazyCell { - import dotty.runtime.LazyVals - var value_0: Int = _ - private var bitmap = 0 - @static private bitmap_offset = LazyVals.getOffset(classOf[LazyCell], "bitmap") - def value(): Int = { - var result: Int = 0 - var retry: Boolean = true - val fieldId: Int = 0 // id of lazy val - var flag: Long = 0L - while retry do { - flag = LazyVals.get(this, bitmap_offset) - LazyVals.STATE(flag, 0) match { - case 0 => - if LazyVals.CAS(this, bitmap_offset, flag, 1) { - try {result = } catch { - case x: Throwable => - LazyVals.setFlag(this, bitmap_offset, 0, fieldId) - throw x - } - value_0 = result - LazyVals.setFlag(this, bitmap_offset, 3, fieldId) - retry = false - } - case 1 => - LazyVals.wait4Notification(this, bitmap_offset, flag, fieldId) - case 2 => - LazyVals.wait4Notification(this, bitmap_offset, flag, fieldId) - case 3 => - retry = false - result = $target - } - } - result - } - } - -This implementation relies on the helper functions provided in \[[11][11]\] -module. The most important of these functions, `wait4Notification` and -`setFlag`, are presented below: - - def setFlag(t: Object, offset: Long, v: Int, fieldId: Int) = { - var retry = true - while (retry) { - val cur = get(t, offset) - if (STATE(cur) == 1) retry = CAS(t, offset, cur, v) - else { - // cur == 2, somebody is waiting on monitor - if (CAS(t, offset, cur, v)) { - val monitor = getMonitor(t, fieldId) - monitor.synchronized { - monitor.notifyAll() - } - retry = false - } - } - } - } - - def wait4Notification(t: Object, offset: Long, cur: Long, fieldId: Int) = { - var retry = true - while (retry) { - val cur = get(t, offset) - val state = STATE(cur) - if (state == 1) CAS(t, offset, cur, 2) - else if (state == 2) { - val monitor = getMonitor(t, fieldId) - monitor.synchronized { - monitor.wait() - } - } - else retry = false - } - } - - val processors: Int = java.lang.Runtime.getRuntime.availableProcessors() - val base: Int = 8 * processors * processors - val monitors: Array[Object] = (0 to base).map { - x => new Object() - }.toArray - - def getMonitor(obj: Object, fieldId: Int = 0) = { - var id = (java.lang.System.identityHashCode(obj) + fieldId) % base - if (id < 0) id += base - monitors(id) - } - -This implementation has the following advantages compared to previous version: -- it allows concurrent initialization of independent fields -- it does not interact with user-written code that synchronizes on `this` -- it does not require expanding monitor on the object to support `notifyAll` - -Some disadvantages: -- the `Unsafe` class, used internally has a disadvantage that it can be disallowed with a custom `SecurityManager`. -Note that this class is extracted from other place in standard library that uses it: scala.concurrent.util.Unsafe.instance -- it requires usage of `identityHashCode` that is stored for every object inside object header. -- as global arrays are used to store monitors, seemingly unrelated things may create contention. This is addressed in detail in evaluation section. - -Both absence of monitor expansion and usage of `identityHashCode` interact with -each other, as both of them operate on the object header. \[[12][12]\] presents -the complete graph of transitions between possible states of the object header. -What can be seen from this transition graph is that in the contended case, -versions V2-V5 were promoting object into the worst case, the `heavyweight -monitor` object, while the new scheme only disables biasing. - -Note that under the schemes presented here, V2-V5, this change only happens in -the event of contention and happens per-object. - -### Non-thread-safe lazy vals ### -While the new versions introduce speedups in the contended case, they do -generate complex byte-code and this may lead to the new scheme being less -appropriate for lazy vals that are not used in concurrent setting. In order to -perfectly fit this use-case we propose to introduce an encoding for -single-threaded lazy vals that is very simple and efficient: - - final class LazyCell { - var value_0 = 0 - var flag = false - def value = - if (flag) value_0 - else { - value_0 = ; - flag = true; - } - } - -This version is faster than all other versions on benchmarks but does not correctly handle safe publication in the case of multiple threads. It can be used in applications that utilize multiple threads if some other means of safe publication is used instead. - -### Elegant Local lazy vals ### -Aside from lazy vals that are fields of objects, scala supports local lazy vals, defined inside methods: - - def method = { - lazy val s = - s - } - -Currently, they use such encoding(we will call it L1): - - def method = { - var @volatile flag: Byte = 0.toByte - var s_0 = 0 - def s = { - if(flag == 0){ - this.synchronized{ - if (flag == 0) { - s_0 = - flag = 1 - } - } - s_0 - } - s - } - -Which is later translated by subsequent phases to: - - private def method$s(flag: VolatileByteRef, s_0: IntRef) = { - if(flag.value == 0){ - this.synchronized{ - if (flag.value == 0) { - s_0.value = - flag.value = 1 - } - } - s_0.value - } - - def method = { - var flag = new VolatileByteRef(0) - var s_0 = new IntRef(0) - method$s(flag, s_0) - } - - - -The current implementation has several shortcomings: - - it allocates two boxes - - it synchronizes on `this`. This is most severe in case of lambdas, as lambdas do not introduce a new `this`. - -We propose a new scheme, that is both simpler in implementation and is more efficient and slightly more compact. -The scheme introduces new helper classes to the standard library: such as `dotty.runtime.LazyInt`\[[17][17]\] and uses them to implement the local lazy val behavior. - - class LazyInt { - var value: Int = _ - @volatile var initialized: Boolean = false - } - - private def method$s(holder: LazyInt) = { - if(!holder.initialized){ - holder.synchronized{ - if (!holder.initialized) { - holder.value = - holder.initialized = true - } - } - holder.value - } - - def method = { - var holder = new LazyInt() - method$s(holder) - } - -This solves the problem with deadlocks introduced by using java8 lambdas.\[[14][14]\] - - -### Language change ### -To address the fact that we now have both thread safe and single-threaded lazy vals, -we propose to bring lazy vals in sync with normal vals with regards to usage of `@volatile` annotation. - -In order to simplify migration, Scalafix the migration tool that will be used to migrate between versions of Scala, including Dotty, supports a `VolatileLazyVal` rewrite that adds `@volatile` to all `lazy vals` present in the codebase. - - -## Evaluation ## - -We focus on the memory footprint increase, the performance comparison and byte-code size. Note that the fast path (i.e. the cost of accessing a lazy val after it has been initialized) stays the same as in the current implementation. Thus, the focus of our measurements is on the overheads in the lazy val initialization in both the uncontended and the contended case. -The micro-benchmarks used for evaluation are available in a GitHub repo \[[6][6]\] and the graphs of the evaluation results are available online \[[7][7]\], \[[18][18]\], \[[19][19]\] . We used the ScalaMeter tool for measurements \[[9][9]\]. - -### Memory usage footprint ### - -We expect that the proposed changes will not change the memory footprint for most objects that contain lazy vals. Each lazy val currently requires 4 or 8 bytes for the field, and an additional bit in the bitmap. As soon as the first bit is introduced into the bitmap, 1 additional byte is allocated for the object. - -Since we now use 2 bits per lazy val field instead of 1, for classes having 4 or less lazy val field declarations the memory footprint per instance will thus not grow. For classes having more lazy val field declarations the memory footprint per instance will in most cases not grow since the objects have to be aligned to an 8 byte boundary anyway. - -We measured the memory footprint of an array of objects with single lazy val fields. The memory footprint did not change with respect to the current version \[[6][6]\] \[[7][7]\]. -The detailed experimental measurements graphs of memory footprint can be seen in graphs.\[[18][18]\] - -### Performance ### - -We measured performance in both the uncontended and the contended case. We measured on an i7-2600, a 64-bit Oracle JVM, version 1.7 update 4. - -For the uncontended case, we measure the cost of creating N objects and initializing their lazy val fields. The measurement includes both the object creation times and their initialization, where the initialization is the dominant factor. - -For the contended case, we measure the cost of initializing the lazy fields of N objects, previously created and stored in an array, by 4 different threads that linearly try to read the lazy field of an object before proceeding to the next one. The goal of this test is to asses the effect of entering the synchronized block and notifying the waiting threads - since the slow path is slower, the threads that “lag” behind should quickly reach the first object with an uninitialized lazy val, causing contention. - -The current lazy val implementation (V1) seems to incur initialization costs that are at least 6 times greater compared to referencing a regular val. The handwritten implementation produces identical byte-code, with the difference that the calls are virtual instead of just querying the field value; this is probably the reason as to why it is up to 50% slower. The 2 synchronized blocks design with an eager notify (V2) is 3-4 times slower than the current implementation - just adding the `notifyAll` call changes things considerably. The 4 state/2 synchronized blocks approach (V3) is only 33-50% slower than the current implementation (V1). The CAS-based approach where `AtomicInteger`s are extended is as fast as the current lazy val initialization (V1), but when generalized and replaced with `AtomicReferenceFieldUpdater`s as discussed before, it is almost 50% slower than the current implementation (V1). The final version, V6 uses `Unsafe` to bring back performance and is as around twice as fast as current implementation (V1) while maintaining correct semantics. - -The CAS-based approaches (V4, V5 and V6) appear to offer the best performance here, being twice as fast than the current implementation (V1). - -The proposed solution with (V6) is 50% faster\[[19][19]\] than the current lazy val implementation in the contended case. This comes at a price of synchronizing on a global array of monitors, which may create contention between seemingly unrelated things. The more monitors that are created, the less is the probability of such contention. There's also a positive effect though, the reuse of global objects for synchronization allows the monitors on the instances containing lazy vals to not be expanded, saving on non-local memory allocation. The current implementation uses `8 * processorCount * processorCount` monitors and the benchmarks and by-hand study with "Vtune Amplifier XE" demonstrate that the positive effect dominates, introducing a 2% speedup\[[13][13]\]. It’s worth mentioning that this is not a typical use-case that reflects a practical application, but rather a synthetic edge case designed to show the worst-case comparison demonstrating cache contention. - -The local lazy vals implementation is around 6x faster than the current version, as it eliminates the need for boxing and reduces the number of allocations from 2 to 1. - -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). -They both perform better than the current implementation in all the contended case. -Specifically, in the contended case, V6 is 2 times faster than V1, while V4-general is 4 times faster. -Unfortunately V4-general is 30% slower in the uncontended case than current implementation(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 ### -The versions presented in V2-V6 have a lot more complex implementation and this shows up the size of the byte-code. In the worst-case scenario, when the `` value is a constant, the current scheme (V1) creates an initializer method that has a size of 34 bytes, while dotty creates a version that is 184 bytes long. Local optimizations present in dotty linker\[[14][14]\] are able to reduce this size down to 160 bytes, but this is still substantially more than the current version. - -On the other hand, the single-threaded version does not need separate initializer method and is around twice smaller than the current scheme (V1). - -The proposed local lazy val transformation scheme also creates less byte-code, introducing 34 bytes instead of 42 bytes, mostly due to reduction in constant table size. - -## Current status ## -Version V6 is implemented and used in Dotty, together with language change that makes lazy vals thread-unsafe if `@volatile` annotation is not specified. -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. -While `sun.misc.Unsafe` will remain available 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. - -## Acknowledgements ## - -We would like to thank Peter Levart and the other members of the concurrency-interest mailing list for their suggestions, as well as the members of the scala-internals mailing list for the useful discussions and input. - - -## References ## -1. [Summary of lazy vals discussions, Scala Internals Mailing list, May 2013][1] -2. [The cost of `notifyAll`, Concurrency Interest Mailing List, May 2013][2] -3. [Scala Parallel Collection in Object Initializer Causes a Program to Hang][3] -4. [Program Hangs If Thread Is Created In Static Initializer Block][4] -5. [Java Language Specification, 12.4.2][5] -6. [GitHub Repo with Microbenchmarks][6] -7. [Uncontended Performance Evaluation Results][7] -8. [ScalaMeter GitHub Repo][8] -9. [Lazy Vals in Dotty, Scala Internals Mailing list, February 2014][9] -10. [Lazy Vals in Dotty, Dotty Internals Mailing list, February 2014][10] -11. [LazyVals runtime module, Dotty sourcecode, February 2014][11] -12. [Synchronization, HotSpot internals wiki, April 2008][12] -13. [Lazy Vals in Dotty, cache contention discussion, February 2014][13] -14. [SI-9824 SI-9814 proper locking scope for lazy vals in lambdas, April 2016][14] -15. [Introducing Scalafix: a migration tool for Scalac to Dotty, October 2016][15] -16. [@static sip, January 2016][16] -17. [LazyVal Holders in Dotty][17] -18. [Memory Footprint Evaluation Results][18] -19. [Contended Performance Evaluation Results][19] -20. [JEP 193: Variable Handles][20] - - [1]: https://groups.google.com/forum/#!topic/scala-internals/cCgBMp5k8R8 "scala-internals" - [2]: https://cs.oswego.edu/pipermail/concurrency-interest/2013-May/011354.html "concurrency-interest" - [3]: https://stackoverflow.com/questions/15176199/scala-parallel-collection-in-object-initializer-causes-a-program-to-hang "pc-object-hang" - [4]: https://stackoverflow.com/questions/7517964/program-hangs-if-thread-is-created-in-static-initializer-block "static-init-hang" - [5]: https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2 "jls-spec" - [6]: https://github.com/DarkDimius/lazy-val-bench/blob/CallSites/src/test/scala/example/package.scala "lazy-val-bench-code" - [7]: https://d-d.me/tnc/30/lazy-sip-perf/report/#config=%7B%22filterConfig%22%3A%7B%22curves%22%3A%5B%220%22%2C%221%22%2C%225%22%2C%226%22%2C%227%22%2C%228%22%2C%2210%22%2C%2212%22%5D%2C%22order%22%3A%5B%22param-size%22%2C%22date%22%5D%2C%22filters%22%3A%5B%5B%22100000%22%2C%22300000%22%2C%22500000%22%2C%221000000%22%2C%223000000%22%2C%225000000%22%5D%2C%5B%221477397877000%22%5D%5D%7D%2C%22chartConfig%22%3A%7B%22type%22%3A0%2C%22showCI%22%3Afalse%7D%7D "lazy-val-bench-report" - [8]: https://axel22.github.io/scalameter/ "scalameter-code" - [9]: https://groups.google.com/forum/#!msg/scala-internals/4sjw8pcKysg/GlXYDDzCgI0J "scala-internals" - [10]: https://groups.google.com/forum/#!topic/dotty-internals/soWIWr3bRk8 "dotty-internals" - [11]: https://github.com/lampepfl/dotty/blob/5cbd2fbc8409b446f8751792b006693e1d091055/src/dotty/runtime/LazyVals.scala - [12]: https://wiki.openjdk.java.net/display/HotSpot/Synchronization - [13]: https://groups.google.com/d/msg/scala-internals/4sjw8pcKysg/gD0au4dmTAsJ - [14]: https://github.com/scala/scala-dev/issues/133 - [15]: https://scala-lang.org/blog/2016/10/24/scalafix.html - [16]: https://github.com/scala/docs.scala-lang/pull/491 - [17]: https://github.com/lampepfl/dotty/blob/f92f278ab686ab218e841082dcb026c6c8ef89b7/library/src/dotty/runtime/LazyHolders.scala - [18]: https://d-d.me/tnc/30/lazy-mem/report/#config=%7B%22filterConfig%22%3A%7B%22curves%22%3A%5B%22-1%22%2C%220%22%2C%221%22%2C%222%22%2C%223%22%2C%224%22%2C%225%22%2C%226%22%2C%227%22%2C%228%22%5D%2C%22order%22%3A%5B%22param-size%22%2C%22date%22%5D%2C%22filters%22%3A%5B%5B%221000000%22%2C%222000000%22%2C%223000000%22%2C%224000000%22%2C%225000000%22%5D%2C%5B%221477396691000%22%5D%5D%7D%2C%22chartConfig%22%3A%7B%22type%22%3A0%2C%22showCI%22%3Afalse%7D%7D - [19]: https://d-d.me/tnc/30/lazy-sip-perf/report/#config=%7B%22filterConfig%22%3A%7B%22curves%22%3A%5B%2216%22%2C%2217%22%2C%2218%22%2C%2219%22%2C%2221%22%2C%2222%22%2C%2223%22%5D%2C%22order%22%3A%5B%22param-size%22%2C%22date%22%5D%2C%22filters%22%3A%5B%5B%22100000%22%2C%22300000%22%2C%22500000%22%2C%221000000%22%2C%223000000%22%2C%225000000%22%5D%2C%5B%221477397877000%22%5D%5D%7D%2C%22chartConfig%22%3A%7B%22type%22%3A0%2C%22showCI%22%3Afalse%7D%7D - [20]: https://openjdk.java.net/jeps/193 diff --git a/_sips/sips/2013-06-10-spores.md b/_sips/sips/2013-06-10-spores.md deleted file mode 100644 index 1868410121..0000000000 --- a/_sips/sips/2013-06-10-spores.md +++ /dev/null @@ -1,459 +0,0 @@ ---- -layout: sip -title: SIP-21 - Spores -vote-status: "dormant" -vote-text: There is an implementation for Scala 2.11. A new owner is needed to champion this proposal for the current Scala version. The proposal needs to be updated to explain how to handle transitive spores. -permalink: /sips/:title.html -redirect_from: /sips/pending/spores.html ---- - -**By: Heather Miller, Martin Odersky, and Philipp Haller** - -Updated September 15th, 2013 - -  - -Functional programming languages are regularly touted as an enabling force, as -an increasing number of applications become concurrent and distributed. -However, managing closures in a concurrent or distributed environment, or -writing APIs to be used by clients in such an environment, remains -considerably precarious-- complicated environments can be captured by these -closures, which regularly leads to a whole host of potential hazards across -libraries/frameworks in Scala's standard library and its ecosystem. - -Potential hazards when using closures incorrectly: - -- Memory leaks -- Race conditions, due to capturing mutable references -- Runtime serialization errors, due to unintended capture of references - -This SIP outlines an abstraction, called _spores_, which enables safer use of -closures in concurrent and distributed environments. This is achieved by -controlling the environment which a spore can capture. Using an -_assignment-on-capture_ semantics, certain concurrency bugs due to capturing mutable -references can be avoided. - -## Motivating Examples - -### Futures and Akka Actors - -In the following example, an Akka actor spawns a future to concurrently -process incoming requests. - -**Example 1:** - - def receive = { - case Request(data) => - Future { - val result = transform(data) - sender ! Response(result) - } - } - -Capturing `sender` in the above example is problematic, since it does not -return a stable value. It is possible that the future's body is executed at a -time when the actor has started processing the next `Request` message which -could be originating from a different actor. As a result, the `Response` -message of the future might be sent to the wrong receiver. - - -### Serialization - -The following example uses Java Serialization to serialize a closure. However, -serialization fails with a `NotSerializableException` due to the unintended -capture of a reference to an enclosing object. - -**Example 2:** - - case class Helper(name: String) - - class Main { - val helper = Helper("the helper") - - val fun: Int => Unit = (x: Int) => { - val result = x + " " + helper.toString - println("The result is: " + result) - } - } - -Given the above class definitions, serializing the `fun` member of an instance -of `Main` throws a `NotSerializableException`. This is unexpected, since `fun` -refers only to serializable objects: `x` (an `Int`) and `helper` (an instance -of a case class). - -Here is an explanation of why the serialization of `fun` fails: since `helper` -is a field, it is not actually copied when it is captured by the closure. -Instead, when accessing helper its getter is invoked. This can be made -explicit by replacing `helper.toString` by the invocation of its getter, -`this.helper.toString`. Consequently, the `fun` closure captures `this`, not -just a copy of `helper`. However, `this` is a reference to class `Main` which -is not serializable. - -The above example is not the only possible situation in which a closure can -capture a reference to `this` or to an enclosing object in an unintended way. -Thus, runtime errors when serializing closures are common. - -## Basic Usage - -Spores have a few modes of usage. The simplest form is: - - val s = spore { - val h = helper - (x: Int) => { - val result = x + " " + h.toString - println("The result is: " + result) - } - } - -In this example, no transformation is actually performed. Instead, the -compiler simply ensures that the spore is _well-formed_, i.e., anything that's -captured is explicitly listed as a value definition before the spore's -closure. This ensures that the enclosing `this` instance is not accidentally -captured, in this example. - -Spores can also be used in for-comprehensions: - - for { i <- collection - j <- doSomething(i) - } yield s"${capture(i)}: result: $j" - -Here, the fact that a spore is created is implicit, that is, the `spore` -marker is not used explicitly. Spores come into play because the underlying -`map` method of the type of `doSomething(i)` takes a spore as a parameter. The -`capture(i)` syntax is an alternative way of declaring captured variables, in -particular for use in for-comprehensions. - -Finally, a regular function literal can be used as a spore. That is, a method -that expects a spore can be passed a function literal so long as the function -literal is well-formed. - - def sendOverWire(s: Spore[Int, Int]): Unit = ... - sendOverWire((x: Int) => x * x - 2) - -## Design - -The main idea behind spores is to provide an alternative way to create -closure-like objects, in a way where the environment is controlled. - -A spore is created as follows. - -**Example 3:** - - val s = spore { - val h = helper - (x: Int) => { - val result = x + " " + h.toString - println("The result is: " + result) - } - } - -The body of a spore consists of two parts: - -1. **the spore header:** a sequence of local value (val) declarations only, and -2. **the closure**. - -In general, a `spore { ... }` expression has the following shape. - -Note that the value declarations described in point 1 above can be `implicit` -but not `lazy`. - -**Figure 1:** - - spore { - val x_1: T_1 = init_1 - ... - val x_n: T_n = init_n - (p_1: S_1, ..., p_m: S_m) => { - - } - } - -The types `T_1, ..., T_n` can also be inferred. - -The closure of a spore has to satisfy the following rule. All free variables -of the closure body have to be either - -1. parameters of the closure, or -2. declared in the preceding sequence of local value declarations, or -3. marked using `capture` (see corresponding section below). - -**Example 4:** - - case class Person(name: String, age: Int) - val outer1 = 0 - val outer2 = Person("Jim", 35) - val s = spore { - val inner = outer2 - (x: Int) => { - s"The result is: ${x + inner.age + outer1}" - } - } - -In the above example, the spore's closure is invalid, and would be rejected -during compilation. The reason is that the variable `outer1` is neither a -parameter of the closure nor one of the spore's value declarations (the only -value declaration is: `val inner = outer2`). - -### Evaluation Semantics - -In order to make the runtime behavior of a spore as intuitive as possible, the -design leaves the evaluation semantics unchanged compared to regular closures. -Basically, leaving out the `spore` marker results in a closure with the same -runtime behavior. - -For example, - - spore { - val l = this.logger - () => new LoggingActor(l) - } - -and - - { - val l = this.logger - () => new LoggingActor(l) - } - -have the same behavior at runtime. The rationale for this design decision is -that the runtime behavior of closure-heavy code can already be hard to reason -about. It would become even more difficult if we would introduce additional -rules for spores. - -### Spore Type - -The type of the spore is determined by the type and arity of the closure. If -the closure has type `A => B`, then the spore has type `Spore[A, B]`. For -convenience we also define spore types for two or more parameters. - -In example 3, the type of s is `Spore[Int, Unit]`. -Implementation -The spore construct is a macro which - -- performs the checking described above, and which -- replaces the spore body so that it creates an instance of one of the Spore traits, according to the arity of the closure of the spore. - -The `Spore` trait for spores of arity 1 is declared as follows: - - trait Spore[-T, +R] extends Function1[T, R] - -For each function arity there exists a corresponding `Spore` trait of the same -arity (called `Spore2`, `Spore3`, etc.) - -### Implicit Conversion - -Regular function literals can be implicitly converted to spores. This implicit -conversion has two benefits: - -1. it enables the use of spores in for-comprehensions. -2. it makes the spore syntax more lightweight, which is important in frameworks such as [Spark](https://spark.incubator.apache.org/) where users often create many small function literals. - -This conversion is defined as a member of the `Spore` companion object, so -it's always in the implicit scope when passing a function literal as a method -argument when a `Spore` is expected. For example, one can do the following: - - def sendOverWire(s: Spore[Int, Int]): Unit = ... - sendOverWire((x: Int) => x * x - 2) - -This is arguably much lighter-weight than having to declare a spore before -passing it to `sendOverWire`. - -In general, the implicit conversion will be successful if and only if the -function literal is well-formed according to the spore rules (defined above in -the _Design_ section). Note that _only function literals can be converted to spores_. -This is due to the fact that the body of the function literal has to be checked -by the spore macro to make sure that the conversion is safe. For _named_ function -values (i.e., not literals) on the other hand, it's not guaranteed that the -function value's body is available for the spore macro to check. - -### Capture Syntax and For-Comprehensions - -To enable the use of spores with for-comprehensions, a `capture` syntax has -been introduced to assist in the spore checking. - -To see why this is necessary, let's start with an example. Suppose we have a -type for distributed collections: - - trait DCollection[A] { - def map[B](sp: Spore[A, B]): DCollection[B] - def flatMap[B](sp: Spore[A, DCollection[B]]): DCollection[B] - } - -This type, `DCollection`, might be implemented in a way where the data is -distributed across machines in a cluster. Thus, the functions passed to `map`, -`flatMap`, etc. have to be serializable. A simple way to ensure this is to -require these arguments to be spores. However, we also would like for-comprehensions -like the following to work: - - def lookup(i: Int): DCollection[Int] = ... - val indices: DCollection[Int] = ... - - for { i <- indices - j <- lookup(i) - } yield j + i - -A problem here is that the desugaring done by the compiler for -for-comprehensions doesn't know anything about spores. This is what -the compiler produces from the above expression: - - indices.flatMap(i => lookup(i).map(j => j + i)) - -The problem is that `(j => j + i)` is not a spore. Furthermore, making it a -spore is not straightforward, as we can't change the way for-comprehensions -are translated. - -We can overcome this by using the implicit conversion introduced in the -previous section to convert the function literal implicitly to a spore. - -However, in continuing to look at this example, it's evident that the lambda -still has the wrong shape. The captured variable `i` is not declared in the -spore header (the list of value definitions preceding the closure within the -spore), like a spore demands. - -We can overcome this using the `capture` syntax – an alternative way of -capturing paths. That is, instead of having to write: - - { - val captured = i - j => j + i - } - -One can also write: - - (j => j + capture(i)) - -Thus, the above for-comprehension can be rewritten using spores and `capture` -as follows: - - for { i <- indices - j <- lookup(i) - } yield j + capture(i) - -Here, `i` is "captured" as it occurs syntactically after the arrow of another -generator (it occurs after `j <- lookup(i)`, the second generator in the -for-comprehension). - -**Note:** anything that is "captured" using `capture` may only be a path. - -**A path** (as defined by the Scala Language Specification, section 3.1) is: - -- The empty path ε (which cannot be written explicitly in user programs). -- `C.this`, where `C` references a class. -- `p.x` where `p` is a path and `x` is a stable member of `p`. -- `C.super.x` or `C.super[M].x` where `C` references a class and `x` references a stable member of the super class or designated parent class `M` of `C`. - -The reason why captured expressions are restricted to paths is that otherwise -the two closures - - (x => + capture()) - -and - - (x => + ) - -(where `` and `` are not just paths) would not have the same -runtime behavior, because in the first case, the closure would have to be -transformed in a way that would evaluate `` "outside of the closure". -Not only would this complicate the reasoning about spore-based code (see the -section Evaluation Semantics above), but it's not clear what "outside of the -closure" even means in a context such as for-comprehensions. - -### Macro Expansion - -An invocation of the spore macro expands the spore's body as follows. Given -the general shape of a spore as shown above, the spore macro produces the -following code: - - new [S_1, ..., S_m, R]({ - val x_1: T_1 = init_1 - ... - val x_n: T_n = init_n - (p_1: S_1, ..., p_m: S_m) => { - - } - }) - -Note that, after checking, the spore macro need not do any further -transformation, since implementation details such as unneeded remaining outer -references are removed by the new backend intended for inclusion in Scala -2.11. It's also useful to note that in some cases these unwanted outer -references are already removed by the existing backend. - -The spore implementation classes follow a simple pattern. For example, for -arity 1, the implementation class is declared as follows: - - class SporeImpl[-T, +R](f: T => R) extends Spore[T, R] { - def apply(x: T): R = f(x) - } - -### Type Inference - -Similar to regular functions and closures, the type of a spore should be -inferred. Inferring the type of a spore amounts to inferring the type -arguments when instantiating a spore implementation class: - - new [S_1, ..., S_m, R]({ - // ... - }) - -In the above expression, the type arguments `S_1, ..., S_m`, and `R` should be -inferred from the expected type. - -Our current proposal is to solve this type inference problem in the context of -the integration of Java SAM closures into Scala. Given that it is planned to -eventually support such closures, and to support type inference for these -closures as well, we plan to piggyback on the work done on type inference for -SAMs in general to achieve type inference for spores. - -## Motivating Examples, Revisited - -We now revisit the motivating examples we described in the above section, this -time in the context of spores. - -### Futures and Akka actors - -The safety of futures can be improved by requiring the body of a new future to -be a nullary spore (a spore with an empty parameter list). - -Using spores, example 1 can be re-written as follows: - - def receive = { - case Request(data) => - future(spore { - val from = sender - val d = data - () => { - val result = transform(d) - from ! Response(result) - } - }) - } - -In this case, the problematic capturing of `this` is avoided, since the result -of `this.sender` is assigned to the spore's local value `from` when the spore -is created. The spore conformity checking ensures that within the spore's -closure, only `from` and `d` are used. - -### Serialization - -Using spores, example 2 can be re-written as follows: - - case class Helper(name: String) - - class Main { - val helper = Helper("the helper") - - val fun: Spore[Int, Unit] = spore { - val h = helper - (x: Int) => { - val result = x + " " + h.toString - println("The result is: " + result) - } - } - } - -Similar to example 1, the problematic capturing of `this` is avoided, since -`helper` has to be assigned to a local value (here, `h`) so that it can be -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/sips/2013-06-30-async.md b/_sips/sips/2013-06-30-async.md deleted file mode 100644 index a83bb6f5a4..0000000000 --- a/_sips/sips/2013-06-30-async.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -layout: sip -title: SIP-22 - Async -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 -redirect_from: /sips/pending/async.html ---- - -**By: Philipp Haller and Jason Zaugg** - -## Introduction - -This is a proposal to add constructs that simplify asynchronous and concurrent programming in Scala. The main constructs, async and await, are inspired by similar constructs introduced in C# 5.0. The main purpose of async/await is to make it possible to express efficient asynchronous code in a familiar direct style (where suspending operations look as if they were blocking). As a result, non-blocking code using Scala’s futures API \[[1][1]\] can be expressed without using higher-order functions, such as map and flatMap, or low-level callbacks. - -On the level of types, async and await are methods with simple, intuitive types: - - def async[T](body: => T): Future[T] - def await[T](future: Future[T]): T - -Here, `Future[T]` refers to the `Future` trait in package `scala.concurrent`. (The system can be adapted to other implementations of future-like abstractions; at the moment the API with the required extension points is internal, though.) The above methods are used as follows: - - val fut = async { - slowComputation() - } - -The async construct marks a block of asynchronous code, and returns a future. Depending on the execution context in the implicit scope (see \[[1][1]\]), the block of asynchronous code is either executed on the current thread or in a thread pool. The async block can contain calls to await: - - val futureDOY: Future[Response] = - WS.url("https://api.day-of-year/today").get - - val futureDaysLeft: Future[Response] = - WS.url("https://api.days-left/today").get - - val respFut = async { - val dayOfYear = await(futureDOY).body - val daysLeft = await(futureDaysLeft).body - Ok(s"$dayOfYear: $daysLeft days left!") - } - -Line 1 and 4 define two futures obtained as results of asynchronous requests to two hypothetical web services using an API inspired by Play Framework \[[2][2]\] (for the purpose of this example, the definition of type `Response` is unimportant). The `await` on line 8 causes the execution of the `async` block to suspend until `futureDOY` is completed (with a successful result or with an exception). When the future is completed successfully, its result is bound to the `dayOfYear` val, and the execution of the `async` block is resumed. When the future is completed with an exception (for example, because of a timeout), the invocation of `await` re-throws the exception that the future was completed with. In turn, this completes future respFut with the same exception. Likewise, the `await` on line 9 suspends the execution of the `async` block until futureDaysLeft is completed. - -## Comparison with Scala’s Futures API - -The provided async and await constructs can significantly simplify code coordinating multiple futures. Consider the following example, written using Scala’s futures API together with for-comprehensions: - - def nameOfMonth(num: Int): Future[String] = ... - val date = “““(\d+)/(\d+)“““.r - - for { doyResponse <- futureDOY - dayOfYear = doyResponse.body - response <- dayOfYear match { - case date(month, day) => - for (name <- nameOfMonth(month.toInt)) - yield Ok(s“It’s $name!“) - case _ => - Future.successful(NotFound(“Not a...“)) - } - } yield response - -Line 1 defines an asynchronous method that converts an integer representing the number of a month to the name of the month (for example, the integer 2 is converted to "February"). Since the method is asynchronous, it returns a `Future[String]`. Line 2 defines a regular expression used to extract the month from a date string such as "07/24". The for-comprehension starting on line 4 first awaits the result of `futureDOY` (the example re-uses the definition of `futureDOY` shown earlier). Scala's futures provide methods like `map` and `flatMap`, and can thus be used as generators in for-comprehensions (for a more in-depth introduction of this feature see the official documentation \[[1][1]\]). The use of for-comprehensions can help make future-based code more clear, but in many cases it requires a significant amount of unnatural clutter and workarounds. The above example suffers from the following issues: - -- To extract `dayOfYear`, we are forced to introduce the name `doyResponse`, a useless intermediate result (line 4); -- to await the completion of the future returned by `nameOfMonth`, we are forced to use a nested for-comprehension (line 8); -- the nested for-comprehension forces us to bind the result of nameOfMonth to name, a useless intermediate variable (line 8); -- the nested for-comprehension forces us to introduce an artificial future that's completed upon creation (line 11); -- the artificial future introduces additional overhead and garbage (line 11); -- finally, the use of for-yield might obscure the actual domain which is asynchronous computations with non-blocking awaits. - -The same example can be written using async/await as follows: - - async { - await(futureDOY).body match { - case date(month, day) => - Ok(s“It’s ${await(nameOfMonth(month.toInt))}!“) - case _ => - NotFound(“Not a date, mate!“) - } - } - -This version avoids all drawbacks of the previous version listed above. In addition, the generated code is more efficient, because it creates fewer closures. - -## Illegal Uses - -The following uses of await are illegal and are reported as errors: -- await requires a directly-enclosing async; this means await must not be used inside a closure nested within an async block, or inside a nested object, trait, or class. -- await must not be used inside an expression passed as an argument to a by-name parameter. -- await must not be used inside a Boolean short-circuit argument. -- return expressions are illegal inside an async block. - -## Implementation - -We have implemented the present proposal using the macro system which has been introduced in Scala 2.10 as an experimental feature. Our implementation \[[3][3]\] is targeted at Scala 2.11.0, but runs on using Scala 2.10.1 without any limitations. - -## Async Transform Specification - -In the following we consider the transformation of an invocation `async { }` of the async macro. -Before the block of code (``) is transformed, it is normalized into a form amenable to a transformation into a state machine. This form is called the "A-Normal Form" (ANF), and roughly means that: - -- `if`, `match`, and other control-flow constructs are only used as statements; they cannot be used as expressions; -- calls to `await` are not allowed in compound expressions. - -After the ANF transform, the async macro prepares the state machine -transformation by identifying vals, vars and defs that are accessed -from multiple states. These will be lifted out to fields in the state -machine object. - -The next step of the transformation breaks the code into "chunks." -Each chunk contains a linear sequence of statements that concludes -with a branching decision, or with the registration of a subsequent -state handler as the continuation (the "on-completion handler"). Once -all chunks have been built, the macro synthesizes a class representing -the state machine. The class contains: - -- an integer representing the current state ID -- the lifted definitions -- an `apply(value: Try[Any]): Unit` method that will be called on completion of each future. The behavior of this method is determined by the current state. It records the downcast result of the future in a field, and calls the `resume()` method. -- the `resume(): Unit` method that switches on the current state and runs the users code for one "chunk," and either: (a) registers the state machine as the handler for the next future, or (b) completes the result promise of the async block, if at the terminal state. -- an `apply(): Unit` method that starts the evaluation of the async block's body. - -### Example - - val future = async { - val f1 = async { true } - val x = 1 - def inc(t: Int) = t + x - val t = 0 - val f2 = async { 42 } - if (await(f1)) await(f2) else { val z = 1; inc(t + z) } - } - -After the ANF transform: -- `await` calls are moved to only appear on the RHS of a value definition; -- `if` is no longer used as an expression; instead each branch writes its result to a synthetic var; -- the `ExecutionContext` used to run the async block is obtained as an implicit argument. - -Follows the end result of the ANF transform (with very minor -simplifications). - - { - (); - val f1: scala.concurrent.Future[Boolean] = { - scala.concurrent.Future.apply[Boolean](true)(scala.concurrent.ExecutionContext.Implicits.global) - }; - val x: Int = 1; - def inc(t: Int): Int = t.+(x); - val t: Int = 0; - val f2: scala.concurrent.Future[Int] = { - scala.concurrent.Future.apply[Int](42)(scala.concurrent.ExecutionContext.Implicits.global) - }; - val await$1: Boolean = scala.async.Async.await[Boolean](f1); - var ifres$1: Int = 0; - if (await$1) - { - val await$2: Int = scala.async.Async.await[Int](f2); - ifres$1 = await$2 - } - else - { - ifres$1 = { - val z: Int = 1; - inc(t.+(z)) - } - }; - ifres$1 - } - -After the full async transform: - -- one class is synthesized that represents the state machine. Its `apply()` method is used to start the computation (even the code before the first await call is executed asynchronously), and the `apply(tr: scala.util.Try[Any])` method will continue after each completed future that the async block awaits; - -- each chunk of code is moved into the a branch of the pattern match in `resume$async`; - -- value and function definitions accessed from multiple states are lifted to be members of class `stateMachine`; others remain local, e.g. `val z`; - -- `result$async` holds the promise which is completed with the result of the async block; - -- `execContext$async` holds the `ExecutionContext` that has been inferred. - -Follows the end result of the full async transform (with very minor -simplifications). - - { - class stateMachine$7 extends StateMachine[scala.concurrent.Promise[Int], scala.concurrent.ExecutionContext] { - var state$async: Int = 0; - val result$async: scala.concurrent.Promise[Int] = scala.concurrent.Promise.apply[Int](); - val execContext$async = scala.concurrent.ExecutionContext.Implicits.global; - var x$1: Int = 0; - def inc$1(t: Int): Int = t.$plus(x$1); - var t$1: Int = 0; - var f2$1: scala.concurrent.Future[Int] = null; - var await$1: Boolean = false; - var ifres$1: Int = 0; - var await$2: Int = 0; - def resume$async(): Unit = try { - state$async match { - case 0 => { - (); - val f1 = { - scala.concurrent.Future.apply[Boolean](true)(scala.concurrent.ExecutionContext.Implicits.global) - }; - x$1 = 1; - t$1 = 0; - f2$1 = { - scala.concurrent.Future.apply[Int](42)(scala.concurrent.ExecutionContext.Implicits.global) - }; - f1.onComplete(this)(execContext$async) - } - case 1 => { - ifres$1 = 0; - if (await$1) - { - state$async = 2; - resume$async() - } - else - { - state$async = 3; - resume$async() - } - } - case 2 => { - f2$1.onComplete(this)(execContext$async); - () - } - case 5 => { - ifres$1 = await$2; - state$async = 4; - resume$async() - } - case 3 => { - ifres$1 = { - val z = 1; - inc$1(t$1.$plus(z)) - }; - state$async = 4; - resume$async() - } - case 4 => { - result$async.complete(scala.util.Success.apply(ifres$1)); - () - } - } - } catch { - case NonFatal((tr @ _)) => { - { - result$async.complete(scala.util.Failure.apply(tr)); - () - }; - () - } - }; - def apply(tr: scala.util.Try[Any]): Unit = state$async match { - case 0 => { - if (tr.isFailure) - { - result$async.complete(tr.asInstanceOf[scala.util.Try[Int]]); - () - } - else - { - await$1 = tr.get.asInstanceOf[Boolean]; - state$async = 1; - resume$async() - }; - () - } - case 2 => { - if (tr.isFailure) - { - result$async.complete(tr.asInstanceOf[scala.util.Try[Int]]); - () - } - else - { - await$2 = tr.get.asInstanceOf[Int]; - state$async = 5; - resume$async() - }; - () - } - }; - def apply: Unit = resume$async() - }; - val stateMachine$7: StateMachine[scala.concurrent.Promise[Int], scala.concurrent.ExecutionContext] = new stateMachine$7(); - scala.concurrent.Future.apply(stateMachine$7.apply())(scala.concurrent.ExecutionContext.Implicits.global); - stateMachine$7.result$async.future - } - -## References - -1. [The Scala Futures API][1] -2. [The Play! Framework][2] -3. [Scala Async on GitHub][3] - - [1]: https://docs.scala-lang.org/overviews/core/futures.html "ScalaFutures" - [2]: https://www.playframework.com/ "Play" - [3]: https://github.com/scala/async "ScalaAsync" diff --git a/_sips/sips/2015-6-18-repeated-byname.md b/_sips/sips/2015-6-18-repeated-byname.md deleted file mode 100644 index ee71571b6a..0000000000 --- a/_sips/sips/2015-6-18-repeated-byname.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: sip -title: SIP-24 - Repeated By Name Parameters -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 -redirect_from: /sips/pending/repeated-byname.html - ---- - -**By: Martin Odersky** - -## Motivation - -Scala so far does not allow by-name repeated parameters. But I can't see a good reason why this combination should be disallowed. Also, the combination is necessary to allow vararg parameters that are passed as expressions to inline methods (instead of being lifted out). - -## Syntax - -The syntax for `ParamType` becomes - - ParamType ::= [`=>'] ParamValueType - ParamValueType ::= Type [`*'] - -The syntax implies that a type such as `=> T*`, which is both by-name and repeated is interpreted as `=> (T*)`, that is, as a by-name type of a repeated type. - -## Translation Rules - -If a parameter has a by-name repeated type `=> T*` it matches an arbitrary number of actual arguments of type `T`. As usual for by-name parameters, the arguments are not evaluated at the point of call. Instead, all arguments are evaluated each time the parameter is referenced in the called method. - -The same holds for an vararg argument of the form `e: _*`. The argument expression `e` is evaluated each time the parameter is referenced in the called method. - -## See also - -[Dotty Issue #499](https://github.com/lampepfl/dotty/issues/499) diff --git a/_sips/sips/2016-07-25-unsigned-integers.md b/_sips/sips/2016-07-25-unsigned-integers.md deleted file mode 100644 index ef8ecd7bf1..0000000000 --- a/_sips/sips/2016-07-25-unsigned-integers.md +++ /dev/null @@ -1,636 +0,0 @@ ---- -layout: sip -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 -redirect_from: /sips/pending/unsigned-integers.html ---- - -__Sébastien Doeraene and Denys Shabalin__ - -**Summary**: We propose the addition of 4 "primitive" types to represent -unsigned integers: `UByte`, `UShort`, `UInt` and `ULong`. - -A prototype implementation of this proposal, with unit tests and benchmarks, -can be found [here](https://github.com/scala/scala/compare/2.12.x...sjrd:uints). - -## History - -| Date | Version | -|--------------|---------------| -| Nov 9th 2015 | Initial Draft | - -## Introduction - Motivation - Abstract - -Scala was initially designed to target the JVM, and, as such, defines exactly -9 primitive types corresponding to the 9 primitive types of the JVM (including -`void`): - -* `Boolean` -* `Char` -* `Byte` -* `Short` -* `Int` -* `Long` -* `Float` -* `Double` -* `Unit` - -Compared to other languages, especially those in the tradition of -compile-to-machine code, this list is missing types for unsigned integer types. - -When compiling Scala to other platforms than the JVM, such as JavaScript with -Scala.js or native code/LLVM with the upcoming ScalaNative, the missing unsigned -integer types are a liability, especially when it comes to *interoperability -with host language libraries*. - -For example, if a C library defines a function accepting a `uint32_t`, how would -we type it in a FFI definition? Even in JavaScript, which supposedly has only -`Double`s, there are APIs working with unsigned integers. The most well-known -one is -[the TypedArray API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). -Currently, because of the lack of unsigned integers in Scala, the -[facade types for `Uint8Array` in Scala.js](https://github.com/scala-js/scala-js/blob/v0.6.5/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala) -is forced to use `Short` elements instead of a more appropriate `UByte`. Worse, -[the one for `Uint32Array`](https://github.com/scala-js/scala-js/blob/v0.6.5/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala) -has to work with *Doubles*, because there is no signed integer type existing on -JavaScript that can represent all values of a 32-bit unsigned int. - -On the JVM, interoperability is not an issue. However, it is still sometimes -useful to manipulate unsigned integers. An evidence of this fact is that Java 8 -has added methods in the JDK to *manipulate* signed integers *as if* they were -unsigned. One example is -[`java.lang.Integer.divideUnsigned`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#divideUnsigned-int-int-). -Using signed integer types but interpreting them as unsigned is an obvious lack -of type safety, however. Java cannot decently add new primitive types for -compatibility reasons, but Scala has custom-made `AnyVal`s and other -Object-Oriented abstractions on top of primitive types that can allow for -additional, zero-overhead "primitive" types. - -We therefore propose to extend the Scala programming language with 4 new -primitive data types, representing unsigned integer types: - -* `scala.UByte`, an unsigned 8-bit integer -* `scala.UShort`, an unsigned 16-bit integer -* `scala.UInt`, an unsigned 32-bit integer -* `scala.ULong`, an unsigned 64-bit integer - -These data types will support a set of operations similar to their signed -counterparts, except that they will obviously encode the unsigned behavior. - -For example: - -```scala -val x = Int.MaxValue.toUInt + 1.toUInt // 2147483648 -assert(x.toString == "2147483648") -val y = Int.MaxValue.toUInt + 5.toUInt // 2147483652 -val z = x / y // unsigned division of 2147483652 by 2147483648 -assert(z == 1.toUInt) -assert(x % y == 4.toUInt) -``` - -## Motivating Examples - -### Examples - -#### Interoperability with host language unsigned integers - -The most important use case for true unsigned integers specified by the -language is interoperability with host languages with unsigned data types. - -If we try to manipulate an `Uint32Array` in Scala.js as currently defined, we -end up manipulating `Double`s, which can become extremely confusing: - -```scala -val array = new Uint32Array(js.Array(5, 7, 6, 98)) -val x = array(3) / array(0) -println(x) // 19.6 (!) -``` - -This also causes type safety issues, since nothing prevents the developer from -introducing a non-integer `Double` into such an array: - -```scala -array(2) = 6.4 // compiles, silent drop of precision -println(array(2)) // 6 -``` - -If we can define `Uint32Array` as an array of `UInt` instead, our problems are -solved: - -```scala -val array = new Uint32Array(js.Array(5, 7, 6, 98).map(_.toUInt)) -val x = array(3) / array(0) -println(x) // 19, as expected - -array(2) = 6.4 // does not compile, yeah! -``` - -#### Implementation of algorithms requiring unsigned operations - -No matter the platform, even on the JVM, we sometimes have to implement -algorithms that are best defined in terms of unsigned operations. Particularly, -more often than not, we want to treat bytes in a buffer as unsigned. - -For example, here is a (buggy) algorithm to decode text encoded in ISO-8859-1 -(latin1) into Unicode code points. Can you spot the issue? - -```scala -def decodeISO88591(buffer: Array[Byte]): String = { - val result = new StringBuilder(buffer.length) - for (i <- 0 until buffer.length) - result.append(buffer(i).toChar) - result.toString() -} -``` - -The problem is that `buffer(i).toChar` will first *sign-extend* the signed -`Byte` to an `Int`, then cut off the 16 most significant bits. If the initial -`Byte` was ">= 0x80", it was actually *negative*, and therefore the resulting -`Char` will have its 8 most significant bits set to 1, which is a bug. - -Fixing the algorithm requires knowledge of 2's complement properties and how -the bits are affected by arithmetic operations. The solution is -`(buffer(i) & 0xff).toChar`, which is totally non-obvious. - -With unsigned bytes, the problem does not happen by construction, and the -algorithm is straightforward: - -```scala -def decodeISO88591(buffer: Array[UByte]): String = { - val result = new StringBuilder(buffer.length) - for (i <- 0 until buffer.length) - result.append(buffer(i).toChar) // correct: UByte.toChar does not sign-extend - result.toString() -} -``` - -### Comparison Examples - -The blog post -[Unsigned int considered harmful for Java](https://www.nayuki.io/page/unsigned-int-considered-harmful-for-java) -explains in detail how to correctly manipulate signed integer types to make -them behave as unsigned. - -There are two main problems with this, considering only the JVM target: - -* We have to remember what operations need dedicated methods to deal with the - unsigned case. -* We cannot express in the type of a value whether it is to be interpreted as - a signed or unsigned integer. Effectively, we're back to *Assembly*-grade - weak typing. - -In addition, those separate manipulations do not allow back-ends to other -targets to effectively interoperate with host language unsigned data types. - -### Counter-Examples - -We do not intend to generalize unsigned numbers to Big integers nor to -floating-point numbers. - -Also, it is out of the scope of this proposal to provide literal constant -notations for unsigned integers. They should be constructed from their -corresponding signed literals, and reinterpreted using `.toUInt` and similar. - -## Drawbacks - -### Performance of arrays - -Unless the implementation of unsigned integer types is very much special-cased -in the compiler, arrays of unsigned integer types will likely suffer from the -same performance penalty as arrays of user-defined `AnyVal`s, because the -elements will be boxed. - -This might cause unexpected performance issues, as developers will think that -`Array[UByte]` is as fast as `Array[Byte]`, when in fact it is not. -Performance-critical code should still use `Array[Byte]`, and reinterpret the -bytes into unsigned bytes and back using `sb.toUByte` and `ub.toByte`, -respectively. - -## Alternatives - -The only other alternative is the status quo, where no unsigned integer types -exist, and difficult 2's complement-aware manipulations must be done when we -need them. - -## Design - -Naively, the design of the API is straightforward. We would define 4 new -data types `UByte`, `UShort`, `UInt` and `ULong`, corresponding to the 4 -signed integer data types. They feature the same set of arithmetic and logic -operations, as well as comparisons. However, such a naive design causes -several issues, especially when signed integers and unsigned integers interact -in operations. - -Here, we discuss the set of operations available, and their semantics. - -### No arithmetic/logic operations between signed and unsigned integers - -To prevent typical caveats when mixing signed and unsigned integers in most -languages, we simply *forbid* any arithmetic or logic operations with operands -of different signedness. - -### Universal equality, and hash codes - -The universal equality operators (`==` and `!=`) should behave consistently -across signed and unsigned types. Since `5.toByte == 5` in Scala, we should -also have `5.toUInt == 5`. This requires to modify the `==` implementation -(in `BoxesRunTime`) to implement cooperative equality checks between all -combinations of signed and unsigned values. - -Negative values of signed integers are *not* equal to any unsigned integer -value. For example, this means that `(-1).toUInt != -1`. This is very simply -explained by the fact that the mathematical value of `(-1).toUInt` is -4294967295. - -Because of transitivity of equality, it must be the case that a large value of -an unsigned integer (whose signed reinterpretation is negative, such as -`0xffffffff.toUInt`) *is* equal to values of a larger signed integer types -(such as `0xffffffffL`). - -Hash codes, as computed by `##`, must be made consistent with the generalized -form of universal equality. - -Note that this definition of universal equality is *essential* for these new -primitives to be usable for interoperability scenarios in Scala.js. This is -because, at runtime, "boxed" versions of numeric types loose their type -information, as they are all stuffed into primitive JavaScript numbers. -Therefore, `0xffff.toUShort` is indistinguishable from `0xffff`, and they must -be equal for this "non-boxing" to be valid. - -### Operations on `UByte` and `UShort` - -By analogy to the fact that operations on `Byte`s and `Short` start by -converting their operands to `Int`s (using sign-extend), operations on `UByte`s -and `UShort`s convert their operands to `UInt`s (without sign-extend, -obviously). - -### Arithmetic operations on `UInt`s and `ULong`s. - -For two operands of the same unsigned integer type with N bits, `a + b`, -`a - b`, `a * b`, `a / b` and `a % b` are computed modulo `2^N`. -For `+`, `-` and `*`, this boils down to a primitive (signed) equivalent -operations, on the JVM. `/` and `%` correspond to -`java.lang.{Integer,Long}.divideUnsigned` and `remainderUnsigned`. - -If one of the operand is a `ULong` but the other isn't, the latter is converted -to a `ULong` before performing the operation. - -There is no `unary_-`, because unsigned integers have no opposite. It could be -argued that `-x` could be useful for low-level bit-twiddling-based algorithms. -However, in that case, `~x + 1.toUInt` can be used instead. A basic peephole -optimizer can simplify the latter as `-x` on platforms where this is relevant. - -### Logic (bitwise) operations on `UInt`s and `ULong`s - -`unary_~`, `|`, `&` and `^` behave in the obvious way. If you want a precise -spec, they always behave as if the operand was reinterpreted into its signed -equivalent, then the operation performed, then the result reinterpreted back -into unsigned. - -### Bit shifting operations on `UInt`s and `ULong`s - -Shift left `<<` and shift logical right `>>>` behave in the obvious way. - -The case of shift arithmetic right `>>` is debatable. We argue that it should -*not* be available on unsigned integers for two reasons. - -First, a shift arithmetic right does not appear to have any *meaning* on -unsigned integers. The correct *arithmetic* shift if `>>>`. Therefore, -similarly to `unary_-`, it should not be introduced. - -Second, existing languages that do have unsigned integer types, such as the C -family, actually give different semantics to `>>` depending on whether it has -a signed or unsigned operand: a `>>` on an unsigned operand does *not* -sign-extend. It would be confusing to a C developer for `x >> 3` to sign-extend -in Scala, but it would be equally confusing to a Scala developer that `x >> 3` -*not* sign-extend. Therefore, we prefer to leave it out completely, and let a -compiler error be raised. - -If a bit-twiddling-based algorithm needs the sign-extending shift right, it is -always possible to reinterpret as signed, do the operation, and reinterpret -back as unsigned: `(x.toInt >> 3).toUInt`. - -Note: the current implementation does provide `>>`, until we agree on this -point. - -### Inequality operators - -`<`, `<=`, `>`, `>=` perform unsigned comparisons. On the JVM, they correspond -to `java.lang.{Integer,Long}.compareUnsigned`. - -### String representation - -The string representation is similar to that of signed integers, except that -all numbers are positive, obviously. On the JVM, they correspond to -`java.lang.{Integer,Long}.toUnsignedString`. - -### Implicit widening conversions - -Unsigned integers can be implicitly widened to "larger" unsigned integer types. -For example, `UShort` can be implicitly converted to `UInt` and `ULong`. - -There are no implicit conversions between signed and unsigned integers. It might -be tempting to allow unsigned integers to be widened to larger signed integer -types, since they can always accommodate their mathematical values. However, -this consequently allows operations between signed and unsigned integers, such -as `5L + 4.toUInt`, because of the conversion from `UInt` to `Long`. Since we -want to disallow those to prevent caveats, we do not allow the implicit -conversions. - -### Explicit conversions between signed and unsigned - -We have already extensively used explicit conversions between signed and -unsigned integer types *of the same size* in this document, such as -`someInt.toUInt`. These are specified as reinterpreting the bit pattern into -the other type, with the common specification that integer types are represented -in 2's complement. - -*Narrowing* conversions are also allowed in both directions, such as -`someInt.toUByte` or `someUInt.toByte`. They are equally well specified as -either `someInt.toUInt.toUByte` or `someInt.toByte.toUByte`, with the same -results, and therefore no ambiguity exists. - -*Widening* conversions from unsigned integers to larger signed integers is -allowed, and is specified by conserving the mathematical value, i.e., the -widening does *not* sign-extend. - -Finally, widening conversions from signed integers to larger unsigned integers -is *disallowed*, because both interpretations are equally valid, and therefore -half of the developers will have the wrong expectation. For example, -`someInt.toULong` can be equally validly specified as `someInt.toUInt.toULong` -or `someInt.toLong.toULong`, but those do not yield the same result (the former -does not sign-extend while the latter does). - -### Consequences on other parts of the library - -`scala.math` should receive new overloads of `max` and `min`, taking unsigned -integer types as parameters, and performing the comparisons with unsigned -semantics. - -There should be instances of the `Ordering` typeclass for unsigned integer -types. - -There should *not* be instances of the `Numeric` and `Integral` typeclasses -for unsigned integer types, since they do not support `unary_-`. - -It might be tempting to define `Range`s over unsigned integers, but we do not -want to go there. - -### Performance considerations - -Since they will inevitably be "branded" as primitive data types, unsigned -integer types should be as efficient as signed integer types. - -Fortunately, the JDK 8 added the necessary methods in the standard JDK to -trivially implement all the operations on unsigned integers. It is expected -that these methods be as fast primitive operations, since they can be -intrinsified easily. - -There also exist efficient implementations of these methods in Scala.js. LLVM -supports the relevant operations by default for ScalaNative, obviously. - -The implementations in `BoxesRunTime` for universal equality and hash codes -receives additional cases, which could slow down `==` on `Any`s and generic -values. However, if a codebase does not use any unsigned integer, the JVM, -using global knowledge, can statically determine that the new type tests are -always `false` and therefore remove them. There should be no performance -penalty on such codebases. - -## Implementation - -Because of the cooperative equality with primitive signed integers, the -addition of unsigned integers must necessarily integrate the core library. - -Besides that, there are two major strategies for the implementation: mostly -user-space or mostly in compiler-space. - -### Mostly user-space vs compiler-space - -An existing implementation living mostly in user-space can be found at -https://github.com/scala/scala/compare/2.12.x...sjrd:uints - -In this approach, unsigned integer types are user-defined `AnyVal`s, and all -the new methods are implemented in the library. The compiler needs very little -adaptations; basically only to dispatch `==` of unsigned integers to -`BoxesRunTime` (otherwise, it is "optimized" as directly calling `equals()`). - -This approach has obvious advantages: -* Little room for mistakes -* Very simple implementation -* Minimal impact on the compiler - -However, it also suffers from a couple of limitations, because the new -"primitive" data types are not really primitive, and do not receive some of the -semantic treatment of primitives. - -First, unboxing from `null`: a primitive data type unboxes `null` to its zero, -but user-defined `AnyVal`s throw in those situations. One way to fix this would -be to "fix" the behavior on `AnyVal`s in general, which we think would be a good -idea on its own anyway. - -Second, weak conformance: unsigned integers don't receive the weak conformance -properties. However, we argue that this is no big deal. The main use case for -weak conformance is so that `List(1, 5, 3.5)` can be inferred as a -`List[Double]` instead of a `List[AnyVal]`. The problem initially happens -because of the way we write literal integers and literal doubles. Since unsigned -integers have no literal notation, and are not implicitly compatible with -signed integers nor doubles, this point is moot. - -In the short term, these limitations do not appear important, and we could live -with them. - -More importantly, however, this implementation prevents specialization to be -applied on unsigned integers, and imposes harsh constraints on how back-ends -can implement unsigned integers. - -In Scala.js these constraints are not too hard from the point of view of -interoperability scenarios, although they could limit the performance we can -get out of unsigned integers. Since interoperability is paramount, we can again -live with the constraints for the time being. - -For ScalaNative, the consequences of these constraints are, as of yet, unknown, -but they could affect interoperability scenarios, and will most probably affect -performance. - -In the longer term, we should therefore consider more tightly integrating -unsigned integer types as true primitives of the compiler. - -Such a strategy will however be much riskier, and will take much more time to -get right. - -### Performance evaluation - -To evaluate performance of our prototype we've implemented a simple jmh -benchmark generator that checks composite performance of evaluation of complex -arithmetic expressions for all number types (both primitive signed ones and -user-defined unsigned ones.) - -For each type we've generated 4 benchmarks that use `+, -, *` ops (fastops) -and 4 benchmarks that use `+, -, *, /, %` (allops). Each of the 4 benchmarks uses exactly -the same arithmetic expressions for all types. Benchmarks on bytes and shorts wrap back -to corresponding type after each operation. - -The split between fastops and allops is important because unsigned division is -quite a bit slower than signed one on latest release of JDK 8: - - Benchmark Type Score Error Units - - division Int 289644141.092 ± 1544.707 ops/s - division UInt 129838344.866 ± 95523.094 ops/s - division Long 129839962.558 ± 90581.655 ops/s - division ULong 116493219.034 ± 67688.631 ops/s - - remainder Int 289454769.011 ± 94380.057 ops/s - remainder UInt 111032938.420 ± 679921.289 ops/s - remainder Long 128315753.345 ± 35932.509 ops/s - remainder ULong 97470062.788 ± 346773.054 ops/s - -And here are the results of composite benchmarks. - - Benchmark Type Score Error Units - - allop0 Byte 76612958.318 ± 97814.015 ops/s - allop0 UByte 26160709.822 ± 2098.556 ops/s - allop0 Short 76800575.238 ± 75373.970 ops/s - allop0 UShort 27172978.979 ± 3244.075 ops/s - allop0 Int 82816920.142 ± 50194.565 ops/s - allop0 UInt 27232792.726 ± 5116.920 ops/s - allop0 Long 28648964.226 ± 6115.162 ops/s - allop0 ULong 29657228.040 ± 159758.363 ops/s - - allop1 Byte 73715102.215 ± 17282.291 ops/s - allop1 UByte 26490808.836 ± 86628.862 ops/s - allop1 Short 73718029.884 ± 19413.132 ops/s - allop1 UShort 27041330.181 ± 4533.663 ops/s - allop1 Int 83239625.538 ± 13575.756 ops/s - allop1 UInt 28425497.966 ± 10927.728 ops/s - allop1 Long 29251967.961 ± 8278.100 ops/s - allop1 ULong 30537156.474 ± 14283.996 ops/s - - allop2 Byte 53138040.117 ± 7692.219 ops/s - allop2 UByte 19912528.484 ± 104896.763 ops/s - allop2 Short 52989318.748 ± 10075.293 ops/s - allop2 UShort 19828139.740 ± 217.796 ops/s - allop2 Int 60104405.263 ± 1888.322 ops/s - allop2 UInt 20576204.367 ± 446.445 ops/s - allop2 Long 20752333.428 ± 789.741 ops/s - allop2 ULong 22949083.651 ± 5766.597 ops/s - - allop3 Byte 68147811.661 ± 7349.838 ops/s - allop3 UByte 28016596.929 ± 4795.992 ops/s - allop3 Short 68147020.665 ± 8444.864 ops/s - allop3 UShort 29092855.210 ± 12323.477 ops/s - allop3 Int 93592095.470 ± 2970.030 ops/s - allop3 UInt 33298135.046 ± 15681.174 ops/s - allop3 Long 32276341.887 ± 3748.706 ops/s - allop3 ULong 53345993.564 ± 5486.483 ops/s - - fastop0 Byte 174384841.686 ± 13685.151 ops/s - fastop0 UByte 172490336.775 ± 42178.142 ops/s - fastop0 Short 174388762.469 ± 10303.837 ops/s - fastop0 UShort 172545184.374 ± 37150.012 ops/s - fastop0 Int 335919041.150 ± 121423.806 ops/s - fastop0 UInt 335925277.378 ± 120408.170 ops/s - fastop0 Long 339125057.494 ± 71538.513 ops/s - fastop0 ULong 339306595.964 ± 70387.619 ops/s - - fastop1 Byte 174736448.461 ± 9934.579 ops/s - fastop1 UByte 173817403.787 ± 20752.221 ops/s - fastop1 Short 174734415.599 ± 9850.473 ops/s - fastop1 UShort 173460828.250 ± 18068.154 ops/s - fastop1 Int 285178506.838 ± 129027.835 ops/s - fastop1 UInt 285137070.275 ± 145958.174 ops/s - fastop1 Long 285590926.722 ± 147048.419 ops/s - fastop1 ULong 274695574.679 ± 4878290.228 ops/s - - fastop2 Byte 168971931.233 ± 40481.486 ops/s - fastop2 UByte 169665745.096 ± 27401.842 ops/s - fastop2 Short 168979347.127 ± 11033.548 ops/s - fastop2 UShort 169675543.605 ± 19494.266 ops/s - fastop2 Int 287563728.176 ± 122987.272 ops/s - fastop2 UInt 287559086.868 ± 126833.074 ops/s - fastop2 Long 296129286.397 ± 171488.897 ops/s - fastop2 ULong 296142819.979 ± 167330.949 ops/s - - fastop3 Byte 333536457.973 ± 63928.967 ops/s - fastop3 UByte 339343014.623 ± 119819.041 ops/s - fastop3 Short 333535961.005 ± 69587.789 ops/s - fastop3 UShort 339354474.225 ± 121131.393 ops/s - fastop3 Int 475167307.642 ± 140060.266 ops/s - fastop3 UInt 475181473.416 ± 116982.494 ops/s - fastop3 Long 487109297.325 ± 580807.835 ops/s - fastop3 ULong 487190439.786 ± 737565.041 ops/s - -As you can see, fastops results have statistically insignificant differences -between signed and unsigned numbers. The same is true for allops for `Long` vs -`ULong`. - -Allops are 2-3x slower for `Byte`s, `Short`s and `Int`s, due to the fact that -unsigned division isn't as well optimised. We can probably make divisions for -`UByte` and `UShort` faster by using the regular division at the `Int` level, -but the current implementation does not do that yet. - -### Time frame - -We propose that unsigned integers be integrated in their user-space form as -early as possible, ideally in Scala 2.12, should this proposal be accepted in -time. An implementation is already available for Scalac, and it comes with an -exhaustive unit test suite. - -In the longer term, for 2.13 or 2.14, we propose to evaluate whether the -benefits of a compiler-space implementation outweigh the risks. The existing -test suite will make sure that the behavior of operations is unchanged. This -second phase should probably be studied jointly with the developments of -ScalaNative. - -## Previous discussions and implementations - -When -[Value Classes (SIP-15)](https://docs.scala-lang.org/sips/value-classes.html) -were first introduced, the possibility to have new numeric types such as -unsigned integers was mentioned as a motivation. Subsequently, several people -[came up with implementations](https://groups.google.com/forum/#!topic/scala-sips/xtmUjsY9gTY) -of unsigned Ints and Longs. Those implementations were however more hacky -proofs of concept than a really thought-out proposal. - -Our proposal improves on those early attempts in several aspects: - -* Comprehensive but curated set of operations that are available on unsigned - integers, in particular no mixing signed and unsigned integers (avoid common - pitfalls found in other languages) -* Precise semantics for all operations (a specification) -* A meaningful notion of equality, which works well with other primitive types -* Use JDK 8 methods to implement operations that are specific to unsigned - integers, such as division -* Complete implementation with a test and benchmark suites - -## Out of scope - -The following related aspects are out of the scope of this proposal: - -### Literal notation for unsigned integers - -This proposal does not introduce any literal notation for unsigned integers. -Instead, we always convert from signed literals, e.g., `5.toUInt`. - -Providing literal notation should be done in the context of a SIP for -generalized user-defined literals. - -### Efficient arrays of unsigned integers - -We do not plan to address the issue of efficient arrays of unsigned integers. -Solving this should be part of a broader context for efficient arrays of -user-defined value classes in general, such as the encoding used in Dotty. - -## Unresolved questions - -* Should `>>` be available on unsigned integers? -* Should `null` unbox to 0 for unsigned integers even in their user-space - implementation? This would require some more changes to the compiler. - -## References - -1. [Implementation mostly in user-space for scalac/JVM](https://github.com/scala/scala/compare/2.12.x...sjrd:uints) diff --git a/_sips/sips/2016-09-09-inline-meta.md b/_sips/sips/2016-09-09-inline-meta.md deleted file mode 100644 index 8aa401d125..0000000000 --- a/_sips/sips/2016-09-09-inline-meta.md +++ /dev/null @@ -1,1012 +0,0 @@ ---- -layout: sip -title: SIP-28 and SIP-29 - Inline meta -vote-status: dormant -vote-text: This proposal needs an owner. -permalink: /sips/:title.html -redirect_from: /sips/pending/inline-meta.html ---- - -**By: Eugene Burmako, Sébastien Doeraene, Vojin Jovanovic, Martin Odersky, Dmitry Petrashko, Denys Shabalin** - -## History - -| Date | Version | -| ---------------|----------------------------------------------| -| Aug 22nd 2016 | Initial Pre-SIP | -| Sep 9th 2016 | Initial SIP | - -## Preface - -This document represents the culmination of a design process that spans several years. -We did our best to extensively evaluate the design space and comprehensively document our findings. - -As a result, this is going to be a very long read. If you're just interested in getting a quick idea -of the proposal, you can read just the ["Intuition"][Intuition] section. - -## Motivation - -Def macros and macro annotations have become an integral -part of the Scala ecosystem. They marry metaprogramming with types -and work in synergy with other language features, enabling [practically important use cases][MacroUsecases]. - -However, in addition to having the reputation of an indispensable tool, Scala macros have also gained notoriety -as an arcane and brittle technology. The most common criticisms of Scala macros concern their -subpar tool support and overcomplicated metaprogramming API powered by scala.reflect. - -While trying to fix these problems via evolutionary changes to the current macro system, -we have realized that they are all caused by the decision to use compiler internals as the underlying metaprogramming API. -Extensive use of desugarings and existence of multiple independent program representations may have worked well -for compiler development, but they turned out to be inadequate for a public API. - -The realization that we cannot make meaningful progress while staying within the confines of scala.reflect -meant that our macro system needs a redesign. We took the best part of the current macros - their smooth integration -into the language - and discarded the rest, replacing scala.reflect with a better metaprogramming API -and coming up with a lightweight declaration syntax. In this document, we present the current version of the design. - -## Table of contents - - * [Intuition](#intuition) - * [Def macros](#def-macros) - * [Discussion](#discussion) - * [Inline/meta](#inlinemeta) - * [Language features](#language-features) - * [Inline definitions](#inline-definitions) - * [Inline reduction](#inline-reduction) - * [Meta expressions](#meta-expressions) - * [Meta expansion](#meta-expansion) - * [Meta APIs](#meta-apis) - * [Design considerations](#design-considerations) - * [Scala.meta](#scalameta) - * [Tool support](#tool-support) - * [Losing whiteboxity](#losing-whiteboxity) - * [Losing compiler internals](#losing-compiler-internals) - * [Macro annotations](#macro-annotations) - * [Why inline](#why-inline) - * [Out of scope](#out-of-scope) - * [Conclusion](#conclusion) - * [Credits](#credits) - -## Intuition - -In this section, we will walk through writing and using compile-time metaprograms that implement a subset of functionality -expected of language-integrated queries. Without going into much detail, -we will obtain high-level intuition behind the underlying mechanisms - -[Language-integrated query][Linq] is a technique that achieves smooth integration of database queries with programming languages. -A common approach to LINQ, popularized by .NET Framework 3.5, consists in representing datasources -by collection-like library types and then allowing users to write queries as series of calls to these types -using familiar higher-order methods like `map`, `filter` and others. - -We will now develop a sketch of a database access library built in the spirit of LINQ. -In this sketch, we will intentionally forgo the practicalities of building a LINQ library -(e.g. mapping from classes to datasources, design of internal ASTs, error reporting, etc.) -in order to clearly illustrate the role played by compile-time metaprogramming. - -Below we define a trait `Query[T]` that encapsulates a query returning a collection -of objects of type `T`. Next to it, we define its children `Table` and `Map` that represent -particular types of queries. `Table` stands for a datasource that knows about the underlying type -(in this sketch, we use an imaginary typeclass `TypeInfo` for that purpose). -`Map` models a restricted subset of SQL SELECT statements via a simple `Node` AST. - -``` -trait Query[T] -case class Table[T: TypeInfo]() extends Query[T] -case class Select[T, U](q: Query[T], fn: Node[U]) extends Query[U] - -trait Node[T] -case class Ref[T](name: String) extends Node[T] - -object Database { - def execute[T](q: Query[T]): List[T] = { ... } -} -``` - -In this model, a SQL query string `"SELECT name FROM users"` is represented as `Select(Table[User](), Ref[String]("name"))`. -Queries like the one just mentioned can be run via `Database.execute` that translates them into SQL, sends the SQL to the database, -receives the response and finally translates it to data objects. For the sake of simplicity, we will assume such implementation as a given. - -The key aspect of a LINQ facility is a convenient notation for queries. -Arguably, none of the aforementioned ways to write queries can be called convenient. -SQL, as any string-based representation, is prone to syntax errors, type errors and injections. -Explicit instantiation of `Query` objects is very verbose, and still doesn't solve all type errors. - -In this sketch, we define a LINQ API in the form of methods that mirror the collection API -from the standard library. We would like our users to be able to encode queries in intuitively looking, -statically typed calls to this API, e.g. `users.map(u => u.name)`, and then have our library translate these -calls into calls to `Query` constructors. - -``` -object Query { - implicit class QueryApi[T](q: Query[T]) { - def map[U](fn: T => U): Query[U] = { ... } - } -} - -case class User(name: String) -val users = Table[User]() -users.map(u => u.name) -// translated to: Select(users, Ref[String]("name")) -``` - -Among other ways, the desired effect can be achieved with compile-time metaprogramming. -A metaprogram that runs at compile time can detect all calls to `Query.map` and rewrite them -to invocations of `Select` with parameters of `map` transformed to corresponding instances of `Node`. - -### Def macros - -Before looking into the design of the new macro system, let's see how the problem at hand -can be solved using the currently available macro system. - -``` -import scala.language.experimental.macros - -object Query { - implicit class QueryApi[T](q: Query[T]) { - def map[U](fn: T => U): Query[U] = macro QueryMacros.map - } -} -``` - -`QueryApi.map` is called a macro def. Since macros are experimental, -in order to define a macro def, it is required to either have `import scala.language.experimental.macros` -in the lexical scope of the definition or to enable the corresponding setting in compiler flags. -This is only necessary to define a macro def, not to use it. - -Macro defs look like normal methods in the sense that -they can have term parameters, type parameters and return types. -Just like regular methods, macro defs can be declared either inside or outside of classes, -can be monomorphic or polymorphic, and can participate in type inference and implicit search. -Refer to [Appendix A][AppendixInteraction] for a detailed account of differences -between macro defs and regular defs. - -Bodies of macro defs have an unusual syntax. The body of a macro def starts with the conditional keyword `macro` and -is followed by a possibly qualified identifier that refers to a macro impl, -an associated metaprogram run by the compiler when it encounters corresponding macro applications. -Macro impls are defined as shown below. - -``` -import scala.reflect.macros.blackbox.Context - -object QueryMacros { - def map(c: Context)(fn: c.Tree): c.Tree = { - import c.universe._ - ... - } -} -``` - -Macro impls take a compiler context that represents the entry point into the macro API. -The macro API consists of a general-purpose metaprogramming toolkit provided by scala.reflect -and several specialized facilities exclusive to macro expansion. -A typical first line of a macro impl is `import c.universe._` that makes the entire scala.reflect API available to the metaprogrammer. - -In addition to the compiler context, for every term parameter of a macro def, its macro impl -takes a term parameter that carries a representation of the corresponding argument of the macro application. -Macro impls can also get ahold of representation of type arguments, but this functionality is unnecessary for this example. - -A macro impl returns an abstract syntax tree, and this AST replaces the original macro application in the compilation pipeline. -This is how we're going to perform the LINQ translation of calls to `Query.map`. - -``` -import scala.reflect.macros.blackbox.Context - -object QueryMacros { - def map(c: Context)(fn: c.Tree): c.Tree = { - import c.universe._ - - // c.prefix looks like: - // Query.QueryApi[]() - val q"$_.$_[$_]($prefix)" = c.prefix - - val node: Tree = fn match { - case q"($param) => $body" => - val sym = param.symbol - body match { - case q"$qual.$_" if qual.symbol == sym => - q"Ref[${body.tpe}](${sym.name.decodedName.toString})" - } - } - - q"Select($prefix, $node)" - } -} -``` - -In the listing above, we handle several challenges of macro writing. -First, we get ahold of the prefix of the application, i.e. the `users` part of `users.map(u => u.name)`. -Unlike macro arguments, prefixes aren't mapped onto parameters of macro impls, -so we need to use the dedicated `c.prefix` API. - -The next challenge is to extract the prefix from the macro application. -Since `Query.map` is an extension method, the actual prefix is going to be `QueryApi(users)`, not `users`, -therefore we need to apply some non-trivial effort. - -One way of getting to the query is to access the corresponding field of the implicit class, -doing something like `QueryApi(users).q`. Unfortunately, this is out of the question, because `q` is private, -and we cannot make it public without adding an extension method called `q` to `Query`. - -Another way of achieving the desired result is to take apart the abstract syntax tree representing `QueryApi(users)`, -extracting `users` as the argument of the application. -It looks like quasiquotes, -which are supposed to provide a convenient WYSIWYG interface to deconstructing Scala code, -are going to be a perfect fit. - -Unfortunately for macro writers, the Scala compiler heavily desugars code during compilation, -so even the modest `QueryApi(users)` -will get to the macro impl in the form of `Query.QueryApi[User](users)`. -Therefore the naive `q"$_($query)"` quasiquote is not going to work, -and we need to apply additional effort. With a bit of knowledge about compiler internals, -we take care of this. - -The final challenge is code generation. -The pattern match in listing above transforms the user-provided lambda expression into an equivalent `Node`. -Since our sketch only supports `Ref`, we only support simple lambdas that select a field from a parameter. -Finally, we produce the macro expansion that has the desired shape. - -### Discussion - -Note how the ability of def macros to access types dramatically improves user experience in comparison with purely syntactic translation. -First, even before `Query.map` gets to expand, the compiler typechecks its argument, making sure that -queries are well-typed. Secondly, we have a way to reliably check the shape of the supported lambda. -The symbol comparison in the nested pattern match makes sure that -the qualifier of field selection refers precisely to the parameter of the lambda -and not to something else accidentally having the same name. Finally, we use information about the type of the body -in order to figure our the mandatory type parameter of `Ref`. - -Also note another piece of knowledge about compiler internals that was essential to robust operation of the macro. -When generating a `Ref`, we can't simply call `sym.name.toString`, because the Scala compiler internally mangles non-alphanumeric names. -If the parameter of the lambda has such a name, a simple `toString` will produce unsatisfying results, -which is why we have to call `Name.decodedName` first. - -Before we conclude, let's highlight a very common metaprogramming mistake that we've just made in `QueryMacros.map`. -Def macros are unhygienic, which means that they don't prevent inadvertent name capture, i.e. scenarios like the following. - -``` -val Select = "hijacked!" -users.map(u => u.name) - -// error: too many arguments for -// method apply: (index: Int)Char in class StringOps -// users.map(u => u.name) -// ^ -``` - -If the macro user accidentally defines a term called `Select` or `Ref`, our macro is going to stop working -with what looks like a nonsensical error message. Because principled tool support wasn't among the design goals of -our macro system, a macro user getting this error is mostly helpless apart from trying to use internal compiler options -that print all macro expansions and then going through the resulting wall of text. - -One reliable approach to prevent hygiene errors is to use fully-qualified names for external references and -to generate unique names for local variables. A somewhat more concise, but potentially much more laborious approach is to explicitly -assign symbols to references and definitions emitted in macro expansions. -This approach often requires extensive knowledge of compiler internals, so it is less frequent in the wild. -Common to both of these approaches is that they require explicit attention from metaprogrammers and -failures to apply them typically go unnoticed. - -To sum it up, even in this simple macro we encountered situations where knowledge of compiler internals -(the desugaring of the `QueryApi` application, the fact that non-alphanumeric names are encoded) was essential. -We also ran into problems with tool support and hygiene, which demonstrates how important they are to a macro system. -These are all common criticisms of def macros. - -### Inline/meta - -User interface of def macros is a seamless extension to the existing language interface. -Thanks to macro defs looking like regular methods and macro applications looking like regular method applications, -macro users are likely to not even realize that they are using macros. - -Unfortunately, metaprogrammer interface leaves much to be desired. First, in the current macro system, -metaprograms have to be defined separately from their signatures, typically involving helper objects and -duplication of parameters. Secondly, the underlying metaprogramming API requires -knowing bits and pieces of compiler internals. For example, in the case of `Query.map`, -the metaprogrammer had to know the internal representation of the `QueryApi` application -and internal details of how names are represented. - -New-style macros provide the same user interface, and at the same time significantly improve metaprogrammer -interface by enabling lightweight macro declaration syntax and using a better metaprogramming API. - -``` -object Query { - implicit class QueryApi[T](q: Query[T]) { - inline def map[U](fn: T => U): Query[U] = meta { - import scala.meta._ - - val q"$_($prefix)" = this - - val node: Tree = fn match { - case q"($name: $_) => $body" => - body match { - case q"$qual.$_" if qual =:= name => - q"Ref[${body.tpe}](${name.toString})" - } - } - - q"Select($prefix, $node)" - } - } -} -``` - -At a glance, new-style macros simply forgo a noticeable amount of ceremony, -merging the previously disparate macro defs and macro impls as well as swapping around a few keywords in the process. -The underlying metaprogram doesn't seem to have changed much, -still using quasiquotes and key APIs like `Tree.tpe` and `Name.toString`. - -First impression notwithstanding, the new design revamps a lot of underlying mechanisms. -Below we provide a high-level overview of our new approach to compile-time metaprogramming, -and refer curious readers to ["Language features"][LanguageFeatures] for information concerning the new expansion mechanism -and to ["Scala.meta"][ScalaMetaSection] for details about the new metaprogramming API. - -**Expansion mechanism**. The new design takes the notions of inlining and compile-time execution -from the current macro system and turns them into separate language features. - -The goal of the `inline` modifier on `Query.map` is to signify that applications of this method are inlined, -i.e. replaced with the method body, in which references to enclosing `this`, self, and formal parameters are rewritten accordingly -(see ["Inline reduction"][InlineExpansion] for details). - -The goal of the `meta` keyword is to demarcate code that executes at compile time and to provide -that code with [scala.meta capabilities][MetaApis]. -The metaprogram written inside the meta scope has access to multiple implicit capabilities -and can get ahold of representations of certain values and types from lexical scope. -The compiler runs this metaprogram, interprets its result as an abstract syntax tree -and replaces the meta expression with that tree (see ["Meta expansion"][MetaExpansion] for details). - -When the compiler encounters a call to `Query.map`, -it simply inlines the body of `map` into the callsite without performing any compile-time function execution. -For the running example of `users.map(u => u.name)`, -this inlining produces the result illustrated in the listing below. - -``` -{ - val prefix$1: QueryApi = QueryApi(users) - val fn$1: User => String = u => u.name - meta { - import scala.meta._ - - val q"$_($prefix)" = q"QueryApi(users)" - - val node: Tree = q"u => u.name" match { - case q"($name: $_) => $body" => - body match { - case q"$qual.$_" if qual =:= name => - q"Ref[${body.tpe}](${name.toString})" - } - } - - q"Select($prefix, $node)" - } -} -``` - -Before inlining a method application, the compiler first hoists the prefix and by-value arguments of the application -into temporary variables. This is done in order to guarantee that applications of inline methods -are semantically equivalent to applications of regular methods. - -Afterwards, the compiler replaces the application with a block that consists of hoisted values and the transformed method body. -In the method body, all regular references to `this` and self as well as -by-value parameters are rewritten to references to the corresponding temporary variables. -If such references are part of a meta scope, they are replaced with scala.meta-based representations of -the prefix and the corresponding arguments, without going into temporary variables. - -When the compiler comes across a meta expression that isn't part of an inline method, -it executes the code inside the expression and replaces the expression with the result of execution. -For our running example, this produces the desired expansion that invokes query constructors corresponding -to the LINQ notation. - -**New metaprogramming API**. Scala.meta noticeably improves on scala.reflect in the convenience of the API -and no longer requires metaprogrammers to understand compiler internals in order to write macros. - -In particular, we don't need to know that construction of the implicit class involves -expanding the reference to `QueryApi` into a fully-qualified name and inferring the missing type argument. -The particular implementation of the scala.meta API that runs the meta expression may or may not -do these desugarings, and scala.meta shields us from this fact. -We can use the WYSIWYG pattern `q"$_($prefix)"` in order to unwrap the original prefix of the call. - -Moreover, we don't have to worry about the compiler internally mangling non-alphanumeric names. -Again, even if the underlying macro engine internally does name mangling, scala.meta abstracts away such implementation details. - -Finally, we are able to improve on scala.reflect thanks to more precise quasiquotes. -If in the current macro system, we used `q"($name: $_) => ..."` to match the `fn` argument, -`name` would capture a scala.reflect `Name` that doesn't carry any semantic information. -In scala.meta, names are full-fledged trees, so we can use `Tree.=:=` to semantically compare the name -with the qualifier of the field selection. - -In order to use semantic APIs, scala.meta metaprograms need the [mirror capability][ScalaMetaSection], -so it is implicitly provided inside meta scopes. As a result, meta expressions can use the full spectrum -of APIs available in scala.meta. - -**To put it in a nutshell**, the new-style macro system combines two language features: -[inline definitions][InlineDefs] and [meta expressions][MetaExprs] -in order to provide a more lightweight syntax for the current def macros. Moreover, this macro system -does a switchover from scala.reflect to scala.meta, featuring a new API that is more convenient and -doesn't require compiler knowledge to be utilized effectively. - -## Language features - -### Inline definitions - -We introduce a new reserved word: `inline`, which can be used as a modifier for -concrete vals, concrete methods and parameters of inline methods, as illustrated in the listing below. - -``` -inline val x = 4 - -inline def square(x: Double) = x * x - -inline def pow(b: Double, inline n: Int): Double = { - if (n == 0) 1 - else b * pow(b, n - 1) -} -``` - -Inline vals are guaranteed to be compile-time constants, superseding the existing approach -of declaring such constants with `final val`. Inline parameters get the same treatment, adding a previously -non-existent functionality of guaranteeing that an argument of a method is a compile-time constant. - -A value is considered to be a compile-time constant if it is a Scala literal, -or it is equivalent to one by the means of inline/meta expansion and constant folding. -Future work may extend this notion to custom classes, but this discussion lies outside the scope of this proposal. - -Inline defs are guaranteed to be inlined at compile time, superseding the existing approach of -annotating methods with the `@inline` annotation. After introducing `inline`, we expect to deprecate and phase out `@inline`. - -The problem with `@inline` is that it doesn't provide guarantees. -As specified in documentation, -`@inline` tells the compiler to "try especially hard to inline the annotated method". -However, different backends interpret this request differently. -The JVM backend ignores this annotation if optimizations are disabled and sometimes skips inlining even when optimizations are enabled. -Both Scala.js and Scala Native always inline methods that are marked with this annotation. -In contrast, `inline` achieves guaranteed inlining, regardless of the backend. - -Inline defs are very similar to macro defs in the sense that they look like regular defs and that they expand at compile time. -As a result, the rules in [Appendix A][AppendixInteraction] also apply to inline defs with three exceptions. - - 1. Inline defs are effectively final; they cannot be overridden. Inline members also never override other members. -The idea of allowing macro defs to override regular defs didn't find compelling use cases, so we prohibit this for inline defs. - - 1. Inline defs can have default parameters. Not supporting default parameters for macro defs was an oversight -of the initial design, so now we fix this oversight in the new macro system. - - 1. Inline defs have regular bodies, just like regular defs. When an inline def is typechecked, its body -is typechecked according to the usual rules, which means that inline defs are eligible for return type inference. If an inline def -doesn't explicitly specify its result type, the result type gets inferred from the type of its body. - -### Inline reduction - -When the compiler encounters certain patterns of code that involve references to inline vals and applications of inline defs, -it will perform the rewritings provided below, if and only if these patterns appear outside the bodies of inline vals and defs. - - 1. If `prefix.v` refers to an inline value, replace the expression with the body of `v`. - - 1. If `prefix.f[Ts](args1)...(argsN)` refers to a fully applied inline method, hoist the prefix and the arguments -into temporary variables with fresh names and then replace the expression with the method body. In the body, replace parameter references -with references to temporary variables created for the corresponding arguments. Also, replace enclosing `this` -and self references with references to the temporary variable created for the prefix. - - ``` - { - val prefix$1 = prefix - val param1$1 = arg1 - ... - val paramN$1 = argN - - } - ``` - - The hoisting is intended to preserve the semantics of method applications under inlining. - A method call should have the same semantics with respect to side effects independently - on whether the method was made inline or not. - If an inline method has by-name parameters, then corresponding arguments are not hoisted. - - The rewriting is done in accordance with hygiene. Any references from the method body to its lexical scope - will be kept in the rewritten code. If the result of the rewriting references private or protected definitions - in the class that defines the inline method, these references will be changed to use accessors generated automatically by the compiler. - To ensure that the rewriting works in the separate compilation setting, - it is critical for the compiler to generate the accessors in advance. - Most of these accessors can be pregenerated by analyzing the bodies of inline methods, - except for members that are referred to inside meta scopes. - Such references are disallowed, because it is impossible to generate them in advance. - - 1. If `prefix.f[Ts](args1)...(argsN)` refers to a partially applied inline method, an error is raised. -Eta expansion of inline methods is prohibited. - -The rules of inline reduction are similar to the rules of feature interaction for macro applications ([Appendix A][AppendixInteraction]) -as well as relevant parts of the rules of def macro expansion ([Appendix B][AppendixExpansion]) with four exceptions. - - 1. Inline reductions in their current form preclude whitebox expansion. -Since bodies of inline vals and defs are typechecked when their definitions are typechecked, -potential meta expansions that may happen afterwards won't be able to change their types. -Implications of this are discussed in ["Losing whiteboxity"][Whiteboxity]. - - 1. By-value and by-name arguments behave differently. By-value arguments are hoisted in temporary variables, -while by-name arguments remain as they were. This doesn't affect meta expansions, -but it does make a semantic difference for parts of inline definitions that are not inside meta scopes. -Note that `this` and self references always follow the by-value scheme, -because there is no syntax in Scala that allows to define them as by-name. - - 1. Named and default arguments are fully supported. Again, not including them in the initial release of def macros -was an oversight, which is now fixed. - - 1. The rules of rewriting mandate the "outside in" style, i.e. calls to inline methods are expanded before possible calls -to inline methods in their prefixes and arguments. This is different from how the "inside out" style of def macros, -where prefixes and arguments are expanded first. Previously, it was very challenging to -take a look at unexpanded trees, but now the metaprogrammer can switch between unexpanded and expanded views using scala.meta. - -From the discussion above, we can see that inline reduction closely resembles the inlining aspect of the current macro system. -The only significant difference is lack of support for whitebox expansion, which will be discussed in ["Losing whiteboxity"][Whiteboxity]. - -### Meta expressions - -A meta expression is an expression of the form `meta { ... }`, where `{ ... }` is some block of Scala code, called meta scope. -(In fact, `meta` may prefix arbitrary expressions, but blocks are expected to be used most commonly). - -Meta expressions can appear both -in the bodies of inline methods (then their expansion is going to be deferred until the enclosing method expands) -and in normal code (in that case, their expansion will take place immediately at the place where the meta expression is written). - -Meta scopes can contain arbitrary code, but they must return values that are either of type `scala.meta.Term` or are convertible -to it via the `scala.meta.Lift` typeclass. There are standard instances of the typeclass that lift simple values to literals -as well as ones that support frequently used collections of liftable values. Metaprogrammers may define and use their own instances -as long as they are available in corresponding meta scopes. - -Inside meta scopes, metaprogrammers can use a combination of general-purpose and expansion-specific metaprogramming facilities. -Refer to ["Meta APIs"][MetaApis] for more information. - -Since meta scopes must return scala.meta trees, and scala.meta trees are by design statically untyped, -the type of meta expressions can't be computed from the inside -and has to come from the outside. Therefore, meta expressions can only be used in contexts that specify an expected type. - -``` -// allowed, expected type provided by the return type -inline def map[U](fn: T => U): Query[U] = meta { ... } - -// not allowed, no explicit return type -val x = meta { ... } - -// allowed, expected type of a condition is Boolean -if (meta(T.isPrimitive)) { ... } -``` - -Meta scopes can reference names in their lexical scope outside the enclosing meta expression. -While crossing the `meta` boundary, references change their meaning. -Concretely, here's how the transformation works for different references: - -**Inline vals and inline parameters**. These are guaranteed to be compile-time constants, -so an inline value of type `T` is available inside a meta scope as a regular value of type `T`. - -**Inline defs**. When viewed from within a meta scope, inline defs become tree transformers. -Types of their inline parameters are unchanged, their non-inline parameters and return type become typed as `scala.meta.Term`. -For example, `inline def pow(b: Double, inline n: Int): Double` transforms into -`def pow(b: scala.meta.Term, n: Int): scala.meta.Term`. - -**Term parameters of an inline method**. References to statically known term parameters, -enclosing `this` or self become values of type `scala.meta.Term`. This is similar to `c.Expr`/`c.Tree` -parameters of macro impls, but more lightweight, because there's no need -to declare these parameters explicitly. - -**Type parameters of an inline method**. References to statically known type parameters become values -of type `scala.meta.Type`. This is similar to `c.WeakTypeTag` parameters of macro impls, -but more lightweight, because metaprogrammers don't need to explicitly manifest their interest in given type parameters -in order to get access to their representations. - -This rule can create clashes between term and type namespaces. -If such a clash happens, i.e. if a reference to a type parameter is ambiguous with an eponymous term, -the compiler emits an error. This is regrettable, but Scala naming conventions make this situation unlikely, -and there is always a workaround of renaming the type parameter. - -**Global definitions**. Statically available types and terms, e.g. `List` or `Int`, -are usable inside meta scopes as themselves. This means that meta expressions, just like macro impls, -can use the standard library and arbitrary third-party libraries. - -**Other definitions**. Macro scopes cannot reference definitions that don't fall into one of the categories mentioned above. -This outlaws usages of local definitions - both terms and types. Such definitions may refer to state that only -exists at runtime, so we prohibit them altogether. This has no analogues in the current macro system, because macro impls -must be defined in static methods, which means that by definition their scope cannot contain local definitions. - -In other words, definitions that are statically available outside meta scopes remain available in meta scopes, -term and type arguments of inline methods become available as their representations, -while signatures of inline methods are recursively transformed according to the rules above. - -From the discussion above, we can see that meta expressions provide an analogue of the compile-time execution part -of the current macro system. In addition to achieving feature parity, meta expressions also improve on the corresponding part of def macros -by allowing metaprograms to easily obtain representations of their term and type parameters and -making it possible to run anonymous snippets of metaprogramming code without wrapping them in a dedicated macro. - -### Meta expansion - -When the compiler encounters a meta expression that appears outside the bodies of inline vals and defs, -it expands that expression as described below. - -A meta expression is expanded by evaluating its body and replacing the original meta expression with an expression -that represents the result of the evaluation. The compiler is responsible for providing [the capabilities][MetaApis] -necessary for meta scopes to evaluate and for converting between its internal representation for language model elements -and representations defined in scala.meta, such as `scala.meta.Term` and `scala.meta.Type`. - -Meta expansion works very similarly to the relevant part of the def macro expansion pipeline ([Appendix B][AppendixExpansion]). -Code in the meta scope is precompiled and then reflectively invoked by the compiler, sharing the class loader with other -metaprograms run inside the compiler. Expansion can return normally or can be interrupted with an exception. Expansion results -are typechecked against the expected type of the meta expression. Typecheck results are upcast to the expected type in blackbox style, -as discussed in ["Losing whiteboxity"][Whiteboxity]. - -Another practicality of meta expansion is the protocol of communication between `inline` and `meta`. -On the one hand, a reasonable default for inlining that doesn't involve meta expressions is to hoist prefixes and by-value arguments -as described in ["Inline reduction"][InlineExpansion]. On the other hand, macro writers default to having unfettered access to -abstract syntax trees without needing to write additional boilerplate. -These two design preferences are at odds with each other. - -Therefore, in our design `inline` both hoists eligible components of inline applications and passes their original -representations into meta expressions. This way, both defaults are satisfied and, additionally, if meta expressions need -to hoist something, they can use the new `hoist` API. - -In the case when an inline method consists in a single meta expression, the new macro engine removes temporary variables -that are produced by hoisting and aren't claimed by `hoist`. In the case when an inline method doesn't contain -meta expressions, all temporary variables are retained, because they are necessary to express method application semantics. -Finally, in the case of a hybrid inline method, meta expressions can look into representations of hoisted expressions, -but they cannot use them in their expansions without calling `hoist` first in order to avoid reordering or duplicating side effects. - -In a nutshell, meta expansion closely resembles the compile-time execution aspect of the current macro system. -The only nuance is the interaction with hoisting performed by the mechanism of inline reduction. - -### Meta APIs - -In scala.meta, all APIs are available out of the box after doing `import scala.meta._`. -However, in order to use most of them, metaprogrammers must have access to certain capabilities -(see ["Scala.meta"][ScalaMetaSection] for details). - -Meta scopes provide two different capabilities. First, there are general-purpose metaprogramming facilities, -enabled by a `Mirror`. Secondly, there are expansion-specific facilities enabled via a newly introduced `Expansion`. - -Mirrors are explained in ["Scala.meta"][ScalaMetaSection], and in this section -we will cover the functionality enabled by expansions. We will also compare this functionality -with macro APIs from the current macro system. - -First, `Context.prefix` is no longer necessary, -because meta expressions can refer to prefixes of enclosing inline definitions via `this`. -`Context.macroApplication` is unnecessary as well, because meta expressions may be written outside inline vals and defs, -which means that they won't have a corresponding application at all. In the rare cases when it is useful to know -the position that spans the entire inline application, it can be obtained by traversing prefixes or arguments via `Tree.parent`. - -Secondly, much like their counterparts in the current macro system, meta APIs support diagnostic messages. -Since the only prevalent macro APIs in this group is `Context.abort`, with `Context.error`, `Context.warning` -and others seeing rare use, we only expose `abort` that works similarly to its predecessor. - -Thirdly, we no longer expose APIs that tightly integrate with compiler internals. Most of these APIs take care -of the limitations of the scala.reflect language model, so in the new metaprogramming framework based on scala.meta -they are simply unnecessary. Others feature advanced functionality that accesses or manipulates internal compiler state, -and this exactly the kind of thing that we would like to avoid in the macro system. We discuss the implications in -["Losing compiler internals"][CompilerInternals]. - -Finally, we also support `hoist`, which takes a `scala.meta.Term`, precomputes it in a temporary variable -outside the meta expansion and returns a reference to it. -Apart from `inline`-compatible precomputation of prefixes and arguments, -this functionality seems relevant to solving the problem of sharing expansions of [implicit materializers][Materialization], -so we are confident that it's a useful addition to the macro API. - -## Design considerations - -### Scala.meta - -Scala.meta is a clean-room implementation of a metaprogramming toolkit for Scala, -designed to be simple, robust and portable. We are striving for scala.meta to become a successor of scala.reflect, -the current de facto standard in the Scala ecosystem. - -We have recently released Scala.meta 1.0.0 that features support for syntactic APIs, such as parsing, tokenization, -quasiquotes and prettyprinting. We are currently working on Scala.meta v2 that will enable semantic APIs, such as -typechecking, name resolution and others. - -In [Appendix C][AppendixMeta], we provide a high-level overview of the architecture of scala.meta -along with several practical examples that illustrate its functionality. - -### Tool support - -The key innovation of the new macro system is the fact that it's based on scala.meta, which finally makes it feasible to -provide third-party implementations of mirrors. - -There now exists a prototype implementation of an IntelliJ mirror, and, in further effort of Mikhail Mutcianko from the IntelliJ team, -this mirror has become the foundation for [an interactive expander of new-style macro annotations][PrototypeIntellij]. -This recent advancement strongly suggests that it was the right choice to bet on scala.meta to fix -code compehension and error reporting issues with the current macro system. - -This takes some pressure off whitebox macros. In the current macro system, there is a preference towards blackbox macros, -because they are much friendlier to IntelliJ. Once we have a fully-working mirror for IntelliJ, user experience of blackbox -and whitebox macros should be equivalent. For additional discussion of whiteboxity from a language design and compiler development -perspective, refer to ["Losing whiteboxity"][Whiteboxity]. - -Improvements in support for incremental compilation, testing and debugging also hinge on a capability of scala.meta -to enable custom mirror implementations. -However, they also require significant new functionality to be developed in the corresponding tools -([additional dependency tracking mechanisms for the incremental compiler][SbtMacroSupport] and changes to the vanilla debugger in IDEs). - -### Losing whiteboxity - -It is desirable for the new design to provide reasonable feature parity with the -current macro system. So far, the biggest digression from this course is giving up whitebox expansion. -In this section, we discuss what it will cost us to follow this through, identify the aspects of the new design that -prevent whiteboxity and propose alternatives. - -The main motivation for getting rid of whitebox expansion is simplification - both of the macro expansion pipeline -and the typechecker. Currently, they are inseparably intertwined, complicating both compiler evolution and tool support. -Therefore, the new design tries to disentangle these systems. - -Let's recapitulate the limitations that def macro expansion applies to applications of blackbox macros, -outlining the most important use cases that blackboxity cannot express. This won't give us the full picture, -because there are many macros in the wild that we haven't classified, but nonetheless it will provide an understanding -of the significant chunk of the Scala macros ecosystem. - - 1. When an application of a blackbox macro expands into a tree `x`, the expansion is wrapped into a type ascription `(x: T)`, -where `T` is the declared return type of the blackbox macro def with type arguments and path dependencies applied in consistency -with the particular macro application being expanded. -This invalidates blackbox macros as an implementation vehicle for [anonymous type providers][AnonymousTypeProviders]. - - 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, -precluding some advanced aspects of [materialization][Materialization]. - - 1. When an application of a blackbox macro still has undetermined type parameters after the Scala type inference algorithm has finished -working, these type parameters are inferred forcibly, 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][Materialization]. - - 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 implemented with macros][ExtractorMacros]. -This precludes blackbox macros from providing precise types to values extracted from patterns written in external DSLs, -preventing a library-based implementation of quasiquotes. - -As we can see, whitebox expansion plays an important role in several use cases, the most practically significant -of them being fundep materialization and quasiquote patterns. Fundep materialization is paramount to the design of Shapeless. -Quasiquote patterns are an integral part of writing any kinds of macros - both blackbox and whitebox - which is the main -reason to use scala.reflect. - -It is now clear that our desire for simplification is at odds with the ways how the Scala community uses macros. -In order to resolve the apparent conflict, we outline several solutions, whose evaluation we leave for later. - -**Give up on simplification**. This is probably the most obvious approach, in which we admit -that tight coupling with the typechecker is intrinsic to the essence of Scala macros and change the new design -to enable whitebox expansion. - -Concretely, accommodating whiteboxity in the new macro system requires changing the aspects of the new design -specified in ["Inline reduction"][InlineExpansion] and ["Meta expansion"][MetaExpansion] according to the -plan provided below. - - * Force inline reductions and meta expansions inside the typechecker. Currently, both the rules of inline reduction -and the rules of meta expansion are intentionally vague about the exact point in the compilation pipeline that does expansions, -but whiteboxity will leave us no room for that. - * Delay typechecking of the bodies of inline vals and defs until their expansion. Meta expressions inside inline definitions -are not expanded, which means that eager typechecking of these definitions precludes whitebox expansion. - * Allow using meta expressions without expected type. This captures the main idea of whitebox expansion, in which compile-time -metaprogramming has the final say about the type of transformed snippets of code. - -**Assimilate the most popular use cases**. Instead of supporting the general notion of whiteboxity, -we can introduce dedicated compiler support for the most popular uses cases including the `Generic` -mechanism in Shapeless and quasiquote patterns in scala.reflect. - -This is an attempt at a compromise. On the one hand, this approach allows us to follow through with simplification. -On the other hand, it significantly minimizes the impact on the existing users of whitebox macros. - -The downside of this solution is that it requires an extensive design process (because it involves adding new language features to Scala) -and assumes that the internalized techniques have reached their final form. If a new version of Shapeless -or a new version of scala.reflect (read: scala.meta) decide to adapt their designs after these designs have been assimilated, -they will have a very hard time doing that. - -**Dedicated support for type-level computations**. Manifestations of whiteboxity are quite diverse, -but a lot of them are reducible to type-level computations. - -For example, let's take a macro `join` that takes two objects and outputs an object that -has fields of both. Since Scala doesn't have row polymorphism, it is impossible to write a type signature -for this macro, so we have to declare it as whitebox. - -``` -scala> def join(x: Any, y: Any): Any = macro ... -defined term macro join: (x: Any, y: Any)Any - -scala> join(new { val x = 2 }, new { val y = 3 }) -res0: AnyRef{val x: Int; val y: Int} = $anon$1@64af328d -``` - -Here we can see how the whitebox macro encapsulates a type-level computation that takes -the types of both arguments (`AnyRef{ val x: Int }` and `AnyRef{ val y: Int }`) -and merges them into the result type. Since this computation doesn't involve the abstract syntax trees -of the arguments, the whitebox part of the macro can be extracted into a helper, making the macro itself -blackbox. - -``` -scala> :paste -// Entering paste mode (ctrl-D to finish) - -trait Join[T, U, V] -object Join { - implicit def materialize[T, U, V]: Join[T, U, V] = macro ... -} - -// Exiting paste mode, now interpreting. - -defined trait Join -defined object Join - -scala> def join[T, U, V](x: T, y: U) - | (implicit ev: Join[T, U, V]): V = macro ... -defined term macro join: [T, U, V](x: T, y: U)... -``` - -This approach can express some macros that refine their return type, all fundep materialization macros, -and even some macros that dynamically compute availability of implicits (such macros can be modified to take -an additional implicit parameter whose failure to materialize can be used to control availability of the enclosing implicit) - -that is, all macros whose whiteboxity depends only on types of their prefix and arguments. - -Now, after eligible whitebox macros are rewritten this way, we can replace the whitebox materializers -that compute types, e.g. `Join.materialize`, with a dedicated language feature that natively expresses them. -The listing below provides a sketch of an imaginary syntax that assumes inline types and type-generating meta expressions. - -``` -inline type Join[T, U] = meta { - import scala.meta._ - val jointVals = union(T, U) - t"{ ..$jointVals }" -} - -inline def join[T, U](x: T, y: U): Join[T, U] = meta { ... } -``` - -From the discussion above, we can see that whiteboxity is an important feature of the current macro system -and is used in multiple highly popular open-source libraries. As a result, giving up whiteboxity -may lead to undesired practical consequences. - -More work is needed to reconcile the design of the new macro system -and existing use cases, and in this section we provided several approaches to addressing this need. In the opinion of the author, -the approach that involves dedicated support to type-level computations is the most promising, because it both simplifies -the macro system and provides support for the most important use cases of whiteboxity. - -### Losing compiler internals - -One of the main goals of this proposal is to stop exposing overcomplicated and brittle compiler internal APIs -in order to make macros more accessible and more robust. - -However, some of the compiler APIs may actually capture useful idioms that we may be able to expose in a principled fashion. -`c.internal.enclosingOwner` immediately comes to mind here. - -More work in collaboration with macro authors is needed to make sure that we don't unnecessarily break existing macros -in the name of elusive conceptual purity. - -### Macro annotations - -Much like the current macro system can get extended to support macro annotations, -the new macro system can get extended to support new-style macro annotations that provide similar functionality. - -``` -import scala.annotation.StaticAnnotation - -class h2db(url: String) extends StaticAnnotation { - inline def apply(defns: Any): Any = meta { - ... - } -} - -object Test { - def main(args: Array[String]): Unit = { - @h2db("coffees") object Db - val brazilian = Db.Coffees.insert("Brazilian", 99.0) - Db.Coffees.update(brazilian.copy(price = 100.0)) - println(Db.Coffees.all) - } -} -``` - -In the current macro system, a macro annotation is a subclass of `StaticAnnotation` that define -a macro def called `macroTransform`. - -Analogously, in the new design, a macro annotation is a subclass of `StaticAnnotation` -that defines an inline def called `apply`. We decided to change the magic name, because the old one no longer applies. -We also simplified the signature to take a block wrapping the definitions being expanded and returns a block wrapping expansion results, -i.e. `Any => Any`, as opposed to having `Any* => Any` in the current system. - -Expansion of new-style macro annotations is very similar to expansion of vanilla macro annotations. -The only difference is that we only provide syntactic APIs, informed about [the limitations][AnnotationsTypecheck] -brought by exposing semantic APIs in macros that generate top-level definitions. -Concretely, meta scopes in macro annotations expose a `Dialect` capability instead of a `Mirror`. -As a result, we can afford to expand on enter without any limitations to the shape of expansion. - -### Why inline - -The main motivation behind inline is to provide a templating system that can express simple use cases -of compile-time metaprogramming in an lightweight declarative style that minimizes explicit introspection. - -For example, in one of the code snippets that illustrates inline defs and inline parameters, we can use the newly -introduced mechanism to express partial evaluation. Thanks to constant folding facilities built into the compiler, -usages of the `pow` method provided below will expand into a sequence of multiplications - all that without -a single line of macro code. - -``` -inline def pow(b: Double, inline n: Int): Double = { - if (n == 0) 1 - else b * pow(b, n - 1) -} -``` - -### Out of scope - -This proposal addresses a lot of pain points of the current macro system, but it doesn't talk about two very common -problems: lack of hygiene and presence of the separate compilation restriction. - -These two problems have a lot in common: we have experimented with both of them, our initial results are promising but insufficient, -both of them can be developed independently of inline/meta. - -Given that both hygiene and joint compilation still require significant research to materialize, -we decided not to include them in this proposal. We think that even without these features, inline/meta represents a significant -improvement over the state of the art, so we leave them for future work that may be submitted in follow-up SIPs when it's done. - -## Conclusion - -Pioneered by def macros and macro annotations, -the area of language-integrated compile-time metaprogramming in Scala has shown significant practical value. - -During the last several years, we have been utilizing and maintaining the current macro system in industrial projects. -Learning from this experience, we have created a new design based on `inline` and `meta` that provides -comparable functionality and avoids the most common pitfalls of existing macros. - -The user interface of the new system is a conservative evolution of def macros. -[Inline defs][InlineDefs] work similarly to macro defs, and -[meta expressions][MetaExprs] play the compile-time execution role of macro impls. -These new language features are designed to be used together, -and in this capacity their look and feel can hardly be distinguished from that of def macros. - -The metaprogrammer interface of the new system is a drastic redesign. We have merged macro defs and macro impls, -and, more importantly, we have switched the underlying metaprogramming API from scala.reflect to [scala.meta][ScalaMetaSection]. -This has allowed us to make significant improvements in robustness and tool support. - -The main open question of the new design is [whether whiteboxity will stay or will be removed][Whiteboxity] from the new macro system. -We also need to figure out [which internal compiler APIs we may want to approximate][CompilerInternals] in the new macro system. -Future work outside the scope of this proposal includes -hygiene and lifting the separate compilation restriction. - -At the time of writing, `inline` and `meta` are partially implemented in [a prototype compiler plugin for Scala 2.11][PrototypeScalac] -and [an accompanying experimental branch of IntelliJ][PrototypeIntellij]. -We are excited with our initial results and are planning to develop the new design further. - -## Credits - -This design was created by Eugene Burmako, Denys Shabalin and Martin Odersky -and was further elaborated together with other members of the inline working group at EPFL: -Sébastien Doeraene, Vojin Jovanovic and Dmitry Petrashko. - -Over time, the ideas behind the design were refined based on experiments carried out by: -Uladzimir Abramchuk, Igor Bogomolov, Mathieu Demarne, Martin Duhem, Adrien Ghosn, Zhivka Gucevska, -Mikhail Mutcianko, Dmitry Naydanov, Artem Nikiforov, Vladimir Nikolaev, Jatin Puri and Valentin Rutz. - -The prototype of scalac integration was developed by Eugene Burmako with open-source contributions from -Tamer Mohammed Abdul-Radi, David Dudson, Takeshi D. Itoh, Oleksandr Olgashko and Hiroshi Yamaguchi. - -The prototype of IntelliJ integration was developed by Mikhail Mutcianko. - -## References - - 1. [Prototype of scalac integration][PrototypeScalac] - 1. [Prototype of IntelliJ integration][PrototypeIntellij] - 1. [(Appendix A) Def macros: feature interaction][AppendixInteraction] - 1. [(Appendix B) Def macros: macro expansion][AppendixExpansion] - 1. [(Appendix C) Scala.meta: high-level overview][AppendixMeta] - -[ScalaMetaSection]: #scalameta -[ScalaMetaWebsite]: https://scalameta.org/ -[MacroUsecases]: https://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf -[PrototypeScalac]: https://github.com/scalameta/paradise -[PrototypeIntellij]: https://www.youtube.com/watch?v=IPnd_SZJ1nM&feature=youtu.be&t=1360 -[Intuition]: #intuition -[LanguageFeatures]: #language-features -[InlineDefs]: #inline-definitions -[InlineExpansion]: #inline-reduction -[MetaExprs]: #meta-expressions -[MetaExpansion]: #meta-expansion -[MetaApis]: #meta-apis -[Whiteboxity]: #losing-whiteboxity -[CompilerInternals]: #losing-compiler-internals -[Hygiene]: #hygiene -[SeparateCompilation]: #separate-compilation-restriction -[Linq]: https://dl.acm.org/citation.cfm?id=1142552 -[Materialization]: https://docs.scala-lang.org/overviews/macros/implicits.html -[SbtMacroSupport]: https://github.com/sbt/sbt/issues/1729 -[AnonymousTypeProviders]: https://docs.scala-lang.org/overviews/macros/typeproviders.html#anonymous-type-providers -[ExtractorMacros]: https://docs.scala-lang.org/overviews/macros/extractors.html -[AnnotationsTypecheck]: https://github.com/scalamacros/paradise/issues/75 -[AppendixInteraction]: https://gist.github.com/xeno-by/e26a904051a171e4bc8b9096630220a7 -[AppendixExpansion]: https://gist.github.com/xeno-by/5dde62aedcc23afc85ecf4d795ac67c2 -[AppendixMeta]: https://gist.github.com/xeno-by/9741ce7532cb30368b3753521bbfce4e diff --git a/_sips/sips/2017-01-11-refer-other-arguments-in-args.md b/_sips/sips/2017-01-11-refer-other-arguments-in-args.md deleted file mode 100644 index 6d5097038a..0000000000 --- a/_sips/sips/2017-01-11-refer-other-arguments-in-args.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -layout: sip -title: SIP-32 - Allow referring to other arguments in default parameters -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/refer-other-arguments-in-args.html ---- - -**By: Pathikrit Bhowmick** - -## History - -| Date | Version | -|---------------|------------------| -| Jan 11th 2017 | Initial Draft | -| Jan 12th 2017 | Initial Feedback | -| Jan 16th 2017 | Minor Changes | - -## Introduction -Currently there is no way to refer to other arguments in the default parameters list: - -Does not compile: -```scala -def substring(s: String, start: Int = 0, end: Int = s.length): String -``` - -The workaround to achieve this is by using a curried-function: -```scala -def substring(s: String, start: Int = 0)(end: Int = s.length): String -``` - -However, the above workaround is not always suitable in all situations since you may not want a curried function. - -The other more verbose alternative is by overloading: -```scala -def substring(s: String, start: Int): String - = substring(s, start = 0, end = s.length) -def substring(s: String, start: Int = 0, end: Int): String -``` - -The above is quite verbose as it required 1 extra function definition per argument that refers other args. - -### Proposal -Allow to refer to ***any*** parameters in the same (or left) curried parameter list: -```scala -def substring(s: String, start: Int = 0, end: Int = s.length) // Legal -def substring(start: Int = 0, end: Int = s.length, s: String) // Legal !!! -def substring(s: String, start: Int = 0)(end: Int = s.length) // Legal (works currently) -def substring(start: Int = 0, end: Int = s.length)(s: String) // Illegal -``` - -The same applies for class arguments: -```scala -class Substring(s: String, start: Int = 0, end: Int = s.length) // Legal -class Substring(start: Int = 0, end: Int = s.length, s: String) // Legal -class Substring(s: String, start: Int = 0)(end: Int = s.length) // Legal -class Substring(start: Int = 0, end: Int = s.length)(s: String) // Illegal -``` - -We should also be able to refer to ***multiple*** parameters: -```scala -def binarySearch(start: Int, end: Int, middle: Int = (start + end)/2) // Legal -``` - -# Motivating examples: - -TBD - -## Interactions with other syntax - -#### Partially Applied Functions: -Works as expected: -```scala -def substring(s: String, start: Int = 0, end: Int = s.length) - -substring(_, start = 0, end = 5) // Legal -substring(_, end = 5) // Legal (start = 0) -substring(_, start = 5) // Legal (same as s => substring(s, start = 5, end = s.length) -``` - -#### Multiple Implicit Parameters -[Multiple implicit parameters](https://github.com/scala/docs.scala-lang/pull/520) should also be allowed to refer to one another (left to right): -```scala -def codec[A](data: A)(implicit encoder: Encoder[A])(implicit decoder: Decoder[A] = encoder.reverse) // Legal -``` - -#### Referring to type members: -The default parameters should be able to refer to type members of other arguments e.g.: -```scala -trait Codec { - type Input - type Output -} - -def codec(codec: Codec, in: codec.Input, out: codec.Output) // Legal !!! -``` - -### Other languages -The following languages allow referring to previously declared arguments in the function signature: -* [CoffeeScript](https://coffeescript.org/) -* [Kotlin](https://kotlinlang.org) -* [TypeScript](https://www.typescriptlang.org/) - -AFAIK, there are no major languages where referring to parameters declared to the ***right*** is allowed. - -### Discussions -[Scala Lang Forum](https://contributors.scala-lang.org/t/refer-to-previous-argument-in-default-argument-list/215/6) diff --git a/_sips/sips/2017-01-13-binary-compatibility.md b/_sips/sips/2017-01-13-binary-compatibility.md deleted file mode 100644 index 96be472776..0000000000 --- a/_sips/sips/2017-01-13-binary-compatibility.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -layout: sip -title: SIP-34 - Improving binary compatibility with @stableABI -vote-status: dormant -vote-text: When 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. -permalink: /sips/:title.html -redirect_from: /sips/pending/binary-compatibility.html ---- - -__Dmitry Petrashko__ - -__first submitted 13 January 2017__ - -## Introduction - -Scala is a language which evolves fast and thus made a decision to only promise binary compatibility across minor releases\[[3]\]. -At the same time, there is a demand to develop APIs that live longer than a major release cycle of Scala. -This SIP introduces an annotation `@stableABI` that checks that `what you write is what you get`. - -`@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. -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]\]: - - > 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. - - 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. - 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 - 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 - 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`. - 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, - meaning that those two classes can be substituted in runtime without linkage errors. - -## Use cases - -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 - 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, -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. - - -## 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, -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. - -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. - - 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. -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. -This is because we will be able to allow more features later, but we won't have an opportunity to remove them. - -## Overview ## -In order for a class, trait or an object to succeed compilation with the `@stableABI` annotation it has to be: - - - defined on the top level; - - if a class or an object has a companion annotated with `@stableABI`, than annotation applies to both of them; - - use a subset of Scala that during compilation does not require changes to public API of the class, including - - synthesizing new members, either concrete or abstract; - - 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. - -Below are several examples of classes and traits that succeed compilation with `@stableABI` - -{% highlight scala %} -@stableABI -trait AbstractFile { - def name(): String - - def path(): String - - def jfile(): Optional[File] -} - -@stableABI -trait SourceFile extends AbstractFile { - def content(): Array[Char] -} - -@stableABI -trait Diagnostic { - def message(): String - - def level(): Int - - def position(): Optional[SourcePosition] -} - -@stableABI -object Diagnostic { - @static final val ERROR: Int = 2 - @static final val WARNING: Int = 1 - @static final val INFO: Int = 0 -} - -@stableABI -class FeaturesInBodies { - def apiMethod: Int = { - // as body of the method isn't part of the public interface, one can use all features of Scala here. - lazy val result = 0 // while lazy vals are prohibited in the class, they are allowed in the bodies of methods - result - } -} -{% endhighlight %} - -## 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-implementation -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. - - - public fields. Can be simulated by explicitly defining public getters and setters that access a private field; - - lazy vals. Can be simulated by explicitly writing an implementation in source; - - case classes. Can be simulated by explicitly defining getters and other members synthesized for a case class(`copy`, `productArity`, `apply`, `unapply`, etc). - -The features listed below cannot be easily re-implemented in a class or trait annotated with `@stableABI`. - - - default arguments; - - default methods. See Addendum; - - constant types(both explicit and inferred); - - inline. - -## Binary compatibility and transitivity ## -Consider a class, that is binary compatible but takes a non-binary compatible argument: - -{% highlight scala %} -@stableABI -class Example { - def foo[T](a: MyOption[T]): T = a.get -} - -trait MyOption[T]{ - lazy val get: T = ??? -} -{% endhighlight %} - - -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. - -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`, -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. -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, -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 -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, -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 differently 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. -`@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; - - 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, -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, -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, -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. -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 -`@stableABI` traits. - -## See Also ## - - 1. [dotty#1900][1] - 2. [MiMa][2] - 3. [releases-compatibility][3] - 4. [Descriptor definition in JVM Specification][4] - 5. [JVM access flags][5] - 6. [Scala AccessModifiers][6] - 7. [Dotty interfaces][7] - 8. [Zinc interfaces][8] - - -[1]: https://github.com/lampepfl/dotty/pull/1900 "an implementation for Dotty" -[2]: https://github.com/typesafehub/migration-manager "MiMa" -[3]: https://docs.scala-lang.org/overviews/core/binary-compatibility-of-scala-releases.html "Binary compatibility of Scala releases" -[4]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3 "Descriptor definition in JVM Specification" -[5]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6-200-A.1 "JVM access flags" -[6]: https://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#modifiers "Scala AccessModifiers" -[7]: https://github.com/lampepfl/dotty/tree/master/interfaces/src/dotty/tools/dotc/interfaces "Dotty interfaces" -[8]: https://github.com/sbt/zinc/tree/v1.0.0/internal/compiler-interface/src/main/java/xsbti "zinc interfaces" - diff --git a/_sips/sips/2017-02-22-comonadic-comprehensions.md b/_sips/sips/2017-02-22-comonadic-comprehensions.md deleted file mode 100644 index 3e29121f79..0000000000 --- a/_sips/sips/2017-02-22-comonadic-comprehensions.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -layout: sip -title: SIP-NN - comonadic-comprehensions -vote-status: rejected -permalink: /sips/:title.html -redirect_from: /sips/pending/comonadic-comprehensions.html ---- - -**By: Shimi Bandiel** - -## History - -| Date | Version | -|---------------|---------------| -| Feb 22nd 2017 | Initial Draft | - -## Motivation - -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): -the cofor comprehension. - -The proposal has an existing implementation in PR 5725 - -## Motivating Examples - -### Examples - -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] = - StreamZipper(left.map(f), f(focus), right.map(f)) - def extract: A = - focus - def coflatMap(f: StreamZipper[A] => B): StreamZipper[B] = - ??? -} - -{% endhighlight %} - -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 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. -The result is a StreamZipper[B] of the results with a cursor pointing at the same location as this. -
  • -
- -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 = - 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 - -{% 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 %} - -We would like to get the following: -{% highlight scala %} - - /* - * A StreamZipper of triplets containing: - * _1 -- the original Person value. - * _2 -- whether this Person's age is higher than the previous and lower than the next. - * We'll call this boolean TAG. - * _3 -- how many consecutive TAGs with value "true" starting from current cursor. - */ - val goal: StreamZipper[(Person, Boolean, Int)] = ??? - -{% endhighlight %} - -It seems we can re-use the isInTheMiddle and numberOfTrues methods. -However, without the proposed cofor syntax we'll probably end with: -{% highlight scala %} - val goal = people.map(p => (p, p.age)).coflatMap { zipperOfTuple => - val ages = zipperOfTuple.map(_._2) - (zipperOfTuple.extract._1, isInTheMiddle(ages)) - }.coflatMap { zipperOfTuple => - val tags = zipperOfTuple.map(_._2) - val persons = zipperOfTuple.map(_._1) - val trues = numberOfTrues(tags) - persons.extract, tags.extract, trues) - } -{% endhighlight %} -From the code above, you can see that it is quite cumbersome to handle the passing of -the context between the invocations of coflatMap. - -The proposed syntax allows for the following usage: -{% highlight scala %} - 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 %} - - -## Syntax - -The proposed syntax is based on the paper by Dominic Orchard and Alan Mycroft [1]. - -The syntax for `cofor` is defined as: -{% highlight scala %} - cofor (pattern0) { - pattern1 <- generator1 - pattern2 <- generator2 - ... - } yield body - - patternN = regular case patterns - generatorN = expr - body = expr - -{% endhighlight %} - -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. - -## Desugaring - -A `cofor` desugaring is much more complex than the respective `for`. - -Desugaring example: - -{% highlight scala %} - 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 %} - -The above `cofor` expression will be desugared into the following function: -{% highlight scala %} - input => { - // desugaring the generators - val enums = - // assign values to input variables - // actual assignment is done through pattern matching - input.map(p => ( - p match { - case p @ Person(_, age) => p - } - , (p match { - case p @ Person(_, age) => age - }, ()))). - coflatMap(env => { - // extracting collected values from the context - val p = env.map(env => env._1) - val age = env.map(env => env._2._1) - // now we pass the current context and the generator result - (isInTheMiddle(age), env.extract) - }).coflatMap(env => { - // extracting collected values from the context - val tag = env.map(env => env._1) - val p = env.map(env => env._2._1) - val age = env.map(env => env._2._2._1) - // now we pass the current context and the generator result - (numberOfTrues(tag), env.extract) - }) - // the body phase (yield) - { - // deconstructing the collected context - val count = enums.map(env => env._1) - val tag = enums.map(env => env._2._1) - val p = enums.map(env => env._2._2._1) - val age = enums.map(env => env._2._2._2._1) - (p.extract, tag.extract, count.extract) - } - } -{% endhighlight %} - -## Drawbacks - -
    -
  1. Adding a new keyword to the language makes it more complex
  2. -
  3. Understanding the desugaring and concept behind cofor is not -trivial and it's much more complex than for (which many developers still -don't feel at ease with).
  4. -
- - -## References - -1. [A Notation for Comonads][1] -2. [Implementation Pull-Request][2] -3. [StreamZipper Example][3] - -[1]: https://www.cs.kent.ac.uk/people/staff/dao7/publ/codo-notation-orchard-ifl12.pdf "codo-notation" -[2]: https://github.com/scala/scala/pull/5725 -[3]: https://github.com/shimib/scala/blob/5e257cd4b371769deafba2be1ae3932d772ca67d/test/files/neg/cofor.scala diff --git a/_sips/sips/2017-10-25-adding-prefix-types.md b/_sips/sips/2017-10-25-adding-prefix-types.md deleted file mode 100644 index 9ef94f2427..0000000000 --- a/_sips/sips/2017-10-25-adding-prefix-types.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -layout: sip -title: SIP-36 - Adding prefix types -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/adding-prefix-types.html ---- - -**By: Oron Port** - -## History - -| Date | Version | -| ------------- | ---------------------------------------- | -| Oct 25th 2017 | Split prefix types from [SIP33](https://docs.scala-lang.org/sips/priority-based-infix-type-precedence.html), and emphasize motivation | -| Nov 29th 2017 | Updated SIP according to feedback in the PR, and recent update to SIP23 | -| Dec 1st 2017 | Added another use-case for prefix type `~` | - -Your feedback is welcome! If you're interested in discussing this proposal, head over to [this](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471) Scala Contributors thread and let me know what you think. - ---- - -## Introduction -Currently scala supports unary prefix operators (`-`, `+`, `~`, `!`) for expressions (e.g., `def unary_-`) and does not support prefix types. See the Scala specification [Prefix Operations](https://scala-lang.org/files/archive/spec/2.12/06-expressions.html#prefix-operations) section. - -**Prefix expression vs. prefix type example**: - -```scala -object PrefixExpression { - case class Nummy(expand : String) { - def unary_- : Nummy = Nummy(s"-$this") - def unary_~ : Nummy = Nummy(s"~$this") - def unary_! : Nummy = Nummy(s"!$this") - def unary_+ : Nummy = Nummy(s"+$this") - } - object N extends Nummy("N") - val n1 = -N - val n2 = ~N - val n3 = !N - val n4 = +N -} -object NonExistingPrefixTypes { - trait unary_-[A] - trait unary_~[A] - trait unary_![A] - trait unary_+[A] - trait N - type N1 = -N //Not working - type N2 = ~N //Not working - type N3 = !N //Not working - type N4 = +N //Not working -} -``` - ---- - -## Motivation -It is easier to reason about the language when mathematical and logical operations for both terms and types are expressed the same. The proposal is relevant solely for projects which utilize numeric literal type operations (supported by SIP23, which was not yet accepted into Lightbend Scala). However, the SIP's implementation is very small and should have minor effect on compiler performance. - -### Motivating examples - -#### Splice prefix types for meta-programming -A requirement for `unary_~` is described by Martin Odersky at [this proposal](https://gist.github.com/odersky/f91362f6d9c58cc1db53f3f443311140). - -#### 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](https://docs.scala-lang.org/sips/pending/42.type.html)) enable developers to express literal type operations more intuitively. - -Consider the following example, where `foo` has two equivalent implementations, one using types, while the other uses terms: - -```scala -import singleton.ops._ - -object PrefixExample { - /* - We would much rather write the following to acheive more clarity and shorter code: - type Foo[Cond1, Cond2, Num] = ITE[Cond1 && !Cond2, -Num, Num] - */ - type Foo[Cond1, Cond2, Num] = ITE[Cond1 && ![Cond2], Negate[Num], Num] - //foo executes typelevel operations by using singleton-ops - def foo[Cond1, Cond2, Num](implicit f : Foo[Cond1, Cond2, Num]) : f.Out = f.value - //foo executes term operations - def foo(cond1 : Boolean, cond2 : Boolean, num : Int) : Int = - if (cond1 && !cond2) -num else num -} - -import PrefixExample._ - -foo[true, false, 3] //returns -3 -foo(true, false, 3) //returns -3 -``` - -Note: `type ![A]` is possible to define, but `type -[A]` is not due to collision with infix type parsing. - -#### DFiant library example - -DFiant is a domain specific language for hardware description I (Oron) am developing. Hardware interfaces have a direction annotation (e.g., `vec : DFBits[8] <> IN`). Sometime interfaces have multiple ports, in different directions. E.g.: - -```scala -trait MyInterface { - val data : DFBits[32] <> IN - val address : DFBits[32] <> OUT -} -``` - -To be able to use the same interface in reverse, we need the ability to easily express it as: - -```scala -trait MyReversableInterface[D <: Direction] { - val data : DFBits[32] <> D - val address : DFBits[32] <> ~D -} -``` - -An implicit conversion can then assure that `~IN` is translated to `OUT` and vice-versa. - -Interfaces may possess dozens of ports, thus without prefix types can be quite cumbersome. - ---- - -## Proposal - -Add support for prefix types, which is equivalent to the prefix operations for expressions. - -``` -PrefixType ::= [`-' | `+' | `~' | `!'] SimpleType -CompoundType ::= PrefixType - | AnnotType {with AnnotType} [Refinement] - | Refinement -``` - ------- - -## Implementation - -A PR for this SIP is available at: [https://github.com/scala/scala/pull/6148](https://github.com/scala/scala/pull/6148) - ------- - -### Interactions with other language features - -#### Variance Annotation -Variance annotation uses the `-` and `+` symbols to annotate contravariant and covariant subtyping, respectively. Introducing unary prefix types may lead to some developer confusion. However, such interaction is very unlikely to occur. E.g.: - -```scala -trait Negate[A] -trait Positive[A] -type unary_-[A] = Negate[A] -type unary_+[A] = Positive[A] -trait Contravariant[B, -A <: +B] //contravariant A subtype upper-bounded by Positive[B] -trait Covariant[B, +A <: -B] //covariant A subtype upper-bounded by Negative[B] -``` - -#### Negative Literal Types -Negative literal types are annotated using the `-` symbol. This can lead to the following confusion: - -```scala -trait Negate[A] -type unary_-[A] = Negate[A] -trait MyTrait[B] - -type MinusFortyTwo = MyTrait[-42] -type NegateFortyTwo = MyTrait[Negate[42]] -``` - -The above example demonstrates a case of two types `MinusFortyTwo` and `NegateFortyTwo` which are different. They may be equivalent in view (implicit conversion between the two type instances), but they are not equal. - -Note: It is not possible to annotate a positive literal type in Scala (checked both in TLS and Dotty): - -```scala -val a : 42 = +42 //works -val b : -42 = -42 //works -val c : +42 = 42 //error: ';' expected but integer literal found -``` - -This means that if unary prefix types are added, then `+42` will be a type expansion of `unary_+[42]`. - -**Related Issues** -* [Dotty Issue #2783](https://github.com/lampepfl/dotty/issues/2783) -* [Typelevel Scala Issue #157](https://github.com/typelevel/scala/issues/157) (Resolved in recent update to SIP23) - -Dotty's implementation of literal types currently fail compilation when infix types interact with a negative literal type. -```scala -type ~~[A, B] -type good = 2 ~~ 2 -type bad = 2 ~~ -2 //Error:(9, 20) ';' expected but integer literal found. -type work_around = 2 ~~ (-2) //Error in Dotty -``` ----- - -### Bibliography -[Scala Contributors](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471) - -[scala-sips](https://groups.google.com/forum/#!topic/scala-sips/ARVf1RLDw9U) diff --git a/_sips/sips/2018-05-26-case-if.md b/_sips/sips/2018-05-26-case-if.md deleted file mode 100644 index 7e4952f262..0000000000 --- a/_sips/sips/2018-05-26-case-if.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: sip -title: SIP-NN - Uncluttering Abuse of Match -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/case-if.html ---- - -**By: Som Snytt and A. P. Marki** - -## History - -| Date | Version | -|---------------|----------------| -| May 26th 2018 | Initial Draft | -| May 28th 2018 | Underscoreless | - -## Motivation - -Since the demise of SIP-12, we have relied on Marie Kondo to declutter -what remains of our lives. But we can do better. - -Currently, `case` syntax requires an underscore to represent a pattern, -even if the code of interest is the guard that follows. - -Anxiety over this underscore is expressed in [a StackOverflow question][1] -in which a programmer mulls the question of "Abuse of Match" and whether -it actually makes you go blind. - -We propose to go underscoreless, pace the famous consultancy. - -If we can't have SIP-12 then we can have tidy syntax for `if-then` in `case` blocks. - -Since `Scala <3` abhors two ways of doing a thing, we eliminate underscore -as a pattern. Underscore as a subpattern, that is, as a `pattern2`, is patterned -after placeholder syntax in expressions, just as constructor patterns are patterned -after constructor invocations. We read `C(_)` and `case C(_) =>` to mean construction -of `C` with an argument unspecified. Similarly, just as placeholder syntax is -restricted, so that an underscore is never an `Expr`, pattern syntax must disallow -underscore as a `Pattern` production. - -In fact, underscore never quite functioned that way, since the definition - - val _ = 42 - -has always incorrectly introduced a variable named `_`. - -Suggestions have surfaced that this syntax should mean something entirely different, -namely, the introduction of a freshly named variable which cannot be named in -source code, but induces the evaluation of the RHS of the definition, and which -can be accessed implicitly if defined as an implicit value. - -However that may be, underscore must first be disallowed as a `Pattern`. - -The only way to coherently define a `case` for which no pattern is applied is to omit -the pattern. And in the absence of alternative semantics, `val _` is not meaningful. -Underscore on the RHS of `var` definitions has already been deprecated, and it is -expected that that syntax will also be removed. - -## Syntax - -In lieu of - - 42 match { - case _ if now isAfter midnight => nothingGoodHappens() - case _ => () - } - -we write - - 42 match { - case if now isAfter midnight => nothingGoodHappens() - case => () - } - -The syntax accepts either a pattern with optional guard, or a guard with no pattern: - - CaseClause ::= ‘case’ (Pattern [Guard] | Guard) ‘=>’ Block - Guard ::= ‘if’ PostfixExpr - -## Further Justifications - -In a respected online [forum][2] which brooks no fools, [Mulliganaceous][3] has posted -an "accepted" answer using the idiom, "in case if". This supports `case if` as -a natural locution. - -In a response to one judgment about abuse of match, [one Scala user][4] whose handle I can -never spell quite right let alone pronounce finds the match version -"clearer and visually more pleasant" than the cluttered `if` expression. - -From the beginning, Scala has made great strides in reducing vertical space in source code. -However, we are still constrained horizontally, despite curved OLED screens. -Recently, [a suggested edit][5] was declined because of maximum line length restrictions. -Every wasted character brings us closer to an unfortunate line break. - -## Implementation - -An implementation is [available][6]. It's pretty slick. - -## References - -1. [Abuse of Match?][1] -2. [Reputation requirements for creating tags and tag synonyms][2] -3. [Mulliganaceous user profile][3] -4. [huynhjl user profile][4] -5. [Sample line length limitation in a Scala project][5] -6. [Implementation][6] - -[1]: https://stackoverflow.com/questions/12556236/abuse-of-match "Abuse of Match?" -[2]: https://meta.stackoverflow.com/a/368537/1296806 "Reputation requirements for creating tags and tag synonyms" -[3]: https://meta.stackoverflow.com/users/8242447/mulliganaceous "Mulliganaceous" -[4]: https://stackoverflow.com/users/257449/huynhjl "huynhjl" -[5]: https://github.com/apache/spark/pull/21369/files#r189794046 "scala-style enforces a max of 100 chars per line" -[6]: https://github.com/scala/scala/pull/6241 "Implementation PR 6241" - diff --git a/_sips/sips/2019-08-11-curried-varargs.md b/_sips/sips/2019-08-11-curried-varargs.md deleted file mode 100644 index 8fbe8f094d..0000000000 --- a/_sips/sips/2019-08-11-curried-varargs.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -layout: sip -title: SIP-NN - Curried varargs -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/curried-varargs.html ---- - -**By: Yang, Bo** - -## History - -| Date | Version | -|---------------|---------------| -| Aug 11th 2019 | Initial Draft | -| Aug 12th 2019 | Translating sequence arguments to `applyNextSeq` calls | - -## Introduction - -The [repeated parameters](https://scala-lang.org/files/archive/spec/2.13/04-basic-declarations-and-definitions.html#repeated-parameters) syntax is widely used in Scala libraries to create collection initializers, string interpolations, and DSLs. Unfortunately, repeated parameters are type unsafe as it erase all arguments to their common supertype, inefficient as it creates a temporary `Seq` that is difficult to be eliminated by optimizer. In practice, all sophisticated string interpolation libraries, including [string formatting](https://github.com/scala/scala/blob/43e040ff7e4ba92ccf223e77540580b32c1473c0/src/library/scala/StringContext.scala#L94) and [quasiquotes](https://github.com/scala/scala/blob/43e040ff7e4ba92ccf223e77540580b32c1473c0/src/reflect/scala/reflect/api/Quasiquotes.scala#L28) in standard library, [scalameta](https://scalameta.org/docs/trees/quasiquotes.html) and my [fastring](https://github.com/Atry/fastring/blob/67ae4eccdb9b7f58416ed90eae85ddb035b1ffb1/shared/src/main/scala/com/dongxiguo/fastring/Fastring.scala#L242) library, are written in macros in order to avoid runtime overhead of repeated parameters. - -We propose **curried varargs** to improve both the type safety and the performance. Given a function call `f(a, b, c)`, when `f` is a subtype of `Curried`, the function call should be rewritten to `f.applyBegin.applyNext(a).applyNext(b).applyNext(c).applyEnd`. - -## Motivating Examples - -### Examples - -Recently I was working on the implementation of [Pre SIP: name based XML literals](https://contributors.scala-lang.org/t/pre-sip-name-based-xml-literals/2175). During implementing that proposal, I found that the proposal is inefficiency due to repeated parameters, and it could be improved dramatically with the help of curried functions. - -For example, according to the proposal the XML literal `
line1
line2
` will result the following code: - -``` scala -xml.tags.div( - xml.attributes.title(xml.text("my-title")), - xml.text("line1"), - xml.tags.br(), - xml.text("line2") -) -``` - -With the help of this curried varargs proposal and `@inline`, we are able to implement an API to build a DOM tree with no additional overhead over manually written Scala code. - -``` scala -import org.scalajs.dom.document -import org.scalajs.dom.raw._ - -object xml { - type Attribute[-A <: Element] = A => Unit - @inline def text(data: String) = data - object attributes { - @inline def title(value: String): Attribute[Element] = _.setAttribute("title", value) - } - object tags { - class Builder[+E <: Element](private val element: E) extends AnyVal with Curried { - @inline def applyBegin = this - @inline def applyNext(text: String) = { - element.appendChild(document.createTextNode(text)) - this - } - @inline def applyNext(node: Node) = { - element.appendChild(node) - this - } - @inline def applyNext[A <: Attribute[E]](attribute: A) = { - attribute(element) - this - } - @inline def applyEnd = element - } - @inline def div = new Builder(document.createElement("div")) - @inline def br = new Builder(document.createElement("br")) - } -} -``` - -Since `xml.tags.div` returns a `Builder`, which is a subtype of `Curried`, calls on `xml.tags.div` will be translated to the curried form, as shown below: -``` scala -xml.tags.div - .applyBegin - .applyNext(xml.attributes.title(xml.text("my-title"))) - .applyNext(xml.text("line1")) - .applyNext(xml.tags.br.applyBegin.applyEnd) - .applyNext(xml.text("line2")) - .applyEnd -``` - -When the above code is compiled in Scala.js, the builders should be eliminated entirely as a zero cost abstraction layer, and the output JavaScript is tiny as shown below: - -``` javascript -var $$this = $m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createElement("div"); -$$this.setAttribute("title", "my-title"); -$$this.appendChild($m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createTextNode("line1")); -var $$this$1 = $m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createElement("br"); -$$this.appendChild($$this$1); -$$this.appendChild($m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createTextNode("line2")); -``` - -### Comparison Examples - -The `Builder` API can be also implemented in repeated parameters: - -``` scala -import org.scalajs.dom.document -import org.scalajs.dom.raw._ - -object xml { - type Attribute[-A <: Element] = A => Unit - @inline def text(data: String) = data - object attributes { - @inline def title(value: String): Attribute[Element] = _.setAttribute("title", value) - } - object tags { - class Builder[+E <: Element](private val element: E) extends AnyVal { - @inline def apply(attributesAndChildren: Any*) = { - attributesAndChildren.foreach { - case text: String => - element.appendChild(document.createTextNode(text)) - case node: Node => - element.appendChild(node) - case attribute: Attribute[E] => - attribute(element) - } - element - } - } - @inline def div = new Builder(document.createElement("div")) - @inline def br = new Builder(document.createElement("br")) - } -} -``` - -However, the Scala compiler is unable to optimize repeated parameters, as a result, the output JavaScript from Scala.js would look like the below code. - -``` javascript -var $$this$1 = $m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createElement("div"); -var this$3 = $m_LScalaFiddle$xml$attributes$(); -var jsx$1 = new $c_sjsr_AnonFunction1().init___sjs_js_Function1((function($this, value) { - return (function(x$1$2) { - x$1$2.setAttribute("title", value) - }) -})(this$3, "my-title")); -var $$this = $m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createElement("br"); -var array = [jsx$1, "line1", $$this, "line2"]; -var i = 0; -var len = $uI(array.length); -while ((i < len)) { - var index = i; - var arg1 = array[index]; - if ($is_T(arg1)) { - var x2 = $as_T(arg1); - $$this$1.appendChild($m_Lorg_scalajs_dom_package$().document__Lorg_scalajs_dom_raw_HTMLDocument().createTextNode(x2)) - } else if ($uZ((arg1 instanceof $g.Node))) { - $$this$1.appendChild(arg1) - } else if ($is_F1(arg1)) { - var x4 = $as_F1(arg1); - x4.apply__O__O($$this$1) - } else { - throw new $c_s_MatchError().init___O(arg1) - }; - i = ((1 + i) | 0) -}; -``` - -Despite of the type safety issue due to the usage of `Any`, the above code are inefficient: - -1. Unnecessary temporary object for the `xml.attributes.title(xml.text("my-title"))`. -2. Unnecessary temporary `Seq` to hold repeated parameters. -3. Unnecessary runtime type check for each argument. - -The similar issues can be found in many other usage of repeated parameters. For example, Scala string interpolation is inefficient due to its internal vararg function call, unless implementing it in a macro; Scala collection initializers (e.g. `List(1, 2, 3)`) create unnecessary temporary `Seq` before creating the desired collection. - -## Design - -This proposal introduces a new type `Curried` defined as following: - -``` scala -trait Curried extends Any -``` - -When a function call `f(p1, p2, p3, ... pn)` is being type checked, the compiler will firstly look for `apply` method on `f`. If an applicable `apply` method is not found and `f` is a subtype of `Curried`, the compiler will convert the function call to curried form `f.applyBegin.applyNext(p1).applyNext(p2).applyNext(p3) ... .applyNext(pn).applyEnd`, and continue type checking the translated call. - -### Expanding sequence argument - -Optionally, some arguments to a `Curried` call may be a sequence argument marked as `_*`. Those are arguments should be translated to `applyNextSeq` calls instead of `applyNext`. For example, `f(p1, s1: _*, p2)` will be translated to the following code. - -``` scala -f.applyBegin - .applyNext(p1) - .applyNextSeq(s1) - .applyNext(p2) -.applyEnd -``` - -Unlike traditional repeated parameters, which restrict the sequence argument at the last position, sequence arguments in a curried call are allowed at any position. - -### Builder type shifting - -The type of partially applied function might be changed during applying each argument. Given the following type signature: - -``` scala -class ListBuilder[A] { - def applyNext[B >: A](b: B): ListBuilder[B] = ??? - def applyNextSeq[B >: A](seqB: Seq[B]): ListBuilder[B] = ??? - def applyEnd: List[A] = ??? -} -object List extends Curried { - def applyBegin[A]: ListBuilder[A] = ??? -} -``` - -`List(42, "a")` should be translated to `List.applyBegin.applyNext(42).applyNext("a").applyEnd`. Then, the typer will infer type parameters as `List.applyBegin[Nothing].applyNext[Int](42).applyNext[Any]("a").applyEnd`, therefore the final return type of `applyEnd` will be `List[Any]`. - -### Explicit type parameters - -When a `Curried` is invoked with some type arguments, those type arguments will be moved to the `applyBegin` method. Therefore, `List[Int](1 to 3: _*)` should be translated to `List.applyBegin[Int].applyNextSeq(1 to 3).applyEnd`. - -### Implicit parameters - -A more common form of curried function call would be like `f(a)(b)(c)`. We prefer the explicit named method calls to `applyNext` instead of the common form, in order to support implicit parameters in `applyNext`. Therefore, each explicit parameter might come with an implicit parameter list, resolving the infamous [multiple type parameter lists](https://github.com/scala/bug/issues/4719) issue. - -### Multiple curried vararg parameter lists - -When a `Curried` is invoked with multiple parameter lists, for example: -``` scala -f(a, b, c)(d, e) -``` - -Then the first parameter list should be translated to a curried call: - -``` scala -f.applyBegin - .applyNext(a) - .applyNext(b) - .applyNext(c) -.applyEnd(d, e) -``` - -`(d, e)` is translated to the curried form only if `applyEnd` returns a `Curried`. - -### Overloaded curried calls - -Curried varargs enables overloaded functions for each parameter. Parameters will not be erased to their common supertype. - -## Implementation - -This proposal can be implemented either in the Scala compiler or in a whitebox macro. [Curried.scala](https://github.com/Atry/Curried.scala) is an implementation of the proposal in a whitebox macro. - -## Alternatives - -### Repeated parameters - -Repeated parameters are packed into a `Seq`, which is then passed to the callee. - -#### Pros - -* Interoperable with Java - -#### Cons - -* Always boxing value class parameters -* Unable to inline function parameters -* Unable to inline call-by-name parameters -* Unable to perform implicit conversion for each parameter -* Unable to infer context bound for each parameter -* Erasing all parameters to their common super type - -## Reference -* [Existing Implementation (Curried.scala)](https://github.com/Atry/Curried.scala) -* [Discussion on Scala Contributors forum](https://contributors.scala-lang.org/t/pre-sip-curried-varargs/3608) -* [Pre SIP: name based XML literals](https://contributors.scala-lang.org/t/pre-sip-name-based-xml-literals/2175) -* [Scala Language Specification - Repeated Parameters](https://scala-lang.org/files/archive/spec/2.13/04-basic-declarations-and-definitions.html#repeated-parameters) diff --git a/_sips/sips/2019-10-24-name-based-xml.md b/_sips/sips/2019-10-24-name-based-xml.md deleted file mode 100644 index 6389003fa9..0000000000 --- a/_sips/sips/2019-10-24-name-based-xml.md +++ /dev/null @@ -1,421 +0,0 @@ ---- -layout: sip -title: SIP-NN - Name Based XML Literals -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/name-based-xml.html ---- - -**By: Yang, Bo** - -## History - -| Date | Version | -|---------------|---------------| -| Oct 24th 2019 | Initial Draft | - -## Introduction - -Name-based `for` comprehension has been proven success in Scala language design. A `for` / `yield` expression will be converted to higher-order function calls to `flatMap` , `map` and `withFilter` methods, no matter which type signatures they are. The `for` comprehension can be used for either `Option` or `List` , even when `List` has an additional implicit `CanBuildFrom` parameter. Third-party libraries like Scalaz and Cats also provides `Ops` to allow monadic data types in `for` comprehension. - -[Name-based pattern matching](https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html) is introduced by Dotty. It is greatly simplified the implementation compared to Scala 2. In addition, specific symbols in Scala library ( `Option` , `Seq` ) are decoupled from the Scala compiler. - -Considering the success of the above name-based syntactic sugars, in order to decouple `scala-xml` library from Scala compiler, name-based XML literal is an obvious approach. - -## Motivating Examples - -### Examples - -Given an XML literal `
line1
line2
`, at the parser phase, the compiler should internally convert it to an AST equivalent to the following code: - -``` scala -xml.literal( - xml.elements.div( - xml.attributes.title(xml.values.`my-title`), - xml.texts.line1, - xml.elements.br(), - xml.texts.line2 - ) -) -``` - -By creating or importing different implementation of `xml`, various representation of objects for the XML literal will be created. For example, to create HTML DOM in Scala.js, you can define `xml` object as following: - -``` scala -import org.scalajs.dom.document -import org.scalajs.dom.raw._ - -object xml { - type Attribute[-A <: Element] = A => Unit - object values extends Dynamic { - def selectDynamic(data: String) = data - } - object texts extends Dynamic { - def selectDynamic(data: String) = data - } - object attributes { - def title(value: String): Attribute[Element] = _.setAttribute("title", value) - } - object elements { - class Builder[+E <: Element](val element: E) extends AnyVal { - def apply(attributesAndChildren: Any*) = { - attributesAndChildren.foreach { - case text: String => - element.appendChild(document.createTextNode(text)) - case builder: Builder[_] => - element.appendChild(builder.element) - case node: Node => - element.appendChild(node) - case attribute: Attribute[E] => - attribute(element) - } - element - } - } - def div = new Builder(document.createElement("div")) - def br = new Builder(document.createElement("br")) - def literal[+E <: Element](builder: Builder[E]) = builder.element - } -} -``` - -The ability of custom implementation for XML literals enables a lot of possibilities, including React-like virtual DOM frameworks, static type checked XML schema, and reactive XHTML/FXML templating. - -### Comparison Examples - -XML literals in Scala 2.13 are symbol based, which means the type of an XML literal is always `scala.xml.Elem`. The code `
line1
line2
` will be parsed to an AST equivalent to the following code: - -``` scala -{ - var $md: MetaData = Null; - $md = new UnprefixedAttribute("title", new Text("my-title"), $md); - new Elem(null, "div", $md, TopScope, false, ({ - val $buf = new NodeBuffer(); - $buf.$amp$plus(new Text("line1")); - $buf.$amp$plus({ - { - new Elem(null, "br", Null, TopScope, true) - } - }); - $buf.$amp$plus(new Text("line2")); - $buf - }: _*)) -} -``` - -Note that `MetaData`, `UnprefixedAttribute`, `Text`, `Elem` and `TopScope` are types defined in `scala.xml`. Library authors cannot create their custom representation of XML literals. - -### Counter-Examples - -With the help of this proposal, a library author can create custom XML based DSL. - -``` scala -
-``` - -It will be translated to: - -``` scala -xml.literal( - xml.elements.div( - xml.elements.label( - xml.attributes.`v-if`(xml.interpolation(math.random() < 0.5)), - xml.texts.`You might see me` - ) - ) -) -``` - -The translated `v-if` method can be supported by creating an `implicit class` for `xml.attributes` easily. However, this proposal is not intended to be used for creating a DSL as an alternative to regular Scala code. Instead, you should just use ordinary `if` expression. - -``` scala -
{ - if (math.random() < 0.5) { - - } else { - - } -}
-``` - -and it will be translated to: - -``` scala -xml.literal( - xml.elements.div( - xml.interpolation { - if (math.random() < 0.5) { - xml.literal( - xml.elements.label( - xml.texts.`You might see me` - ) - ) - } else { - xml.literal( - xml.comment("condition not met") - ) - } - } - ) -) -``` - -Name based XML literal is best for modeling existing XML format within Scala source files. For other cases, just use ordinary Scala control flows for dynamic conditions, and use ordinary Scala case class for data structures not defined in the existing XML format. - -## Design - -### Goals - -* Keeping source-level backward compatibility to existing symbol-based XML literals in most use cases of `scala-xml`. -* Allowing schema-aware XML literals, i.e. static type varying according to tag names, similar to the current TypeScript and [Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala) behavior. -* Schema-aware XML literals should be understandable by both the compiler and IDE (e.g. no white box macros involved). -* Existing libraries like ScalaTags should be able to support XML literals by adding a few simple wrapper classes. No macro or meta-programming knowledge is required for library authors. -* Able to implement an API to build a DOM tree with no more cost than manually written Scala code. - -### Non-goals - -* Embedding fully-featured standard XML in Scala. -* Allowing arbitrary tag names and attribute names (or avoiding reserved word). -* Distinguishing lexical differences, e.g. `` vs `` . - -### Other consideration -#### White space only text - -Whitespace-only is preserved by default. However, a XML library vendor is able to discard whitespace texts during constructing the object for the XML literal. - -#### The usage of varargs - -Since an element might contain arbitrary number of child nodes, varargs are required to handle these child nodes. Unfortunately, traditional varargs are type unsafe as it erase all arguments to their common super type, inefficient as it creates a temporary `Seq` that is difficult to be eliminated by optimizer. - -Both problems can be resolved with the help of [Curried Varargs](https://docs.scala-lang.org/sips/curried-varargs.html), which is another SIP to address the type safety and performance issues in traditional varargs. - -## Other Examples - -### Self-closing tags without prefixes - -``` scala - -``` - -will be translated to - -``` scala -xml.literal( - xml.elements.`tag-name`() -) -``` - -### Node list - -``` scala - - -``` - -will be translated to - -``` scala -xml.literal( - xml.elements.`tag-name`(), - `prefix-1`.elements.`tag-name`() -) -``` -### Attributes - -``` scala - -``` - -will be translated to - -``` scala -xml.literal( - xml.elements.`tag-name`( - xml.attributes.`attribute-1`(xml.values.value), - xml.attributes.`attribute-2`(xml.interpolation(f())) - ) -) -``` - -### CDATA - -`` will be translated to `xml.literal(xml.texts.raw)` if `-Xxml:coalescing` flag is on, or `xml.literal(xml.cdata("raw"))` if the flag is turned off as `-Xxml:-coalescing` . - -### Process instructions - -``` scala - -``` - -will be translated to - -``` scala -xml.literal( - xml.processInstructions.`xml-stylesheet`("type=\"text/xsl\" href=\"style.xsl\"") -) -``` - -### Child nodes - -``` scala - - text & hexadecimal reference & decimal reference - - - { math.random } - - -``` - -will be translated to - -``` scala -xml.literal( - xml.elements.`tag-name`( - xml.attributes.`attribute-1`(xml.values.value), - xml.texts.`$u000A text `, - xml.entities.amp, - xml.texts.` hexadecimal reference `, - xml.entities.AMP, - xml.texts.` decimal reference$u000A `, - xml.elements.`child-1`(), - xml.texts.`$u000A `, - xml.comment(" my comment "), - xml.texts.`$u000A `, - xml.interpolation(math.random), - xml.texts.`$u000A `, - xml.cdata(" raw "), // or (xml.texts.` raw `), if `-Xxml:coalescing` flag is set - xml.texts.`$u000A ` - ) -) -``` - -Note that hexadecimal references and decimal references will be unescaped and translated to `xml.texts` automatically, while entity references are translated to fields in `xml.entities` . - -### Prefixes without `xmlns` bindings. - -``` scala - - content - - -``` - -will be translated to - -``` scala -xml.literal( - `prefix-1`.elements.`tag-name-1`( - `prefix-1`.attributes.`attribute-1`(`prefix-1`.values.`value-1`), - `prefix-2`.attributes.`attribute-2`(`prefix-2`.values.`value-2`), - `prefix-1`.texts.`$u000A `, - xml.elements.`tag-name-2`( - xml.texts.content - ), - `prefix-1`.texts.`$u000A `, - `prefix-1`.comment(" my comment "), - `prefix-1`.texts.`$u000A` - ) -) -``` - -Note that unprefixed attribute will be treated as if it has the same prefix as its enclosing element. - -### `xmlns` bindings. - -``` scala - - content - - -``` - -will be translated to - -``` scala -xml.literal( - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).elements.`tag-name-1`( - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).attributes.`attribute-1`(xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).values.`value-1`), - xml.prefixes.`prefix-2`(xml.uris.`http://example.com/2`).attributes.`attribute-2`(xml.prefixes.`prefix-2`(xml.uris.`http://example.com/2`).values.`value-2`), - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).texts.`$u000A `, - xml.noPrefix(xml.uris.`http://example.com/0`).elements.`tag-name-2`( - xml.noPrefix(xml.uris.`http://example.com/0`).texts.content - ), - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).texts.`$u000A `, - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).comment(" my comment "), - xml.prefixes.`prefix-1`(xml.uris.`http://example.com/1`).texts.`$u000A` - ) -) -``` - -## Implementation - -The implementation of this proposal can be two parts: - -1. Compile-time XML translator -2. XML library vendors - -### Compile-time XML translator - -Compile-time XML translator will translate XML literal to Scala AST before type checking. It can be implemented either in the Scala compiler or in a white box macro. [nameBasedXml.scala](https://github.com/GlasslabGames/nameBasedXml.scala) is an implementation of the proposal in a white box macro. - -### XML library vendors - -An XML library vendor should provide a package or object named `xml` , which contains the following methods or values: - -* `elements` -* `attributes` -* `values` -* `entities` -* `processInstructions` -* `texts` -* `comment` -* `cdata` -* `interpolation` -* `noPrefix` -* `prefixes` -* `uris` -* `literal` - -All above methods except `literal` should return a builder, and `literal` will turn one or more builders into an XML object / or an XML node list. - -An XML library user can switch different implementations by importing different `xml` packages or objects. `scala.xml` is used by default when no explicit import is present. - -In a schema-aware XML library like Binding.scala, its `elements` , `attributes` , `processInstructions` and `entities` methods should return factory objects that contain all the definitions of available tag names and attribute names. An XML library user can provide additional tag names and attribute names in user-defined implicit classes for `tags` and `attributes` . - -In a schema-less XML library like `scala-xml` , its `elements` , `attributes` , `processInstructions` and `entities` should return builders that extend [scala.Dynamic](https://www.scala-lang.org/api/current/scala/Dynamic.html) in order to handle tag names and attribute names in `selectDynamic` or `applyDynamic` . - -Those XML libraries can be extended with the help of standard XML namespace bindings. A plug-in author can create `implicit class` for `xml.uris` to introduce foreign elements embedded in existing XML literals. - -[html.scala](https://github.com/GlasslabGames/html.scala) is an XML library vendor for creating reactive HTML templates. - -## Drawbacks - -### Name clash - -`` or `` will not compile due to name clash to `Any.toString` and `Any.equals` . - -* Compilation error is the desired behavior in a schema-aware XML library as long as `toString` is not a valid name in the schema. Fortunately, unlike JSX, `
` should compile because `class` is a valid method name. -* A schema-less XML library user should instead explicit construct `` in Scala code, e.g. `new Elem("toString")` . - -## Alternative approach - -XML initialization can be implemented in a special string interpolation as `xml""`, which can be implemented in a macro library. [scala-xml-quote](https://github.com/densh/scala-xml-quote) is an implementation in this approach. The pros and cons of these approaches are listed in the following table: - -||symbol-based XML literals in Scala 2.12|name-based XML literals in this proposal|`xml` string interpolation| -| --- | --- | --- | --- | -|XML is parsed by ...|compiler|compiler|library, IDE, and other code browsers including Github, Jekyll (if syntax highlighting is wanted)| -|Is third-party schema-less XML library supported?|No, unless using white box macros|Yes|Yes| -|Is third-party schema-aware XML library supported?|No, unless using white box macros|Yes|No, unless using white box macros| -|How to highlight XML syntax?|By regular highlighter grammars|By regular highlighter grammars|By special parsing rule for string content| -|Can presentation compiler perform code completion for schema-aware XML literals?|No|Yes|No| - -## References - -* [nameBasedXml.scala](https://github.com/GlasslabGames/nameBasedXml.scala) -* [API Documentation](https://javadoc.io/page/org.lrng.binding/namebasedxml_2.12/1.0.0/org/lrng/binding/nameBasedXml.html) -* [html.scala](https://github.com/GlasslabGames/html.scala) -* [Discussion on Scala Contributors Forum](https://contributors.scala-lang.org/t/pre-sip-name-based-xml-literals/2175) -* [Curried Varargs](https://docs.scala-lang.org/sips/curried-varargs.html) -* [scala-xml-quote](https://github.com/densh/scala-xml-quote) diff --git a/_sips/sips/2021-05-18-sealed-types.md b/_sips/sips/2021-05-18-sealed-types.md deleted file mode 100644 index 369c705079..0000000000 --- a/_sips/sips/2021-05-18-sealed-types.md +++ /dev/null @@ -1,278 +0,0 @@ ---- -layout: sip -title: Sealed Types -vote-status: pending -permalink: /sips/:title.html -redirect_from: /sips/pending/sealed-types.html ---- - -**By: Dale Wijnand and Fengyun Liu** - -## History - -| Date | Version | -|---------------|-----------| -| May 18th 2021 | Submitted | - -## Introduction - -Exhaustivity checking is one of the safety belts for using pattern matching in functional -programming. However, if one wants to partition values of an existing type to reason about them in -separate cases of a match or as separate types this requires wrapping the values in new classes, -which incurs a boxing cost. - -As an alternative, one may define custom extractors and use them as the case patterns. However, there is no -way in Scala to declare that these extractors are complementary, i.e. the match is exhaustive when -the complete set of extractors are used together. - -Similarly, one can define `TypeTest`s for matching abstract types, but there is no way to determine -if a match on an abstract type exhausts all its subtypes. - -This SIP solves these three problems by introducing *sealed types*. - -## Motivating Examples - -We've identified several real world use cases that calls for enhancing exhaustivity checking, and -used them to stress the design proposed here. You can find them [here][problems], but we'll present -an example below. - -Using the opaque types and custom extractors we can work safely with positive integers and negative -integers: - -```scala -opaque type Pos <: Int = Int -opaque type Neg <: Int = Int - -object Pos { def unapply(x: Int): Option[Pos] = if (x > 0) Some(x) else None } -object Neg { def unapply(x: Int): Option[Neg] = if (x < 0) Some(x) else None } - -(n: Int) match - case 0 => - case Pos(x) => - case Neg(x) => -``` - -With the above, when we get a `Pos` value, it's guaranteed to be a positive number. The same goes -for `Neg`. Sadly the match is reported as not exhaustive because the two extractors and the value -`0` aren't known to be complementary. - -## Design - -We identify two root causes: - -1. You can't define a type, that isn't a class, as `sealed` -2. You can't define a mapping from values to types - -The *sealed type*, proposed by this SIP, allow partitioning of value of a given type into a sealed -type hierarchy. [Here][solutions] you can find how sealed types address the issues faced by all the -motivating examples, but we'll present here how it fixes the number example above. - -In order to partition int into positive, zero, and negative numbers we'll define a new `sealed` type -`Num` and how to distinguish its subtypes with match syntax: - -```scala -sealed type Num = Int { - case 0 => val Zero - case n if n > 0 => type Pos - case _ => type Neg -} -``` - -This sealed type definition desugars into the following type and value definitions: - -```scala -type Num = Int -val Zero: Num = 0 -opaque type Pos <: Num = Num -opaque type Neg <: Num = Num -``` - -The match desugars into an ordinal method, that reuses the logic to associate an ordinal for each -case: - -```scala -extension (n: Num): - def ordinal: Int = (n: Int) match { - case 0 => 0 - case n if n > 0 => 1 - case _ => 2 - } -``` - -Finally a series of `TypeTest`s are defined, allowing for values of both the underlying type `Int` -and the sealed type `Num` to be tested against the subtypes and singleton subtypes `Pos`, -`Zero.type`, and `Neg`, using the `ordinal` method: - -```scala -given TypeTest[Int, Zero.type] = (n: Int) => if ((n: Num).ordinal == 0) Some(n) else None -given TypeTest[Int, Pos] = (n: Int) => if ((n: Num).ordinal == 1) Some(n) else None -given TypeTest[Int, Neg] = (n: Int) => if ((n: Num).ordinal == 2) Some(n) else None -given TypeTest[Int, Num] = (n: Int) => Some(n) -given [T <: Num](using t: TypeTest[Int, T]): TypeTest[Num, T] = (n: Num) => t.unapply(n) -``` - -Given the above, one can either change the usage from extractors to types: - -```scala -(n: Int) match - case 0 => - case x: Pos => - case x: Neg => -``` - -Or we can keep the usage the same by redefining the extractors (using a value class name-based -extractors `PosExtractor` and `NegExtractor` to avoid allocating): - -```scala -object Pos { def unapply(x: Pos): PosExtractor = new PosExtractor(x) } -object Neg { def unapply(x: Neg): NegExtractor = new NegExtractor(x) } - -class PosExtractor(private val x: Pos) extends AnyVal { def isEmpty: false = false ; def get = x } -class NegExtractor(private val x: Neg) extends AnyVal { def isEmpty: false = false ; def get = x } - -(n: Int) match - case 0 => - case Pos(x) => - case Neg(x) => -``` - -## Syntax - -The existing syntax is enhanced as follows: - -``` -TypeDcl ::= `sealed` [`opaque`] `type` id [TypeParamClause] -TypeDef ::= `sealed` [`opaque`] `type` id [TypeParamClause] [`>:` Type] [`<:` Type] `=` Type `{` - `case` Pattern [Guard] `=>` (`type` id [TypeParamClause] | `val` id [`:` Type]) - `}` -``` - -Specifically: - -* the `sealed` modifier becomes available for type definitions and declarations -* on the right-hand side of definitions is the underlying type of the sealed type -* following the underlying type is a match that operates on a value of the - underlying type and defines the type or singleton type associated to that case. -* the type is defined using the `type` keyword and singleton types are defined using `val` - -## Desugaring - -Using the example - -``` -sealed [opaque] type T[X..] [bounds] = U { - case p1 => type C[X..] - case p2 => val S -} -``` - -That desugars into: -* a type alias `type T[X..] [bounds] = U`, `opaque` if the sealed type is `opaque` (see Restrictions) -* opaque type definitions, `opaque type C[X..] <: T[Y..] = T[Y..]`, for each non-singleton type case - - any type argument `Y` that isn't defined from `X..` will be: - + its lower bound, if the type parameter is covariant - + its upper bound, if the type parameter is contravariant - + a wildcard type, with the type parameter's bounds, if the type parameter is invariant -* val definitions, `val S: T[Y..] = p`, for singleton type cases, using the same type argument rules -* an ordinal method, `extension (t: T): def ordinal: Int = (t: U) match { case p1 => 0 p2 => 1 .. }` - - each of the sealed type's cases is associated with a unique ordinal - - ordinals starts from 0 and increase for each case, in the order of their definition - - the ordinal method adds a `case _ => -1` default case, if the sealed type's match is inexhaustive - - such an ordinal method may only be defined by the compiler, to preserve exhaustivity guarantees -* a series a `TypeTest`s, defined in terms of the ordinal method - - a `TypeTest` between `U` and each case `A`, having ordinal `ordA`: - + `given TypeTest[U, C] = (u: U) => if ((u: T).ordinal == $ordA) Some(u) else None` - + `given TypeTest[U, S.type] = (u: U) => if ((u: T).ordinal == $ordA) Some(u) else None` - - a type test between the underlying type and the sealed type: - + `given TypeTest[U, T] = (u: U) => if ((u: T).ordinal == -1) None else Some(u)` - - a generic type test between `T` and each case `A`, defined in terms of the above type tests: - + `given [A <: T](using t: TypeTest[U, A]): TypeTest[T, A] = (x: T) => t.unapply(x)` - -## Restrictions - -1. If the match on the value of the underlying type is not exhaustive, then the sealed type must be - declared `opaque`, in order to preserve the fact that the sealed type represents only a subset of - the values of the underlying type (e.g. positive integers) -2. No other type may be declared to subtype the opaque type `T` -3. For singleton types, the pattern `p` must be a stable value, e.g. a `val`, a `case object`, or a literal -4. Each case must define a new type or singleton type - -## Alternative Design - -An alternative design is to introduce an annotation `@complete` to specify that -a type can be partitioned into a list of subtypes. - -For example, given the following definition: - -```scala - opaque type Nat <: Int = Int - opaque type Neg <: Int = Int - - @complete[(Nat, Neg)] // Num decomposes to `Nat` and `Neg` - type Num = Int - - given TypeTest[Int, Neg] = (n: Int) => if (x < 0) Some(n) else None - given TypeTest[Int, Nat] = (n: Int) => if (x >= 0) Some(n) else None -``` - -The user now can write code as follows: - -``` Scala - def foo(n: Num) = - n match - case x: Neg => - case x: Nat => -``` - -Knowing that the type `Num` can be decomposed to `Neg` and `Nat`, the compiler -can verify that the pattern match above is exhaustive. - -This approach, however, is relatively low-level and the compiler does not -provide any guarantee that the annotation is actually correct. - -You can find more examples [here][complete-gist] - -## Related Work - -Haskell has a `COMPLETE` pragma which allows patterns and type constructors to be -defined to be a complete set, relying on the programmer getting it right. - -```haskell -data Choice a = Choice Bool a - -pattern LeftChoice :: a -> Choice a -pattern LeftChoice a = Choice False a - -pattern RightChoice :: a -> Choice a -pattern RightChoice a = Choice True a - -{-# COMPLETE LeftChoice, RightChoice #-} - -foo :: Choice Int -> Int -foo (LeftChoice n) = n * 2 -foo (RightChoice n) = n - 2 -``` - -## References - -1. [Opaque types][1] -2. [Forum discussion about Opt[T]][2] -3. [Github discussion about enhancing exhaustivity check][3] -4. [_Lightweight static capabilities_][4], Oleg Kiselyov, Chung-chieh Shan, 2007 -5. [TypeTest documentation][5] - -[1]: https://docs.scala-lang.org/sips/opaque-types.html -[2]: https://contributors.scala-lang.org/t/trouble-with-2-13-4-exhaustivity-checking-being-too-strict/4817 -[3]: https://github.com/lampepfl/dotty/issues/10961 -[4]: http://okmij.org/ftp/Computation/lightweight-guarantees/lightweight-static-capabilities.pdf -[5]: http://dotty.epfl.ch/docs/reference/other-new-features/type-test.html -[6]: https://github.com/lampepfl/dotty/pull/11186 -[problems]: https://gist.github.com/dwijnand/d33436cf197daa15216b3cd35d03ba1c#file-sealedtypeproblems-scala -[solutions]: https://gist.github.com/dwijnand/d33436cf197daa15216b3cd35d03ba1c#file-sealedtypesolutions-scala -[complete-gist]: https://gist.github.com/dwijnand/d33436cf197daa15216b3cd35d03ba1c#file-z-complete-scala - -* https://github.com/lampepfl/dotty/issues/10961 False “match may not be exhaustive warning” -* https://github.com/lampepfl/dotty/pull/11186 Implement @covers annotation for partial irrefutable specification -* https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/exts/pragmas.html#complete-pragma -* https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/exts/pattern_synonyms.html -* https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html diff --git a/_sips/sips/2014-06-27-42.type.md b/_sips/sips/42.type.md similarity index 89% rename from _sips/sips/2014-06-27-42.type.md rename to _sips/sips/42.type.md index aef0b4320b..861505d42e 100644 --- a/_sips/sips/2014-06-27-42.type.md +++ b/_sips/sips/42.type.md @@ -1,7 +1,8 @@ --- layout: sip title: SIP-23 - Literal-based singleton types -vote-status: complete +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/42.type.html --- @@ -32,17 +33,17 @@ their role is to give the meaning of paths selecting types and terms from nested paths have an intuitive meaning to programmers from a wide range of backgrounds which belies their underpinning by a somewhat "advanced" concept in type theory. -Nevertheless, by pairing a type with it's unique inhabitant, singleton types bridge the gap between -types and values, and their presence in Scala has over the years allowed Scala programmers to explore -techniques which would typically only be available in languages, such as Agda or Idris, with support +Nevertheless, by pairing a type with its unique inhabitant, singleton types bridge the gap between +types and values, and their presence in Scala has, over the years, allowed Scala programmers to explore +techniques which would typically only be available in languages such as Agda or Idris, with support for full-spectrum dependent types. Scala's semantics have up until now been richer than its syntax. The only singleton types which are currently _directly_ expressible are those of the form `p.type` where `p` is a path pointing to a value of some subtype of `AnyRef`. Internally the Scala compiler also represents singleton types for -individual values of subtypes of `AnyVal`, such as `Int` or values of type `String` which don't +individual values of subtypes of `AnyVal`, such as `Int` or values of type `String`, which don't correspond to paths. These types are inferred in some circumstances, notably as the types of `final` -vals. Their primary purpose has been to represent compile time constants (see [6.24 Constant +vals. Their primary purpose has been to represent compile-time constants (see [6.24 Constant Expressions](https://scala-lang.org/files/archive/spec/2.12/06-expressions.html#constant-expressions) and the discussion of "constant value definitions" in [4.1 Value Declarations and Definitions](https://scala-lang.org/files/archive/spec/2.12/04-basic-declarations-and-definitions.html#value-declarations-and-definitions)). @@ -88,15 +89,15 @@ Lightbend Scala compiler. foo(1: 1) // type ascription ``` -+ The `.type` singleton type forming operator can be applied to values of all subtypes of `Any`. - To prevent the compiler from widening our return type we assign to a final val. ++ The `.type` singleton-type-forming operator can be applied to values of all subtypes of `Any`. + To prevent the compiler from widening our return type, we assign to a final val. ``` def foo[T](t: T): t.type = t final val bar = foo(23) // result is bar: 23 ``` + The presence of an upper bound of `Singleton` on a formal type parameter indicates that - singleton types should be inferred for type parameters at call sites. To help see this + singleton types should be inferred for type parameters at call sites. To help see this, we introduce type constructor `Id` to prevent the compiler from widening our return type. ``` type Id[A] = A @@ -117,7 +118,7 @@ Lightbend Scala compiler. ``` + A `scala.ValueOf[T]` type class and corresponding `scala.Predef.valueOf[T]` operator has been - added yielding the unique value of types with a single inhabitant. + added, yielding the unique value of types with a single inhabitant. ``` def foo[T](implicit v: ValueOf[T]): T = v.value foo[13] // result is 13: Int @@ -128,13 +129,13 @@ Lightbend Scala compiler. Many of the examples below use primitives provided by the Scala generic programming library [shapeless](https://github.com/milessabin/shapeless/). It provides a `Witness` type class and a -family of Scala macro based methods and conversions for working with singleton types and shifting +family of Scala-macro-based methods and conversions for working with singleton types and shifting from the value to the type level and vice versa. One of the goals of this SIP is to enable Scala programmers to achieve similar results without having to rely on a third party library or fragile and non-portable macros. The relevant parts of shapeless are excerpted in [Appendix 1](#appendix-1--shapeless-excerpts). -Given the definitions there, some of forms summarized above can be expressed in current Scala, +Given the definitions there, some of the forms summarized above can be expressed in current Scala, ``` val wOne = Witness(1) val one: wOne.T = wOne.value // wOne.T is the type 1 @@ -146,13 +147,13 @@ foo[wOne.T] // result is 1: 1 "foo" ->> 23 // shapeless record field constructor // result type is FieldType["foo", Int] ``` -The syntax is awkward and hiding it from library users is challenging. Nevertheless they enable many +The syntax is awkward, and hiding it from library users is challenging. Nevertheless they enable many constructs which have proven valuable in practice. #### shapeless records shapeless models records as HLists (essentially nested pairs) of record values with their types -tagged with the singleton types of their keys. The library provides user friendly mechanisms for +tagged with the singleton types of their keys. The library provides user-friendly mechanisms for constructing record _values_, however it is extremely laborious to express the corresponding _types_. Consider the following record value, ``` @@ -164,7 +165,7 @@ val book = HNil ``` -Using shapeless and current Scala the following would be required to give `book` an explicit type +Using shapeless and current Scala, the following would be required to give `book` an explicit type annotation, ``` val wAuthor = Witness("author") @@ -240,20 +241,20 @@ val c: Int Refined Greater[w6.T] = a ^ ``` -Under this proposal we can express these refinements much more succinctly, +Under this proposal, we can express these refinements much more succinctly, ``` val a: Int Refined Greater[5] = 10 val b: Int Refined Greater[4] = a ``` -Type level predicates of this kind have proved to be useful in practice and are supported by modules +Type-level predicates of this kind have proved to be useful in practice and are supported by modules of a [number of important libraries](https://github.com/fthomas/refined#external-modules). Experience with those libraries has led to a desire to compute directly over singleton types, in -effect to lift whole term-level expressions to the type-level which has resulted in the development +effect to lift whole term-level expressions to the type level, which has resulted in the development of the [singleton-ops](https://github.com/fthomas/singleton-ops) library. singleton-ops is built -with Typelevel Scala which allows it to use literal types as discussed in this SIP. +with Typelevel Scala, which allows it to use literal types, as discussed in this SIP. ``` import singleton.ops._ @@ -278,7 +279,7 @@ singleton-ops is used by a number of libraries, most notably our next motivating [Libra](https://github.com/to-ithaca/libra) is a a dimensional analysis library based on shapeless, spire and singleton-ops. It support SI units at the type level for all numeric types. Like -singleton-ops Libra is built using Typelevel Scala and so is able to use literal types as discussed +singleton-ops, Libra is built using Typelevel Scala and so is able to use literal types, as discussed in this SIP. Libra allows numeric computations to be checked for dimensional correctness as follows, @@ -323,7 +324,7 @@ case class Residue[M <: Int](n: Int) extends AnyVal { } ``` -Given this definition we can work with modular numbers without any danger of mixing numbers with +Given this definition, we can work with modular numbers without any danger of mixing numbers with different moduli, ``` @@ -341,7 +342,7 @@ fiveModTen + fourModEleven ``` Also note that the use of `ValueOf` as an implicit argument of `+` means that the modulus does not -need to be stored along with the `Int` in the `Residue` value which could be beneficial in +need to be stored along with the `Int` in the `Residue` value, which could be beneficial in applications which work with large datasets. ### Proposal details @@ -359,7 +360,7 @@ applications which work with large datasets. | ‘(’ Types ‘)’ ``` - Examples, + Examples: ``` val one: 1 = 1 // val declaration def foo(x: 1): Option[1] = Some(x) // param type, type arg @@ -367,7 +368,7 @@ applications which work with large datasets. foo(1: 1) // type ascription ``` -+ The restriction that the singleton type forming operator `.type` can only be appended to ++ The restriction that the singleton-type-forming operator `.type` can only be appended to stable paths designating a value which conforms to `AnyRef` is dropped -- the path may now conform to `Any`. Section [3.2.1](https://scala-lang.org/files/archive/spec/2.12/03-types.html#singleton-types) of the SLS is @@ -384,7 +385,7 @@ applications which work with large datasets. > denoted by `p` (i.e., the value `v` for which `v eq p`). Where the path does not conform to > `scala.AnyRef` the type denotes the set consisting of only the value denoted by `p`. - Example, + Example: ``` def foo[T](t: T): t.type = t final val bar = foo(23) // result is bar: 23 @@ -470,7 +471,7 @@ applications which work with large datasets. > corresponding to a singleton-apt definition, or (2) The upper bound Ui of Ti conforms to > `Singleton`. - Example, + Example: ``` type Id[A] = A def wide[T](t: T): Id[T] = t @@ -482,17 +483,17 @@ applications which work with large datasets. Note that we introduce the type constructor `Id` simply to avoid widening of the return type. + A `scala.ValueOf[T]` type class and corresponding `scala.Predef.valueOf[T]` operator has been - added yielding the unique value of types with a single inhabitant. + added, yielding the unique value of types with a single inhabitant. Type inference allows us to infer a singleton type from a literal value. It is natural to want to be able to go in the other direction and infer a value from a singleton type. This latter capability was exploited in the motivating `Residue` example given earlier, and is widely relied - on in current Scala in uses of shapeless's records, and `LabelledGeneric` based type class + on in current Scala in uses of shapeless's records, and `LabelledGeneric`-based type class derivation. - Implicit resolution is Scala's mechanism for inferring values from types and in current Scala + Implicit resolution is Scala's mechanism for inferring values from types, and in current Scala, shapeless provides a macro-based materializer for instances of its `Witness` type class. This SIP - adds a directly compiler supported type class as a replacement, + adds a directly compiler-supported type class as a replacement: ``` final class ValueOf[T](val value: T) extends AnyVal @@ -501,20 +502,20 @@ applications which work with large datasets. Instances are automatically provided for all types with a single inhabitant, which includes literal and non-literal singleton types and `Unit`. - Example, + Example: ``` def foo[T](implicit v: ValueOf[T]): T = v.value foo[13] // result is 13: Int ``` - A method `valueOf` is also added to `scala.Predef` analogously to existing operators such as + A method `valueOf` is also added to `scala.Predef`, analogously to existing operators such as `classOf`, `typeOf` etc. ``` def valueOf[T](implicit vt: ValueOf[T]): T = vt.value ``` - Example, + Example: ``` object Foo valueOf[Foo.type] // result is Foo: Foo.type @@ -530,11 +531,11 @@ applications which work with large datasets. where the `TypePat` is a literal type is translated as a match against the subsuming non-singleton type followed by an equality test with the value corresponding to the literal type. - Where applied to literal types `isInstanceOf` is translated to a test against + Where applied to literal types, `isInstanceOf` is translated to a test against the subsuming non-singleton type and an equality test with the value corresponding to the literal type. - Examples, + Examples: ``` (1: Any) match { case one: 1 => true @@ -543,28 +544,28 @@ applications which work with large datasets. (1: Any).isInstanceOf[1] // result is true: Boolean ``` - Importantly, that doesn't include `asInstanceOf` as that is a user assertion to the compiler, with + Importantly, that doesn't include `asInstanceOf`, as that is a user assertion to the compiler, with the compiler inserting in the generated code just enough code for the underlying runtime to not give a `ValidationError`. The compiler should not, for instance, generate code such that an expression like `(1: Any).asInstanceOf[2]` would throw a `ClassCastException`. + Default initialization for vars with literal types is forbidden. - The default initializer for a var is already mandated to be it's natural zero element (`0`, - `false`, `null` etc.). This is inconsistent with the var being given a non-zero literal type, + The default initializer for a var is already mandated to be its natural zero element (`0`, + `false`, `null` etc.). This is inconsistent with the var being given a non-zero literal type: ``` var bad: 1 = _ ``` - Whilst we could, in principle, provide an implicit non-default initializer for cases such as these + Whilst we could, in principle, provide an implicit non-default initializer for cases such as these, it is the view of the authors of this SIP that there is nothing to be gained from enabling this - construction and that default initializer should be forbidden. + construction, and that default initializer should be forbidden. -## Follow on work from this SIP +## Follow-on work from this SIP Whilst the authors of this SIP believe that it stands on its own merits, we think that there are two -areas where follow on work is desirable, and one area where another SIP might improve the implementation of SIP-23. +areas where follow-on work is desirable, and one area where another SIP might improve the implementation of SIP-23. ### Infix and prefix types @@ -572,7 +573,7 @@ areas where follow on work is desirable, and one area where another SIP might im has emerged from the work on refined types and computation over singleton types mentioned in the motivation section above. -Once literal types are available it is natural to want to lift entire expressions to the type level +Once literal types are available, it is natural to want to lift entire expressions to the type level as is done already in libraries such as [singleton-ops](https://github.com/fthomas/singleton-ops). However, the precedence and associativity of symbolic infix _type constructors_ don't match the precedence and associativity of symbolic infix _value operators_, and prefix type constructors don't @@ -581,13 +582,13 @@ terms. ### Byte and short literals -`Byte` and `Short` have singleton types, but lack any corresponding syntax either at the type or at the term level. -These types are important in libraries which deal with low level numerics and protocol implementation +`Byte` and `Short` have singleton types, but lack any corresponding syntax either at the type or at the term level. +These types are important in libraries which deal with low-level numerics and protocol implementation (see eg. [Spire](https://github.com/non/spire) and [Scodec](https://github.com/scodec/scodec)) and elsewhere, and the ability to, for instance, index a type class by a byte or short literal would be valuable. -A prototype of this syntax extension existed at an early stage in the development of Typelevel Scala +A prototype of this syntax extension existed at an early stage in the development of Typelevel Scala, but never matured. The possibility of useful literal types adds impetus. ### Opaque types @@ -609,7 +610,7 @@ would be elided, and the `valueOf[A]` method would be compiled to an identity fu ## Appendix 1 -- shapeless excerpts -Extracts from shapeless relevant to the motivating examples for this SIP, +Extracts from shapeless relevant to the motivating examples for this SIP: ``` trait Witness { diff --git a/_sips/sips/adding-prefix-types.md b/_sips/sips/adding-prefix-types.md new file mode 100644 index 0000000000..611baff9c0 --- /dev/null +++ b/_sips/sips/adding-prefix-types.md @@ -0,0 +1,6 @@ +--- +title: SIP-36 - Adding prefix types +status: withdrawn +pull-request-number: 35 + +--- diff --git a/_sips/sips/allow-referring-to-other-arguments-in-default-parameters.md b/_sips/sips/allow-referring-to-other-arguments-in-default-parameters.md new file mode 100644 index 0000000000..9eaec18d7b --- /dev/null +++ b/_sips/sips/allow-referring-to-other-arguments-in-default-parameters.md @@ -0,0 +1,6 @@ +--- +title: SIP-32 - Allow referring to other arguments in default parameters +status: rejected +pull-request-number: 29 + +--- diff --git a/_sips/sips/alternative-bind-variables.md b/_sips/sips/alternative-bind-variables.md new file mode 100644 index 0000000000..ab6899a026 --- /dev/null +++ b/_sips/sips/alternative-bind-variables.md @@ -0,0 +1,331 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: waiting-for-implementation +presip-thread: https://contributors.scala-lang.org/t/pre-sip-bind-variables-for-alternative-patterns/6321/13 +title: SIP-60 - Bind variables within alternative patterns +--- + +**By: Yilin Wei** + +## History + +| Date | Version | +|---------------|--------------------| +| Sep 17th 2023 | Initial Draft | +| Jan 16th 2024 | Amendments | + +## Summary + +Pattern matching is one of the most commonly used features in Scala by beginners and experts alike. Most of +the features of pattern matching compose beautifully — for example, a user who learns about bind variables +and guard patterns can mix the two features intuitively. + +One of the few outstanding cases where this is untrue, is when mixing bind variables and alternative patterns. The part of +current [specification](https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html) which we are concerned with is under section **8.1.12** and is copied below, with the relevant clause +highlighted. + +> … All alternative patterns are type checked with the expected type of the pattern. **They may not bind variables other than wildcards**. The alternative … + +We propose that this restriction be lifted and this corner case be eliminated. + +Removing the corner case would make the language easier to teach, reduce friction and allow users to express intent in a more natural manner. + +## Motivation + +## Scenario + +The following scenario is shamelessly stolen from [PEP 636](https://peps.python.org/pep-0636), which introduces pattern matching to the +Python language. + +Suppose a user is writing classic text adventure game such as [Zork](https://en.wikipedia.org/wiki/Zork). For readers unfamiliar with +text adventure games, the player typically enters freeform text into the terminal in the form of commands to interact with the game +world. Examples of commands might be `"pick up rabbit"` or `"open door"`. + +Typically, the commands are tokenized and parsed. After a parsing stage we may end up with a encoding which is similar to the following: + +```scala +enum Word + case Get, North, Go, Pick, Up + case Item(name: String) + + case class Command(words: List[Word]) +``` + +In this encoding, the string `pick up jar`, would be parsed as `Command(List(Pick, Up, Item("jar")))`. + +Once the command is parsed, we want to actually *do* something with the command. With this particular encoding, +we would naturally reach for a pattern match — in the simplest case, we could get away with a single recursive function for +our whole program. + +Suppose we take the simplest example where we want to match on a command like `"north"`. The pattern match consists of +matching on a single stable identifier, `North` and the code would look like this: + +~~~ scala +import Command.* + +def loop(cmd: Command): Unit = + cmd match + case Command(North :: Nil) => // Code for going north +~~~ + +However as we begin play-testing the actual text adventure, we observe that users type `"go north"`. We decide +our program should treat the two distinct commands as synonyms. At this point we would reach for an alternative pattern `|` and +refactor the code like so: + +~~~ scala + case Command(North :: Nil | Go :: North :: Nil) => // Code for going north +~~~ + +This clearly expresses our intent that the two commands map to the same underlying logic. + +Later we decide that we want more complex logic in our game; perhaps allowing the user to pick up +items with a command like `pick up jar`. We would then extend our function with another case, binding the variable `name`: + +~~~ scala + case Command(Pick :: Up :: Item(name) :: Nil) => // Code for picking up items +~~~ + +Again, we might realise through our play-testing that users type `get` as a synonym for `pick up`. After playing around +with alternative patterns, we may reasonably write something like: + +~~~ scala + case Command(Pick :: Up :: Item(name) :: Nil | Get :: Item(name) :: Nil) => // Code for picking up items +~~~ + +Unfortunately at this point, we are stopped in our tracks by the compiler. The bind variable for `name` cannot be used in conjunction with alternative patterns. +We must either choose a different encoding. We carefully consult the specification and that this is not possible. + +We can, of course, work around it by hoisting the logic to a helper function to the nearest scope which function definitions: + +~~~ scala +def loop(cmd: Cmd): Unit = + def pickUp(item: String): Unit = // Code for picking up item + cmd match + case Command(Pick :: Up :: Item(name)) => pickUp(name) + case Command(Get :: Item(name)) => pickUp(name) +~~~ + +Or any number of different encodings. However, all of them are less intuitive and less obvious than the code we tried to write. + +## Commentary + +Removing the restriction leads to more obvious encodings in the case of alternative patterns. Arguably, the language +would be simpler and easier to teach — we do not have to remember that bind patterns and alternatives +do not mix and need to teach newcomers the workarounds. + +For languages which have pattern matching, a significant number also support the same feature. Languages such as [Rust](https://github.com/rust-lang/reference/pull/957) and [Python](https://peps.python.org/pep-0636/#or-patterns) have +supported it for some time. While +this is not a great reason for Scala to do the same, having the feature exist in other languages means that users +that are more likely to expect the feature. + +A smaller benefit for existing users, is that removing the corner case leads to code which is +easier to review; the absolute code difference between adding a bind variable within an alternative versus switching to a different +encoding entirely is smaller and conveys the intent of such changesets better. + +It is acknowledged, however, that such cases where we share the same logic with an alternative branches are relatively rare compared to +the usage of pattern matching in general. The current restrictions are not too arduous to workaround for experienced practitioners, which +can be inferred from the relatively low number of comments from the original [issue](https://github.com/scala/bug/issues/182) first raised in 2007. + +To summarize, the main arguments for the proposal are to make the language more consistent, simpler and easier to teach. The arguments +against a change are that it will be low impact for the majority of existing users. + +## Proposed solution + +Removing the alternative restriction means that we need to specify some additional constraints. Intuitively, we +need to consider the restrictions on variable bindings within each alternative branch, as well as the types inferred +for each binding within the scope of the pattern. + +## Bindings + +The simplest case of mixing an alternative pattern and bind variables, is where we have two `UnApply` methods, with +a single alternative pattern. For now, we specifically only consider the case where each bind variable is of the same +type, like so: + +~~~ scala +enum Foo: + case Bar(x: Int) + case Baz(y: Int) + + def fun = this match + case Bar(z) | Baz(z) => ... // z: Int +~~~ + +For the expression to make sense with the current semantics around pattern matches, `z` must be defined in both branches; otherwise the +case body would be nonsensical if `z` was referenced within it (see [missing variables](#missing-variables) for a proposed alternative). + +Removing the restriction would also allow recursive alternative patterns: + +~~~ scala +enum Foo: + case Bar(x: Int) + case Baz(x: Int) + +enum Qux: + case Quux(y: Int) + case Corge(x: Foo) + + def fun = this match + case Quux(z) | Corge(Bar(z) | Baz(z)) => ... // z: Int +~~~ + +Using an `Ident` within an `UnApply` is not the only way to introduce a binding within the pattern scope. +We also expect to be able to use an explicit binding using an `@` like this: + +~~~ scala +enum Foo: + case Bar() + case Baz(bar: Bar) + + def fun = this match + case Baz(x) | x @ Bar() => ... // x: Foo.Bar +~~~ + +## Types + +We propose that the type of each variable introduced in the scope of the pattern be the least upper-bound of the type +inferred within within each branch. + +~~~ scala +enum Foo: + case Bar(x: Int) + case Baz(y: String) + + def fun = this match + case Bar(x) | Baz(x) => // x: Int | String +~~~ + +We do not expect any inference to happen between branches. For example, in the case of a GADT we would expect the second branch of +the following case to match all instances of `Bar`, regardless of the type of `A`. + +~~~ scala +enum Foo[A]: + case Bar(a: A) + case Baz(i: Int) extends Foo[Int] + + def fun = this match + case Baz(x) | Bar(x) => // x: Int | A +~~~ + +### Given bind variables + +It is possible to introduce bindings to the contextual scope within a pattern match branch. + +Since most bindings will be anonymous but be referred to within the branches, we expect the _types_ present in the contextual scope for each branch to be the same rather than the _names_. + +~~~ scala + case class Context() + + def run(using ctx: Context): Unit = ??? + + enum Foo: + case Bar(ctx: Context) + case Baz(i: Int, ctx: Context) + + def fun = this match + case Bar(given Context) | Baz(_, given Context) => run // `Context` appears in both branches +~~~ + +This begs the question of what to do in the case of an explicit `@` binding where the user binds a variable to the same _name_ but to different types. We can either expose a `String | Int` within the contextual scope, or simply reject the code as invalid. + +~~~ scala + enum Foo: + case Bar(s: String) + case Baz(i: Int) + + def fun = this match + case Bar(x @ given String) | Baz(x @ given Int) => ??? +~~~ + +To be consistent with the named bindings, we argue that the code should compile and a contextual variable added to the scope with the type of `String | Int`. + +### Quoted patterns + +[Quoted patterns](https://docs.scala-lang.org/scala3/guides/macros/quotes.html#quoted-patterns) will not be supported in this SIP and the behaviour of quoted patterns will remain the same as currently i.e. any quoted pattern appearing in an alternative pattern binding a variable or type variable will be rejected as illegal. + +### Alternatives + +#### Enforcing a single type for a bound variable + +We could constrain the type for each bound variable within each alternative branch to be the same type. Notably, this is what languages such as Rust, which do not have sub-typing do. + +However, since untagged unions are part of Scala 3 and the fact that both are represented by the `|`, it felt more natural to discard this restriction. + +#### Type ascriptions in alternative branches + +Another suggestion is that an _explicit_ type ascription by a user ought to be defined for all branches. For example, in the currently proposed rules, the following code would infer the return type to be `Int | A` even though the user has written the statement `id: Int`. + +~~~scala +enum Foo[A]: + case Bar[A](a: A) + case Baz[A](a: A) + + def test = this match + case Bar(id: Int) | Baz(id) => id +~~~ + +In the author's subjective opinion, it is more natural to view the alternative arms as separate branches — which would be equivalent to the function below. + +~~~scala +def test = this match + case Bar(id: Int) => id + case Baz(id) => id +~~~ + +On the other hand, if it is decided that each bound variable ought to be the same type, then arguably "sharing" explicit type ascriptions across branches would reduce boilerplate. + +#### Missing variables + +Unlike in other languages, we could assign a type, `A | Null`, to a bind variable which is not present in all of the alternative branches. Rust, for example, is constrained by the fact that the size of a variable must be known and untagged unions do not exist. + +Arguably, missing a variable entirely is more likely to be an error — the absence of a requirement for `var` declarations before assigning variables in Python means that beginners can easily assign variables to the wrong variable. + +It may be, that the enforcement of having to have the same bind variables within each branch ought to be left to a linter rather thana a hard restriction within the language itself. + +## Specification + +We do not believe there are any syntax changes since the current specification already allows the proposed syntax. + +We propose that the following clauses be added to the specification: + +Let $`p_1 | \ldots | p_n`$ be an alternative pattern at an arbitrary depth within a case pattern and $`\Gamma_n`$ is the named scope associated with each alternative. + +If `p_i` is a quoted pattern binding a variable or type variable, the alternative pattern is considered invalid. Otherwise, let the named variables introduced within each alternative $`p_n`$, be $`x_i \in \Gamma_n`$ and the unnamed contextual variables within each alternative have the type $`T_i \in \Gamma_n`$. + +Each $`p_n`$ must introduce the same set of bindings, i.e. for each $`n`$, $`\Gamma_n`$ must have the same **named** members $`\Gamma_{n+1}`$ and the set of $`{T_0, ... T_n}`$ must be the same. + +If $`X_{n,i}`$, is the type of the binding $`x_i`$ within an alternative $`p_n`$, then the consequent type, $`X_i`$, of the +variable $`x_i`$ within the pattern scope, $`\Gamma`$ is the least upper-bound of all the types $`X_{n, i}`$ associated with +the variable, $`x_i`$ within each branch. + +## Compatibility + +We believe the changes would be backwards compatible. + +# Related Work + +The language feature exists in multiple languages. Of the more popular languages, Rust added the feature in [2021](https://github.com/rust-lang/reference/pull/957) and +Python within [PEP 636](https://peps.python.org/pep-0636/#or-patterns), the pattern matching PEP in 2020. Of course, Python is untyped and Rust does not have sub-typing +but the semantics proposed are similar to this proposal. + +Within Scala, the [issue](https://github.com/scala/bug/issues/182) first raised in 2007. The author is also aware of attempts to fix this issue by [Lionel Parreaux](https://github.com/dotty-staging/dotty/compare/main...LPTK:dotty:vars-in-pat-alts) and the associated [feature request](https://github.com/lampepfl/dotty-feature-requests/issues/12) which +was not submitted to the main dotty repository. + +The associated [thread](https://contributors.scala-lang.org/t/pre-sip-bind-variables-for-alternative-patterns/6321) has some extra discussion around semantics. Historically, there have been multiple similar suggestions — in [2023](https://contributors.scala-lang.org/t/qol-sound-binding-in-pattern-alternatives/6226) by Quentin Bernet and in [2021](https://contributors.scala-lang.org/t/could-it-be-possible-to-allow-variable-binging-in-patmat-alternatives-for-scala-3-x/5235) by Alexey Shuksto. + +## Implementation + +The author has a current in-progress implementation focused on the typer which compiles the examples with the expected types. Interested + parties are welcome to see the WIP [here](https://github.com/scala/scala3/compare/main...yilinwei:dotty:main). + +### Further work + +#### Quoted patterns + +More investigation is needed to see how quoted patterns with bind variables in alternative patterns could be supported. + +## Acknowledgements + +Many thanks to **Zainab Ali** for proof-reading the draft, **Nicolas Stucki** and **Guillaume Martres** for their pointers on the dotty +compiler codebase. diff --git a/_sips/sips/async.md b/_sips/sips/async.md new file mode 100644 index 0000000000..49448af0cb --- /dev/null +++ b/_sips/sips/async.md @@ -0,0 +1,6 @@ +--- +title: SIP-22 - Async +status: withdrawn +pull-request-number: 21 + +--- diff --git a/_sips/sips/better-fors.md b/_sips/sips/better-fors.md new file mode 100644 index 0000000000..10cfa733ea --- /dev/null +++ b/_sips/sips/better-fors.md @@ -0,0 +1,481 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +title: SIP-62 - For comprehension improvements +--- + +**By: Kacper Korban (VirtusLab)** + +## History + +| Date | Version | +|---------------|------------------------| +| June 6th 2023 | Initial Draft | +| Feb 15th 2024 | Reviewed Version | +| Nov 21th 2024 | Addendum for change 3. | + +## Summary + +`for`-comprehensions in Scala 3 improved their usability in comparison to Scala 2, but there are still some pain points relating both usability of `for`-comprehensions and simplicity of their desugaring. + +This SIP tries to address some of those problems, by changing the specification of `for`-comprehensions. From user perspective, the biggest change is allowing aliases at the start of the `for`-comprehensions. e.g. + +``` +for { + x = 1 + y <- Some(2) +} yield x + y +``` + +## Motivation + +There are some clear pain points related to Scala'3 `for`-comprehensions and those can be divided into two categories: + +1. User-facing and code simplicity problems + + Specifically, for the following example written in a Haskell-style do-comprehension + + ```haskell + do + a = largeExpr(arg) + b <- doSth(a) + combineM(a, b) + ``` + in Scala we would have to write + + ```scala + val a = largeExpr(b) + for + b <- doSth(a) + x <- combineM(a, b) + yield x + ``` + + This complicates the code, even in this simple example. +2. The simplicity of desugared code + + The second pain point is that the desugared code of `for`-comprehensions can often be surprisingly complicated. + + e.g. + ```scala + for + a <- doSth(arg) + b = a + yield a + b + ``` + + Intuition would suggest for the desugared code will be of the form + + ```scala + doSth(arg).map { a => + val b = a + a + b + } + ``` + + But because of the possibility of an `if` guard being immediately after the pure alias, the desugared code is of the form + + ```scala + doSth(arg).map { a => + val b = a + (a, b) + }.map { case (a, b) => + a + b + } + ``` + + These unnecessary assignments and additional function calls not only add unnecessary runtime overhead but can also block other optimizations from being performed. + +## Proposed solution + +This SIP suggests the following changes to `for` comprehensions: + +1. Allow `for` comprehensions to start with pure aliases + + e.g. + ```scala + for + a = 1 + b <- Some(2) + c <- doSth(a) + yield b + c + ``` +2. Simpler conditional desugaring of pure aliases. i.e. whenever a series of pure aliases is not immediately followed by an `if`, use a simpler way of desugaring. + + e.g. + ```scala + for + a <- doSth(arg) + b = a + yield a + b + ``` + + will be desugared to + + ```scala + doSth(arg).map { a => + val b = a + a + b + } + ``` + + but + + ```scala + for + a <- doSth(arg) + b = a + if b > 1 + yield a + b + ``` + + will be desugared to + + ```scala + doSth(arg).map { a => + val b = a + (a, b) + }.withFilter { case (a, b) => + b > 1 + }.map { case (a, b) => + a + b + } + ``` + +3. Avoiding redundant `map` calls if the yielded value is the same as the last bound value. + + e.g. + ```scala + for + a <- List(1, 2, 3) + yield a + ``` + + will just be desugared to + + ```scala + List(1, 2, 3) + ``` + +### Detailed description + +#### Ad 1. Allow `for` comprehensions to start with pure aliases + +Allowing `for` comprehensions to start with pure aliases is a straightforward change. + +The Enumerators syntax will be changed from: + +``` +Enumerators ::= Generator {semi Enumerator | Guard} +``` + +to + +``` +Enumerators ::= {Pattern1 `=' Expr semi} Generator {semi Enumerator | Guard} +``` + +Which will allow adding 0 or more aliases before the first generator. + +When desugaring is concerned, a for comprehension starting with pure aliases will generate a block with those aliases as `val` declarations and the rest of the desugared `for` as an expression. Unless the aliases are followed by a guard, then the desugaring should result in an error. + +New desugaring rule will be added: + +```scala +For any N: + for (P_1 = E_1; ... P_N = E_N; ...) + ==> + { + val x_2 @ P_2 = E_2 + ... + val x_N @ P_N = E_N + for (...) + } +``` + +e.g. + +```scala +for + a = 1 + b <- Some(2) + c <- doSth(a) +yield b + c +``` + +will desugar to + +```scala +{ + val a = 1 + for + b <- Some(2) + c <- doSth(a) + yield b + c +} +``` + +#### Ad 2. Simpler conditional desugaring of pure aliases. i.e. whenever a series of pure aliases is not immediately followed by an `if`, use a simpler way of desugaring. + +Currently, for consistency, all pure aliases are desugared as if they are followed by an `if` condition. Which makes the desugaring more complicated than expected. + +e.g. + +The following code: + +```scala +for + a <- doSth(arg) + b = a +yield a + b +``` + +will be desugared to: + +```scala +doSth(arg).map { a => + val b = a + (a, b) +}.map { case (a, b) => + a + b +} +``` + +The proposed change is to introduce a simpler desugaring for common cases, when aliases aren't followed by a guard, and keep the old desugaring method for the other cases. + +A new desugaring rules will be introduced for simple desugaring. + +```scala +For any N: + for (P <- G; P_1 = E_1; ... P_N = E_N; ...) + ==> + G.flatMap (P => for (P_1 = E_1; ... P_N = E_N; ...)) + +And: + + for () yield E ==> E + +(Where empty for-comprehensions are excluded by the parser) +``` + +It delegares desugaring aliases to the newly introduced rule from the previous impreovement. i.e. + +```scala +For any N: + for (P_1 = E_1; ... P_N = E_N; ...) + ==> + { + val x_2 @ P_2 = E_2 + ... + val x_N @ P_N = E_N + for (...) + } +``` + +One other rule also has to be changed, so that the current desugaring method, of passing all the aliases in a tuple with the result, will only be used when desugaring a generator, followed by some aliases, followed by a guard. + +```scala +For any N: + for (P <- G; P_1 = E_1; ... P_N = E_N; if E; ...) + ==> + for (TupleN(P, P_1, ... P_N) <- + for (x @ P <- G) yield { + val x_1 @ P_1 = E_2 + ... + val x_N @ P_N = E_N + TupleN(x, x_1, ..., x_N) + }; if E; ...) +``` + +This changes will make the desugaring work in the following way: + +```scala +for + a <- doSth(arg) + b = a +yield a + b +``` + +will be desugared to + +```scala +doSth(arg).map { a => + val b = a + a + b +} +``` + +but + +```scala +for + a <- doSth(arg) + b = a + if b > 1 +yield a + b +``` + +will be desugared to + +```scala +doSth(arg).map { a => + val b = a + (a, b) +}.withFilter { case (a, b) => + b > 1 +}.map { case (a, b) => + a + b +} +``` + +#### Ad 3. Avoiding redundant `map` calls if the yielded value is the same as the last bound value. + +This change is strictly an optimization. This allows for the compiler to get rid of the final `map` call, if the yielded value is the same as the last bound pattern. The pattern can be either a single variable binding or a tuple. + +This optimization should be done after type checking (e.g. around first transform). See the reasons to why it cannot be done in desugaring in [here](#previous-design-in-desugaring). + +We propose an approach where an attachment (`TrailingForMap`) is attached to the last `map` `Apply` node. After that, a later phase will look for `Apply` nodes with this attachment and possibly remove the `map` call. + +The condition for allowing to remove the last map call (for a binding `pat <- gen yield pat1`) are as follows: +- `pat` is (syntactically) equivalent to `pat1` ($pat =_{s} pat1$) + + where + + $x =_{s} x, \text{if x is a variable reference}$ + + $x =_{s} (), \text{if x is a variable reference of type Unit}$ + + $(x_1, ..., x_n) =_{s} (y_1, ..., y_n) \iff \forall i \in n.\; x_i =_{s} y_i$ + + This means that the two patterns are equivalent if they are the same variable, if they are tuples of the same variables, or if one is a variable reference of type `Unit` and the other is a `Unit` literal. +- `pat` and `pat1` have the same types (`pat.tpe` =:= `pat1.tpe`) + +##### Changes discussion + +This adresses the problem of changing the resulting type after removing trailing `map` calls. + +There are two main changes compared to the previous design: +1. Moving the implementation to the later phase, to be able to use the type information and explicitly checking that the types are the same. +2. Allowing to remove the last `map` call if the yielded value is a `Unit` literal (and obviously the type doesn't change). + +The motivation for the second change is to avoid potential memory leaks in effecting loops. e.g. + +```scala +//> using scala 3.3.3 +//> using lib "dev.zio::zio:2.1.5" + +import zio.* + +def loop: Task[Unit] = + for + _ <- Console.print("loop") + _ <- loop + yield () + +@main +def run = + val runtime = Runtime.default + Unsafe.unsafe { implicit unsafe => + runtime.unsafe.run(loop).getOrThrowFiberFailure() + } +``` + +This kind of effect loop is pretty commonly used in Scala FP programs and often ends in `yield ()`. + +The problem with the desugaring of this for-comprehensions is that it leaks memory because the result of `loop` has to be mapped over with `_ => ()`, which often does nothing. + +##### Previous design (in desugaring) + +One desugaring rule has to be modified for this purpose. + +```scala + for (P <- G) yield P ==> G +If P is a variable or a tuple of variables and G is not a withFilter. + + for (P <- G) yield E ==> G.map (P => E) +Otherwise +``` + +e.g. +```scala +for + a <- List(1, 2, 3) +yield a +``` + +will just be desugared to + +```scala +List(1, 2, 3) +``` + +**Cause of change** + +This design ended up breaking quite a few existing projects in the open community build run. + +For example, consider the following code: + +```scala +//> using scala 3.nightly + +import scala.language.experimental.betterFors + +case class Container[A](val value: A) { + def map[B](f: A => B): Container[B] = Container(f(value)) +} + +sealed trait Animal +case class Dog() extends Animal + +def opOnDog(dog: Container[Dog]): Container[Animal] = + for + v <- dog + yield v +``` + +With the new desugaring, the code gave an error about type mismatch. + +```scala +-- [E007] Type Mismatch Error: /home/kpi/bugs/better-fors-bug.scala:13:2 ------- +13 | for + | ^ + | Found: (dog : Container[Dog]) + | Required: Container[Animal] +14 | v <- dog +15 | yield v + | + | longer explanation available when compiling with `-explain` +``` + +This is because the container is invariant. And even though the last `map` was an identity function, it was used to upcast `Dog` to `Animal`. + +### Compatibility + +This change may change the semantics of some programs. It may remove some `map` calls in the desugared code, which may change the program semantics (if the `map` implementation was side-effecting). + +For example the following code will now have only one `map` call, instead of two: +```scala +for + a <- doSth(arg) + b = a +yield a + b +``` + +### Other concerns + +As far as I know, there are no widely used Scala 3 libraries that depend on the desugaring specification of `for`-comprehensions. + +The only Open community build library that failed because of the change to the desugaring specification is [`avocADO`](https://github.com/VirtusLab/avocado). + +## Links + +1. Scala contributors discussion thread (pre-SIP): https://contributors.scala-lang.org/t/pre-sip-improve-for-comprehensions-functionality/3509/51 +2. Github issue discussion about for desugaring: https://github.com/scala/scala3/issues/2573 +3. Scala 2 implementation of some of the improvements: https://github.com/oleg-py/better-monadic-for +4. Implementation of one of the simplifications: https://github.com/scala/scala3/pull/16703 +5. Draft implementation branch: https://github.com/dotty-staging/dotty/tree/improved-fors +6. Minimized issue reproducing the problem with the current desugaring: https://github.com/scala/scala3/issues/21804 +7. (empty :sad:) Contributors thread about better effect loops with for-comprehensions: https://contributors.scala-lang.org/t/pre-sip-sip-62-addition-proposal-better-effect-loops-with-for-comprehensions/6759 +8. Draft implementation of dropping the last map call after type checking (only for `Unit` literals): https://github.com/KacperFKorban/dotty/commit/31cbd4744b9375443a0770a8b8a9d16de694c6bb#diff-ed248bb93940ea4f38e6da698051f882e81df6f33fea91a046d1d4f6af506296R2066 diff --git a/_sips/sips/binary-api.md b/_sips/sips/binary-api.md new file mode 100644 index 0000000000..e94bf85a54 --- /dev/null +++ b/_sips/sips/binary-api.md @@ -0,0 +1,279 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +title: SIP-52 - Binary APIs +--- + +**By: Author Nicolas Stucki** + +## History + +| Date | Version | +|---------------|------------------------| +| Feb 27 2023 | Initial Draft | +| Aug 16 2023 | Single Annotation | +| Aug 24 2023 | Change Annotation Name | +| Jan 09 2024 | Change Overload Rules | +| Feb 29 2024 | Experimental in [Scala 3.4.0](https://www.scala-lang.org/blog/2024/02/29/scala-3.4.0-and-3.3.3-released.html) | + +## Summary + +The purpose of binary APIs is to have publicly accessible definitions in generated bytecode for definitions that are package private or protected. +This proposal introduces the `@publicInBinary` annotation on term definitions and the `-WunstableInlineAccessors` linting flag. + + +## Motivation + +### Provide a sound way to refer to private members in inline definitions + +Currently, the compiler automatically generates accessors for references to private members in inline definitions. This scheme interacts poorly with binary compatibility. It causes the following three unsoundness in the system: +* Changing any definition from private to public is a binary incompatible change +* Changing the implementation of an inline definition can be a binary incompatible change +* Removing final from a class is a binary incompatible change + +You can find more details in [https://github.com/scala/scala3/issues/16983](https://github.com/scala/scala3/issues/16983) + +### Avoid duplication of inline accessors + +Ideally, private definitions should have a maximum of one inline accessor, which is not the case now. +When an inline method accesses a private/protected definition that is defined outside of its class, we generate an inline in the class of the inline method. This implies that accessors might be duplicated if a private/protected definition is accessed from different classes. + +### Removing deprecated APIs + +There is no precise mechanism to remove a deprecated method from a library without causing binary incompatibilities. We should have a straightforward way to indicate that a method is no longer publicly available but still available in the generated code for binary compatibility. + +```diff +- @deprecated(...) def myOldAPI: T = ... ++ private[C] def myOldAPI: T = ... +``` + +Related to discussion in [https://github.com/lightbend/mima/discussions/724](https://github.com/lightbend/mima/discussions/724). + +### No way to inline reference to private constructors + +It is currently impossible to refer to private constructors in inline methods. +```scala +class C private() +object C: + inline def newC: C = new C() // Implementation restriction: cannot use private constructors in inline methods +``` +If users want to access one of those, they must write an accessor explicitly. This extra indirection is undesirable. +```scala +class C private() +object C: + private def newCInternal: C = new C() + inline def newC: C = newCInternal +``` + +## Proposed solution + +### High-level overview + +This proposal introduces the `@publicInBinary` annotation, and adds a migration path to inline methods in libraries (requiring binary compatibility). + +#### `@publicInBinary` annotation + +A binary API is a definition that is annotated with `@publicInBinary`. +This annotation can be placed on `def`, `val`, `lazy val`, `var`, `object`, and `given` definitions. +A binary API will be publicly available in the bytecode. + +This annotation cannot be used on `private`/`private[this]` definitions. With the exception of class constructors. + +Removing this annotation from a non-public definition is a binary incompatible change. + +Example: + +~~~ scala +class C { + @publicInBinary private[C] def packagePrivateAPI: Int = ... + @publicInBinary protected def protectedAPI: Int = ... + @publicInBinary def publicAPI: Int = ... // warn: `@publicInBinary` has no effect on public definitions +} +~~~ +will generate the following bytecode signatures +~~~ java +public class C { + public C(); + public int packagePrivateAPI(); + public int protectedAPI(); + public int publicAPI(); +} +~~~ + +In the bytecode, `@publicInBinary` definitions will have the [ACC_PUBLIC](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1) flag. + + +#### Binary API and inlining + +A non-public reference in an inline method is handled as follows: + - if the reference is a `@publicInBinary` the reference is used; + - otherwise, an accessor is automatically generated and used. + +Example: +~~~ scala +import scala.annotation.publicInBinary +class C { + @publicInBinary private[C] def a: Int = ... + private[C] def b: Int = ... + @publicInBinary protected def c: Int = ... + protected def d: Int = ... + inline def foo: Int = a + b + c + d +} +~~~ +before inlining the compiler will generate the accessors for inlined definitions +~~~ scala +class C { + @publicInBinary private[C] def a: Int = ... + private[C] def b: Int = ... + @publicInBinary protected def c: Int = ... + protected def d: Int = ... + final def C$$inline$b: Int = ... + final def C$$inline$d: Int = ... + inline def foo: Int = a + C$$inline$b + c + C$$inline$d +} +~~~ + +##### `-WunstableInlineAccessors` + +In addition we introduce the `-WunstableInlineAccessors` flag to allow libraries to detect when the compiler generates unstable accessors. +The previous code would show a linter warning that looks like this: + +~~~ +-- [E...] Compatibility Warning: C.scala ----------------------------- + | inline def foo: Int = a + b + c + d + | ^ + | Unstable inline accessor C$$inline$b was generated in class C. + | + | longer explanation available when compiling with `-explain` +-- [E...] Compatibility Warning: C.scala ----------------------------- + | inline def foo: Int = a + b + c + d + | ^ + | Unstable inline accessor C$$inline$d was generated in class C. + | + | longer explanation available when compiling with `-explain` +~~~ + +When an accessor is detected we can tell the user how to fix the issue. For example we could use the `-explain` flag to add the following details to the message. + +
+With `-WunstableInlineAccessors -explain` + +~~~ +-- [E...] Compatibility Warning: C.scala ----------------------------- + | inline def foo: Int = a + b + c + d + | ^ + | Unstable inline accessor C$$inline$b was generated in class C. + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public method b causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | method b is public in the binary API. + | * Option 1: Annotate method b with @publicInBinary + | * Option 2: Make method b public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class C: + | @publicInBinary private[C] def C$$inline$b: Int = this.b + ----------------------------------------------------------------------------- +-- [E...] Compatibility Warning: C.scala ----------------------------- + | inline def foo: Int = a + b + c + d + | ^ + | Unstable inline accessor C$$inline$d was generated in class C. + |----------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public method d causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | method d is public in the binary API. + | * Option 1: Annotate method d with @publicInBinary + | * Option 2: Make method d public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class C: + | @publicInBinary private[C] def C$$inline$d: Int = this.d + ----------------------------------------------------------------------------- +~~~ + +
+ +### Specification + +We must add `publicInBinary` to the standard library. + +```scala +package scala.annotation + +final class publicInBinary extends scala.annotation.StaticAnnotation +``` + +#### `@publicInBinary` annotation + +* Only valid on `def`, `val`, `lazy val`, `var`, `object`, and `given`. +* If a definition overrides a `@publicInBinary` definition, it must also be annotated with `@publicInBinary`. +* TASTy will contain references to non-public definitions that are out of scope but `@publicInBinary`. TASTy already allows those references. +* The annotated definitions will be public in the generated bytecode. Definitions should be made public as early as possible in the compiler phases, as this can remove the need to create other accessors. It should be done after we check the accessibility of references. + +#### Inline + +* Inlining will not require the generation of an inline accessor for binary APIs. +* The user will be warned if a new inline accessor is automatically generated under `-WunstableInlineAccessors`. + The message will suggest `@publicInBinary` and how to fix potential incompatibilities. + +### Compatibility + +The introduction of the `@publicInBinary` do not introduce any binary incompatibility. + +Using references to `@publicInBinary` in inline code can cause binary incompatibilities. These incompatibilities are equivalent to the ones that can occur due to the unsoundness we want to fix. When migrating to binary APIs, the compiler will show the implementation of accessors that the users need to add to keep binary compatibility with pre-publicInBinary code. + +### Other concerns + +* Tools that analyze inlined TASTy code will need to know about `@publicInBinary`. For example [MiMa](https://github.com/lightbend/mima/) and [TASTy MiMa](https://github.com/scalacenter/tasty-mima). + +## Alternatives + +### Add a `@binaryAccessor` +This annotation would generate an stable accessor. This annotation could be used on `private` definition. It would also mitigate [migration costs](https://gist.github.com/nicolasstucki/003f7293941836b08a0d53dbcb913e3c) for library authors that have published unstable accessors. + +* Implementation https://github.com/scala/scala3/pull/16992 + + +### Make all `private[C]` part of the binary API + +Currently, we already make `private[C]` public in the binary API but do not have the same guarantees regarding binary compatibility. +For example, the following change is binary compatible but would remove the existence of the `private[C]` definition in the bytecode. +```diff +class C: +- private[C] def f: T = ... +``` +We could change the rules to make all `private[C]` part of binary compatible to flag such a change as binary incompatible. This would imply that all these +methods can be accessed directly from inline methods without generating an accessor. + +The drawback of this approach is that that we would need to force users to keep their `private[C]` methods even if they never used inline methods. + + +## Related work + +* Initial discussions: [https://github.com/scala/scala3/issues/16983](https://github.com/scala/scala3/issues/16983) +* Initial proof of concept (outdated): [https://github.com/scala/scala3/pull/16992](https://github.com/scala/scala3/pull/16992) +* Single annotation proof of concept: [https://github.com/scala/scala3/pull/18402](https://github.com/scala/scala3/pull/18402) +* Community migration analysis: [Gist](https://gist.github.com/nicolasstucki/003f7293941836b08a0d53dbcb913e3c) +* Kotlin: [PublishedApi](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-published-api/) plays the same role as `@publicInBinary` + but its interaction with (inline definitions)[https://kotlinlang.org/docs/inline-functions.html#restrictions-for-public-api-inline-functions] + is stricter as they do not support automatic accessor generation. + + diff --git a/_sips/sips/2019-07-27-binary-integer-literals.md b/_sips/sips/binary-integer-literals.md similarity index 94% rename from _sips/sips/2019-07-27-binary-integer-literals.md rename to _sips/sips/binary-integer-literals.md index da803bd5ae..ef761601fb 100644 --- a/_sips/sips/2019-07-27-binary-integer-literals.md +++ b/_sips/sips/binary-integer-literals.md @@ -1,7 +1,8 @@ --- layout: sip -title: SIP-NN - Support Binary Integer Literals -vote-status: pending +title: SIP-42 - Support Binary Integer Literals +stage: completed +status: shipped permalink: /sips/:title.html --- diff --git a/_sips/sips/2017-11-20-byname-implicits.md b/_sips/sips/byname-implicits.md similarity index 99% rename from _sips/sips/2017-11-20-byname-implicits.md rename to _sips/sips/byname-implicits.md index db735776a6..fd63269068 100644 --- a/_sips/sips/2017-11-20-byname-implicits.md +++ b/_sips/sips/byname-implicits.md @@ -1,11 +1,14 @@ --- layout: sip -title: SIP-NN - Byname implicit arguments -vote-status: pending +title: SIP-31 - Byname implicit arguments +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/byname-implicits.html --- +> This proposal has been implemented in Scala 2.13.0 and Scala 3.0.0. + **Author: Miles Sabin** **Supervisor and advisor: Martin Odersky** @@ -36,7 +39,7 @@ the knot" implicitly. ### Implementation status -Byname implicits have been implemented in [Dotty](https://github.com/lampepfl/dotty/issues/1998) +Byname implicits have been implemented in [Dotty](https://github.com/scala/scala3/issues/1998) with an earlier iteration of the divergence checking algorithm described below. A full implementation of this proposal exists as a [pull request](https://github.com/scala/scala/pull/6050) relative to the 2.13.x branch of the Lightbend Scala compiler and it is scheduled to be included in @@ -164,7 +167,7 @@ object Semigroup { } } ``` - + then we can manually write instances for, for example, tuples of types which have `Semigroup` instances, @@ -384,7 +387,7 @@ val showListInt: Show[List[Int]] = showUnit ) ) -``` +``` where at least one argument position between the val definition and the recursive occurrence of `showListInt` is byname. @@ -496,16 +499,16 @@ any _Tj_, where _i_ < _j_. The essence of the algorithm described in the Scala Language Specification is as follows, > Call the sequence of open implicit types _O_. This is initially empty. -> -> To resolve an implicit of type _T_ given stack of open implicits _O_, -> +> +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> > + Identify the definition _d_ which satisfies _T_. -> +> > + If the core type of _T_ dominates any element of _O_ then we have observed divergence and we're > done. -> +> > + If _d_ has no implicit arguments then the result is the value yielded by _d_. -> +> > + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+T_, and the result is the > value yielded by _d_ applied to its resolved arguments. @@ -547,15 +550,15 @@ divergence check across the set of relevant implicit definitions. This gives us the following, -> To resolve an implicit of type _T_ given stack of open implicits _O_, -> +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> > + Identify the definition _d_ which satisfies _T_. -> +> > + If the core type of _T_ dominates the type _U_ of some element __ of _O_ then we have > observed divergence and we're done. -> +> > + If _d_ has no implicit arguments then the result is the value yielded by _d_. -> +> > + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+_, and the result is > the value yielded by _d_ applied to its resolved arguments. @@ -643,8 +646,8 @@ larger than _U_ despite using only elements that are present in _U_. This gives us the following, -> To resolve an implicit of type _T_ given stack of open implicits _O_, -> +> To resolve an implicit of type _T_ given stack of open implicits _O_, +> > + Identify the definition _d_ which satisfies _T_. > > + if there is an element _e_ of _O_ of the form __ such that at least one element between _e_ @@ -655,7 +658,7 @@ This gives us the following, > observed divergence and we're done. > > + If _d_ has no implicit arguments then the result is the value yielded by _d_. -> +> > + Otherwise for each implicit argument _a_ of _d_, resolve _a_ against _O+_, and the result is > the value yielded by _d_ applied to its resolved arguments. @@ -848,7 +851,7 @@ object Test { because the path `foo` in `foo.Out` is not stable. Full parity with shapeless's `Lazy` would require lazy (rather than byname) implicit parameters (see [this Dotty -ticket](https://github.com/lampepfl/dotty/issues/3005) for further discussion) and is orthogonal to +ticket](https://github.com/scala/scala3/issues/3005) for further discussion) and is orthogonal to this SIP in that they would drop out of support for lazy parameters more generally, as described in [this Scala ticket](https://github.com/scala/bug/issues/240). diff --git a/_sips/sips/clause-interleaving.md b/_sips/sips/clause-interleaving.md new file mode 100644 index 0000000000..e9809815e2 --- /dev/null +++ b/_sips/sips/clause-interleaving.md @@ -0,0 +1,173 @@ +--- +layout: sip +title: SIP-47 - Clause Interleaving +stage: completed +status: shipped +permalink: /sips/:title.html +--- + +**By: Quentin Bernet and Guillaume Martres and Sébastien Doeraene** + +## History + +| Date | Version | +|---------------|-----------------------| +| May 5th 2022 | Initial Draft | +| Aug 17th 2022 | Formatting | +| Sep 22th 2022 | Type Currying removed | + +## Summary + +We propose to generalize method signatures to allow any number of type parameter lists, interleaved with term parameter lists and using parameter lists. As a simple example, it would allow to define +~~~ scala +def pair[A](a: A)[B](b: B): (A, B) = (a, b) +~~~ +Here is also a more complicated and contrived example that highlights all the possible interactions: +~~~ scala +def foo[A](using a: A)(b: List[A])[C <: a.type, D](cd: (C, D))[E]: Foo[A, B, C, D, E] +~~~ + + +## Motivation + +Consider an API for a heterogenous key-value store, where keys know what type of value they must be associated to: +~~~ scala +trait Key: + type Value + +class Store: + def get(key: Key): key.Value = … + def put(key: Key)(value: => key.Value): Unit = … +~~~ +We want to provide a method `getOrElse`, taking a default value to be used if the key is not present. Such a method could look like +~~~ scala +def getOrElse(key: Key)(default: => key.Value): key.Value = … +~~~ +However, at call site, it would prevent from using as default value a value that is not a valid `key.Value`. This is a limitation compared to other `getOrElse`-style methods such as that of `Option`, which allow passing any supertype of the element type. + +In current Scala, there is no way to define `Store.getOrElse` in a way that supports this use case. We may try to define it as +~~~ scala +def getOrElse[V >: key.Value](key: Key)(default: => V): V = … +~~~ +but that is not valid because the declaration of `V` needs to refer to the path-dependent type `key.Value`, which is defined in a later parameter list. + +We might also try to move the type parameter list after `key` to avoid that problem, as +~~~ scala +def getOrElse(key: Key)[V >: key.Value](default: => V): V = … +~~~ +but that is also invalid because type parameter lists must always come first. + +A workaround is to return an intermediate object with an `apply` method, as follows: +~~~ scala +class Store: + final class StoreGetOrElse[K <: Key](val key: K): + def apply[V >: key.Value](default: => V): V = … + def getOrElse(key: Key): StoreGetOrElse[key.type] = StoreGetOrElse(key) +~~~ +This definition provides the expected source API at call site, but it has two issues: +* It is more complex than expected, forcing a user looking at the API to navigate to the definition of `StoreGetOrElse` to make sense of it. +* It is inefficient, as an intermediate instance of `StoreGetOrElse` must be created for each call to `getOrElse`. +* Overloading resolution looks at clauses after the first one, but only in methods, the above is ambiguous with any `def getOrElse(k:Key): ...`, whereas the proposed signature is not ambiguous with for example `def getOrElse(k:Key)[A,B](x: A, y: B)` + +Another workaround is to return a polymorphic function, for example: +~~~scala +def getOrElse(k:Key): [V >: k.Value] => (default: V) => V = + [V] => (default: V) => ??? +~~~ +While again, this provides the expected API at call site, it also has issues: +* The behavior is not the same, as `default` has to be a by-value parameter +* The definition is hard to visually parse, as users are more used to methods (and it is our opinion this should remain so) +* The definition is cumbersome to write, especially if there are a lot of term parameters +* It is inefficient, as many closures must be created for each call to `getOrElse` (one per term clause to the right of the first non-initial type clause). +* Same problem as above with overloading + +## Proposed solution +### High-level overview + +To solve the above problems, we propose to generalize method signatures so that they can have multiple type parameter lists, interleaved with term parameter lists and using parameter lists. + +For the heterogeneous key-value store example, this allows to define `getOrElse` as follows: +~~~ scala +def getOrElse(key: Key)[V >: key.Value](default: => V): V = … +~~~ +It provides the best of all worlds: +* A convenient API at call site +* A single point of documentation +* Efficiency, since the method erases to a single JVM method with signature `getOrElse(Object,Object)Object` + +### Specification +We amend the syntax of def parameter clauses as follows: +~~~ +DefDcl ::= DefSig ‘:’ Type +DefDef ::= DefSig [‘:’ Type] ‘=’ Expr +DefSig ::= id [DefParamClauses] [DefImplicitClause] +DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent +DefParamClause ::= DefTypeParamClause + | DefTermParamClause + | UsingParamClause +DefTypeParamClause ::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTermParamClause ::= [nl] ‘(’ [DefTermParams] ‘)’ +UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’ +DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ +DefTermParams ::= DefTermParam {‘,’ DefTermParam} +DefTermParam ::= {Annotation} [‘inline’] Param +Param ::= id ‘:’ ParamType [‘=’ Expr] +~~~ + +The main rules of interest are `DefParamClauses` and `DefParamClauseChunk`, which now allow any number of type parameter clauses, term parameter clauses and using parameter clauses, in any order as long as there are no two adjacent type clauses. + +Note that these are also used for the right-hand side of extension methods, thus clause interleaving also applies to them. + +It is worth pointing out that there can still only be at most one implicit parameter clause, which, if present, must be at the end. + +The type system and semantics naturally generalize to these new method signatures. + +### Restrictions + +#### Type Currying +Currying type clauses enables partial type inference, as the left clause can be specified while the right one is not. +As this is a very useful feature, we expect people would use it liberally, and recommending the curried form. +We are uncertain about the readability of the resulting methods, we have therefore decided to not include type currying as part of this proposal. + +Note however that, if absolutely necessary, it is still possible to curry type parameters as such: `def foo[A](using DummyImplicit)[B]`, since the implicit search for `DummyImplicit` will always succeed. +This is sufficiently unwieldy that it is unlikely the above becomes the norm. + +#### Class Signatures +Class signatures are unchanged. Classes can still only have at most one type parameter list, which must come first. For example, the following definition is still invalid: +~~~ scala +class Pair[+A](val a: A)[+B](val b: B) +~~~ +Class signatures already have limitations compared to def signatures. For example, they must have at least one term parameter list. There is therefore precedent for limiting their expressiveness compared to def parameter lists. + +The rationale for this restriction is that classes also define associated types. It is unclear what the type of an instance of `Pair` with `A` and `B` should be. It could be defined as `Foo[A][B]`. That still leaves holes in terms of path-dependent types, as `B`'s definition could not depend on the path `a`. Allowing interleaved type parameters for class definitions is therefore restricted for now. It could be allowed with a follow-up proposal. + +Note: As `apply` is a normal method, it is totally possible to define a method `def apply[A](a: A)[B](b: B)` on `Pair`'s companion object, allowing to create instances with `Pair[Int](4)[Char]('c')`. + +#### LHS of extension methods +The left hand side of extension methods remains unchanged, since they only have one explicit term clause, and since the type parameters are very rarely passed explicitly, it is not as necessary to have multiple type clauses there. + +Currently, Scala 2 can only call/override methods with at most one leading type parameter clause, which already forbids calling extension methods like `extension (x: Int) def bar[A](y: A)`, which desugars to `def bar(x: Int)[A](y: A)`. This proposal does not change this, so methods like `def foo[A](x: A)[B]` will not be callable from Scala 2. + +### Compatibility +The proposal is expected to be backward source compatible. New signatures currently do not parse, and typing rules are unchanged for existing signatures. + +Backward binary compatibility is straightforward. + +Backward TASTy compatibility should be straightforward. The TASTy format is such that we can extend it to support interleaved type parameter lists without added complexity. If necessary, a version check can decide whether to read signatures in the new or old format. For typechecking, like for source compatibility, the typing rules are unchanged for signatures that were valid before. + +Of course, libraries that choose to evolve their public API to take advantage of the new signatures may expose incompatibilities. + +## Alternatives +The proposal is a natural generalization of method signatures. +We could have extended the proposal to type currying (allowing partial type inference), but have not due to the concerns mentionned in [Restrictions](#restrictions). +This might be the subject of a follow up proposal, if the concerns can be addressed. + +As discussed above, we may want to consider generalizing class parameter lists as well. However, we feel it is better to leave that extension to a follow-up proposal, if required. + +## Related work +* Pre-SIP: [https://contributors.scala-lang.org/t/clause-interweaving-allowing-def-f-t-x-t-u-y-u/5525](https://contributors.scala-lang.org/t/clause-interweaving-allowing-def-f-t-x-t-u-y-u/5525) +* An implementation of the proposal is available as a pull request at [https://github.com/scala/scala3/pull/14019](https://github.com/scala/scala3/pull/14019) + +## FAQ +Currently empty. diff --git a/_sips/sips/comonadic-comprehensions.md b/_sips/sips/comonadic-comprehensions.md new file mode 100644 index 0000000000..d668fc4d78 --- /dev/null +++ b/_sips/sips/comonadic-comprehensions.md @@ -0,0 +1,6 @@ +--- +title: SIP-NN - comonadic-comprehensions +status: rejected +pull-request-number: 32 + +--- diff --git a/_sips/sips/concurrency-with-higher-order-coroutines.md b/_sips/sips/concurrency-with-higher-order-coroutines.md new file mode 100644 index 0000000000..837df46236 --- /dev/null +++ b/_sips/sips/concurrency-with-higher-order-coroutines.md @@ -0,0 +1,7 @@ +--- +title: SIP-55 - Concurrency with Higher-Order Coroutines +status: under-review +pull-request-number: 63 +stage: design + +--- diff --git a/_sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md b/_sips/sips/converters-among-optional-functions-partialfunctions-and-extractor-objects.md similarity index 96% rename from _sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md rename to _sips/sips/converters-among-optional-functions-partialfunctions-and-extractor-objects.md index 7bd7571ea8..cad08453c3 100644 --- a/_sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.md +++ b/_sips/sips/converters-among-optional-functions-partialfunctions-and-extractor-objects.md @@ -1,11 +1,14 @@ --- layout: sip -title: SIP-NN - Converters among optional Functions, PartialFunctions and extractor objects -vote-status: pending +title: SIP-38 - Converters among optional Functions, PartialFunctions and extractor objects +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/converters-among-optional-functions-partialfunctions-and-extractor-object.html --- +> This proposal has been implemented in Scala 2.13.0 and Scala 3.0.0. + **By: Yang Bo** diff --git a/_sips/sips/curried-varargs.md b/_sips/sips/curried-varargs.md new file mode 100644 index 0000000000..402630e152 --- /dev/null +++ b/_sips/sips/curried-varargs.md @@ -0,0 +1,6 @@ +--- +title: SIP-45 - Curried varargs +status: rejected +pull-request-number: 41 + +--- diff --git a/_sips/sips/drop-stdlib-forwards-bin-compat.md b/_sips/sips/drop-stdlib-forwards-bin-compat.md new file mode 100644 index 0000000000..771804f668 --- /dev/null +++ b/_sips/sips/drop-stdlib-forwards-bin-compat.md @@ -0,0 +1,315 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +title: SIP-51 - Drop Forwards Binary Compatibility of the Scala 2.13 Standard Library +--- + +**By: Lukas Rytz** + + +## History + +| Date | Version | +|----------------|--------------------| +| Dec 8, 2022 | Initial Version | + + +## Summary + +I propose to drop the forwards binary compatibility requirement that build tools enforce on the Scala 2.13 standard library. +This will allow implementing performance optimizations of collection operations that are currently not possible. +It also unblocks adding new classes and new members to existing classes in the standard library. + + +## Backwards and Forwards Compatibility + +A library is backwards binary compatible if code compiled against an old version works with a newer version on the classpath. +Forwards binary compatibility requires the opposite: code compiled against a new version of a library needs to work with an older version on the classpath. +A more in-depth explanation of binary compatibility is available on the [Scala documentation site](https://docs.scala-lang.org/overviews/core/binary-compatibility-of-scala-releases.html). + +Scala build tools like sbt automatically update dependencies on the classpath to the latest patch version that any other dependency on the classpath requires. +For example, with the following definition + +~~~ scala +libraryDependencies ++= List( + "com.softwaremill.sttp.client3" %% "core" % "3.8.3", // depends on ws 1.3.10 + "com.softwaremill.sttp.shared" %% "ws" % "1.2.7", // for demonstration +) +~~~ + +sbt updates the `ws` library to version 1.3.10. +Running the `evicted` command in sbt displays all dependencies whose version were changed. + +This build tool feature allows library authors to only maintain backwards binary compatibility in new versions, they don't need to maintain forwards binary compatibility. +Backwards binary compatible changes include the addition of new methods in existing classes and the addition of new classes. +Such additions don't impact existing code that was compiled against an older version, all definitions that were previously present are still there. + +### The Standard Library + +The Scala standard library is treated specially by sbt and other build tools, its version is always pinned to the `scalaVersion` of the build definition and never updated automatically. + +For example, the `"com.softwaremill.sttp.client3" %% "core" % "3.8.3"` library has a dependency on `"org.scala-lang" % "scala-library" % "2.13.10"` in its POM file. +When a project uses this version of the sttp client in a project with `scalaVersion` 2.13.8, sbt will put the Scala library version 2.13.8 on the classpath. + +This means that the standard library is required to remain both backwards and forwards binary compatible. +The implementation of sttp client 3.8.3 can use any feature available in Scala 2.13.10, and that compiled code needs to work correctly with the Scala 2.13.8 standard library. + +The suggested change of this SIP is to drop this special handling of the Scala standard library and therefore lift the forwards binary compatibility requirement. + + +## Motivation + +### Adding Overrides for Performance + +The forwards binary compatibility constraint regularly prevents adding optimized overrides to collection classes. +The reason is that the bytecode signature of an overriding method is not necessarily identical to the signature of the overridden method. +Example: + +~~~ scala +class A { def f: Object = "" } +class B extends A { override def f: String = "" } +~~~ + +The bytecode signature of `B.f` has return type `String`. +(In order to implement dynamic dispatch at run time (overriding), the compiler generates a "bridge" method `B.f` with return type `Object` which forwards to the other `B.f` method.) +Adding such an override is not forwards binary compatible, because code compiled against `B` can link to the `B.f` method with return type `String`, which would not exist in the previous version. + +It's common that forwards binary compatibility prevents adding optimizing overrides, most recently in [LinkedHashMap](https://github.com/scala/scala/pull/10235#issuecomment-1336781619). + +Sometimes, if an optimization is considered important, a type test is added to the existing implementation to achieve the same effect. +These workarounds could be cleaned up. +Examples are [`mutable.Map.mapValuesInPlace`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/mutable/Map.scala#L201-L204), [`IterableOnce.foldLeft`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/IterableOnce.scala#L669-L670), [`Set.concat`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/Set.scala#L226-L227), and many more. + +### Adding Functionality + +Dropping forwards binary compatiblity allows adding new methods to existing classes, as well as adding new classes. +While this opens a big door in principle, I am certain that stability, consistency and caution will remain core considerations when discussing additions to the standard library. +However, I believe that allowing to (carefully) evolve the standard library is greatly beneficial for the Scala community. + +Examples that came up in the past + - various proposals for new operations are here: https://github.com/scala/scala-library-next/issues and https://github.com/scala/scala-library-next/pulls + - addition of `ExecutionContext.opportunistic` in 2.13.4, which could not be made public: https://github.com/scala/scala/pull/9270 + - adding `ByteString`: https://contributors.scala-lang.org/t/adding-akkas-bytestring-as-a-scala-module-and-or-stdlib/5967 + - new string interpolators: https://github.com/scala/scala/pull/8654 + +## Alternatives and Related Work + +For binary compatible overrides, it was considered to add an annotation that would enforce the existing signature in bytecode. +However, this approach turned out to be too complex in the context of further overrides and Java compatibility. +Details are in the [corresponding pull request](https://github.com/scala/scala/pull/9141). + +Extensions to the standard library can be implemented in a separate library, and such a library exists since 2020 as [scala-library-next](https://github.com/scala/scala-library-next). +This library has seen very little adoption so far, and I personally don't think this is likely going (or possible) to change. +One serious drawback of an external library is that operations on existing classes can only be added as extension methods, which makes them less discoverable and requires adding an import. +This drawback could potentially be mitigated with improvements in Scala IDEs. + +An alternative to `scala-library-next` would be to use the Scala 3 library (`"org.scala-lang" % "scala3-library_3"`) which is published with Scala 3 releases. +This library is handled by build tools like any other library and therefore open for backwards binary compatible additions. +Until now, the Scala 3 library is exclusively used as a "runtime" library for Scala 3, i.e., it contanis definitions that are required for running code compiled with Scala 3. +Additions to the Scala 3 library would not be available to the still very large userbase of Scala 2.13. +Like for `scala-library-next`, additions to existing classes can again only be done in the form of extension methods. +Also, I believe that there is great value in keeping the Scala 2.13 and 3 standard libraries aligned for now. + + +## Implications + +### Possible Linkage Errors + +The policy change can only be implemented in new build tool releases, which makes it possible that projects run into linkage errors at run time. +Concretely, a project might update one of its dependencies to a new version which requires a more recent Scala library than the one defined in the project's `scalaVersion`. +If the project continues using an old version of sbt, the build tool will keep the Scala library pinned. +The new library might reference definitions that don't exist in the older Scala library, leading to linkage errors. + +### Scala.js and Scala Native + +Scala.js distributes a JavaScript version of the Scala library. +This artifact is currently released once per Scala.js version. +When a new Scala version comes out, a new Scala.js compiler is released, but the Scala library artifact continues to be used until the next Scala.js version. +This scheme does not work if the new Scala version has new definitions, so it needs to be adjusted. +Finding a solution for this problem is necessary and part of the implementation phase. + +A similar situation might exist for Scala Native. + +### Compiler and Library Version Mismatch + +Defining the `scalaVersion` in a project would no longer pin the standard library to that exact version. +The Scala compiler on the other hand would be kept at the specified version. +This means that Scala compilers will need to be able to run with a newer version of the Scala library, e.g., the Scala compiler 2.13.10 needs to be able to run with a 2.13.11 standard library on the compilation classpath. +I think this will not cause any issues. + +Note that there are two classpaths at play here: the runtime classpath of the JVM that is running the Scala compiler, and the compilation classpath in which the compiler looks up symbols that are referenced in the source code being compiled. +The Scala library on the JVM classpath could remain in sync with the compiler version. +The Scala library on the compilation classpath would be updated by the build tool according to the dependency graph. + +### Newer than Expected Library + +Because the build tool can update the Scala library version, a project might accidentally use / link to new API that does not yet exist in the `scalaVersion` that is defined in the build definition. +This is safe, as the project's POM file will have a dependency on the newer version of the Scala library. +The same situation can appear with any other dependency of a project. + +### Applications with Plugin Systems + +In applications where plugins are dynamically loaded, plugins compiled with a new Scala library could fail to work correctly if the application is running with an older Scala library. + +This is however not a new issue, the proposed change would just extend the existing problem to the Scala library. + +## Limitations + +Adding new methods or fields to existing traits remains a binary incompatible change. +This is unrelated to the Standard library, the same is true for other libraries. +[MiMa](https://github.com/lightbend/mima) is a tool for ensuring changes are binary compatible. + + +## Build Tools + +### Mill + +In my testing, Mill has the same behavior as sbt, the Scala library version is pinned to the project's `scalaVersion`. + +
+ +~~~ +$> cat build.sc +import mill._, scalalib._ +object proj extends ScalaModule { + def scalaVersion = "2.13.8" + def ivyDeps = Agg( + ivy"com.softwaremill.sttp.client3::core:3.8.3", + ivy"com.softwaremill.sttp.shared::ws:1.2.7", + ) +} +$> mill show proj.runClasspath +[1/1] show > [37/37] proj.runClasspath +[ + "qref:868554b6:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/client3/core_2.13/3.8.3/core_2.13-3.8.3.jar", + "qref:f3ba6af6:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/shared/ws_2.13/1.3.10/ws_2.13-1.3.10.jar", + "qref:438104da:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.8/scala-library-2.13.8.jar", + "qref:0c9ef1ab:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/model/core_2.13/1.5.2/core_2.13-1.5.2.jar", + "qref:9b3d3f7d:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/shared/core_2.13/1.3.10/core_2.13-1.3.10.jar" +] +~~~ + +
+ +### Gradle + +Gradle handles the Scala library the same as other dependencies, so it already implements the behavior proposed by this SIP. + +
+ +~~~ +$> cat build.gradle +plugins { + id 'scala' +} +repositories { + mavenCentral() +} +dependencies { + implementation 'org.scala-lang:scala-library:2.13.8' + implementation 'com.softwaremill.sttp.client3:core_2.13:3.8.3' + implementation 'com.softwaremill.sttp.shared:ws_2.13:1.2.7' +} +$> gradle dependencies --configuration runtimeClasspath + +> Task :dependencies + +------------------------------------------------------------ +Root project 'proj' +------------------------------------------------------------ + +runtimeClasspath - Runtime classpath of source set 'main'. ++--- org.scala-lang:scala-library:2.13.8 -> 2.13.10 ++--- com.softwaremill.sttp.client3:core_2.13:3.8.3 +| +--- org.scala-lang:scala-library:2.13.10 +| +--- com.softwaremill.sttp.model:core_2.13:1.5.2 +| | \--- org.scala-lang:scala-library:2.13.8 -> 2.13.10 +| +--- com.softwaremill.sttp.shared:core_2.13:1.3.10 +| | \--- org.scala-lang:scala-library:2.13.9 -> 2.13.10 +| \--- com.softwaremill.sttp.shared:ws_2.13:1.3.10 +| +--- org.scala-lang:scala-library:2.13.9 -> 2.13.10 +| +--- com.softwaremill.sttp.shared:core_2.13:1.3.10 (*) +| \--- com.softwaremill.sttp.model:core_2.13:1.5.2 (*) +\--- com.softwaremill.sttp.shared:ws_2.13:1.2.7 -> 1.3.10 (*) + +(*) - dependencies omitted (listed previously) +~~~ + +
+ +### Maven + +Maven [does not update](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) versions of dependencies that are explicitly listed in the `pom.xml` file, so it's possible to run into linkage errors at run time already now. +The maven versions plugin can display and update dependencies to newer versions. + +
+ +~~~ +$> cat pom.xml + + + 4.0.0 + a.b + proj + 1.0.0-SNAPSHOT + + UTF-8 + UTF-8 + 1.8 + 2.13.8 + + + + org.scala-lang + scala-library + ${scala.version} + + + com.softwaremill.sttp.client3 + core_2.13 + 3.8.3 + + + com.softwaremill.sttp.shared + ws_2.13 + 1.2.7 + + + + + + net.alchim31.maven + scala-maven-plugin + 4.8.0 + + + + +$> mvn dependency:build-classpath +[INFO] --- maven-dependency-plugin:2.8:build-classpath (default-cli) @ proj --- +[INFO] Dependencies classpath: +/Users/luc/.m2/repository/org/scala-lang/scala-library/2.13.8/scala-library-2.13.8.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/client3/core_2.13/3.8.3/core_2.13-3.8.3.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/model/core_2.13/1.5.2/core_2.13-1.5.2.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/shared/core_2.13/1.3.10/core_2.13-1.3.10.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/shared/ws_2.13/1.2.7/ws_2.13-1.2.7.jar +$> mvn versions:display-dependency-updates +[INFO] --- versions-maven-plugin:2.13.0:display-dependency-updates (default-cli) @ proj --- +[INFO] The following dependencies in Dependencies have newer versions: +[INFO] com.softwaremill.sttp.client3:core_2.13 ............... 3.8.3 -> 3.8.5 +[INFO] com.softwaremill.sttp.shared:ws_2.13 ................. 1.2.7 -> 1.3.12 +[INFO] org.scala-lang:scala-library ....................... 2.13.8 -> 2.13.10 +~~~ + +
+ +### Bazel + +I have never used bazel and did not manage set up / find a sample build definition to test its behavior. +Help from someone knowing bazel would be appreciated. + +### Pants + +As with bazel, I did not yet manage to set up / find an example project. + +### Other Tools + +The SIP might also require changes in other tools such as scala-cli, coursier or bloop. diff --git a/_sips/sips/2009-06-02-early-member-definitions.md b/_sips/sips/early-member-definitions.md similarity index 73% rename from _sips/sips/2009-06-02-early-member-definitions.md rename to _sips/sips/early-member-definitions.md index 06adf76c50..244b7c15b3 100644 --- a/_sips/sips/2009-06-02-early-member-definitions.md +++ b/_sips/sips/early-member-definitions.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-4 - Early Member Definitions -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/early-member-definitions.html --- diff --git a/_sips/sips/eference-able-package-objects.md b/_sips/sips/eference-able-package-objects.md new file mode 100644 index 0000000000..cfba956529 --- /dev/null +++ b/_sips/sips/eference-able-package-objects.md @@ -0,0 +1,7 @@ +--- +title: 'SIP-68: Reference-able Package Objects' +status: under-review +pull-request-number: 100 +stage: implementation + +--- diff --git a/_sips/sips/fewer-braces.md b/_sips/sips/fewer-braces.md new file mode 100644 index 0000000000..321f665233 --- /dev/null +++ b/_sips/sips/fewer-braces.md @@ -0,0 +1,296 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: completed +status: shipped +title: SIP-44 - Fewer Braces +--- + +**By: Martin Odersky** + +## History + +| Date | Version | +|----------------|--------------------| +| July 1st 2022 | Initial Draft | +| July 21st 2022 | Expanded Other Conerns Section | + +## Summary + +The current state of Scala 3 makes braces optional around blocks and template definitions (i.e. bodies of classes, objects, traits, enums, or givens). This SIP proposes to allow optional braces also for function arguments. +The advantages of doing so is that the language feels more systematic, and programs become typographically cleaner. +The changes have been implemented and and made available under the language import `language.experimental.fewerBraces`. The proposal here is to make them available without a language import instead. + + +## Motivation + +After extensive experience with the current indentation rules I conclude that they are overall a big success. +However, they still feel incomplete and a bit unsystematic since we can replace `{...}` in the majority of situations, but there are also important classes of situations where braces remain mandatory. In particular, braces are currently needed around blocks as function arguments. + +It seems very natural to generalize the current class syntax indentation syntax to function arguments. In both cases, an indentation block is started by a colon at the end of a line. Doing so will bring two major benefits: + + - Better _consistency_, since we avoid the situation where braces are sometimes optional and in other places mandatory. + - Better _readability_ in many common use cases, similar to why current + optional braces lead to better readability. + +## Proposed solution + +The proposed solution is described in detail in https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html#variant-indentation-marker--for-arguments. I inline the relevant sections here: + +First, here is the spec for colons at ends of lines for template bodies. This is part of official Scala 3. I cited it here for context. + +> A template body can alternatively consist of a colon followed by one or more indented statements. To this purpose we introduce a new `` token that reads as +the standard colon "`:`" but is generated instead of it where `` +is legal according to the context free syntax, but only if the previous token +is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, "`)`", and "`]`". + +> An indentation region can start after a ``. A template body may be either enclosed in braces, or it may start with +` ` and end with ``. +Analogous rules apply for enum bodies, type refinements, and local packages containing nested definitions. + +Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is so far one exception, though: Arguments to functions can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. + +To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import +```scala +import language.experimental.fewerBraces +``` +This SIP proposes to make this variant the default, so no language import is needed to enable it. +In this variant, a `` token is also recognized where function argument would be expected. Examples: + +```scala +times(10): + println("ah") + println("ha") +``` + +or + +```scala +credentials `++`: + val file = Path.userHome / ".credentials" + if file.exists + then Seq(Credentials(file)) + else Seq() +``` + +or + +```scala +xs.map: + x => + val y = x - 1 + y * y +``` +What's more, a `:` in these settings can also be followed on the same line by the parameter part and arrow of a lambda. So the last example could be compressed to this: + +```scala +xs.map: x => + val y = x - 1 + y * y +``` +and the following would also be legal: +```scala +xs.foldLeft(0): (x, y) => + x + y +``` + +The grammar changes for this variant are as follows. + +``` +SimpleExpr ::= ... + | SimpleExpr ColonArgument +InfixExpr ::= ... + | InfixExpr id ColonArgument +ColonArgument ::= colon [LambdaStart] + indent (CaseClauses | Block) outdent +LambdaStart ::= FunParams (‘=>’ | ‘?=>’) + | HkTypeParamClause ‘=>’ +``` +## Compatibility + +The proposed solution changes the meaning of the following code fragments: +```scala + val x = y: + Int + + val y = (xs.map: (Int => Int) => + Int) +``` +In the first case, we have a type ascription where the type comes after the `:`. In the second case, we have +a type ascription in parentheses where the ascribing function type is split by a newline. Note that we have not found examples like this in the dotty codebase or in the community build. We verified this by compiling everything with success with `fewerBraces` enabled. So we conclude that incompatibilities like these would be very rare. +If there would be code using these idioms, it can be rewritten quite simply to avoid the problem. For instance, the following fragments would be legal (among many other possible variations): +```scala + val x = y + : Int + + val y = (xs.map: (Int => Int) + => Int) +``` + +## Other concerns + +### Tooling + +Since this affects parsing, the scalameta parser and any other parser used in an IDE will also need to be updated. The necessary changes to the Scala 3 parser were made here: https://github.com/scala/scala3/pull/15273/commits. The commit that embodies the core change set is here: https://github.com/scala/scala3/pull/15273/commits/421bdd660b0456c2ff1ae386f032c41bb1e0212a. + +### Handling Edge Cases + +The design intentionally does not allow `:` to be placed after an infix operator or after a previous indented argument. This is a consequence of the following clause in the spec above: + +> To this purpose we introduce a new `` token that reads as +the standard colon "`:`" but is generated instead of it where `` +is legal according to the context free syntax, but only if the previous token +is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, "`)`", and "`]`". + +This was done to prevent hard-to-decypher symbol salad as in the following cases: (1) +```scala +a < b || : // illegal + val x = f(c) + x > 0 +``` +or (2) +```scala +source --> : x => // illegal + val y = x * x + println(y) +``` +or (3) +```scala +xo.fold: + defaultValue +: // illegal + x => f(x) +``` +or (4) +```scala +xs.groupMapReduce: item => + key(item) +: item => // illegal + value(item) +: (value1, value2) => // illegal + reduce(value1, value2) +``` + +I argue that the language already provides mechanisms to express these examples without having to resort to braces. (Aside: I don't think that resorting to braces occasionally is a bad thing, but some people argue that it is, so it's good to have alternatives). Basically, we have three options + + - Use parentheses. + - Use an explicit `apply` method call. + - Use a `locally` call. + +Here is how the examples above can be rewritten so that they are legal but still don't use braces: + +For (1), use `locally`: +```scala +a < b || locally: + val x = f(c) + x > 0 +``` +For (2), use parentheses: +```scala +source --> ( x => + val y = x * x + println(y) +) +``` +For (3) and (4), use `apply`: +```scala +xo.fold: + defaultValue +.apply: + x => f(x) + +xs.groupMapReduce: item => + key(item) +.apply: item => + value(item) +.apply: (value1, value2) => + reduce(value1, value2) +``` +**Note 1:** I don't argue that this syntax is _more readable_ than braces, just that it is reasonable. The goal of this SIP is to have the nicer syntax for +all common cases and to not be atrocious for edge cases. I think this +goal is achieved by the presented design. + +**Note 2:** The Scala compiler should add a peephole optimization +that elides an eta expansion in front of `.apply`. E.g. the `fold` example should be compiled to the same code as `xs.fold(defaultValue)(x => f(x))`. + +**Note 3:** To avoid a runtime footprint, the `locally` method should be an inline method. We can achieve that by shadowing the stdlib, or else we can decide on a different name. `nested` or `block` have been proposed. In any case this could be done in a separate step. + +### Syntactic confusion with type ascription + +A frequently raised concern against using `:` as an indentation marker is that it is too close to type ascription and therefore might be confusing. + +However, I have seen no evidence so far that this is true in practice. Of course, one can make up examples that look ambiguous. But as outlined, the community build and the dotty code base do not contain a single case where +a type ascription is now accidentally interpreted as an argument. + +Also the fact that Python chose `:` for type ascription even though it was already used as an indentation marker should give us confidence. + +In well written future Scala code we can use visual keys that would tell us immediately which is which. Namely: + +> If the `:` or `=>` is at the end of a line, it's an argument, otherwise it's a type ascription. + +According to the current syntax, if you want a multi-line type ascription, you _cannot_ write +```scala +anExpr: + aType +``` +It _must be_ rewritten to +```scala +anExpr + : aType +``` +(But, as stated above, it seems nobody actually writes code like that). +Similarly, it is _recommended_ that you put `=>` in a multi-line function type at the start of lines instead of at the end. I.e. this code does not follow the guidelines (even though it is technically legal): +```scala +xs.map: ((x: Int) => + Int) +``` +You should reformat it like this: +```scala +val y = xs.map: ((x: Int) + => Int) +``` +If we propose these guidelines then the only problem that remains is that if one intentionally writes confusing layout _and_ one reads only superficially then things can be confusing. But that's really nothing out of the ordinary. + + +## Open questions + +None directly related to the SIP. As mentioned above we should decide eventually whether we should stick with `locally` or replace it by something else. But that is unrelated to the SIP proper and can be decided independently. + +## Alternatives + +I considered two variants: + +The first variant would allow lambda parameters without preceding colons. E.g. +```scala +xs.foldLeft(z)(a, b) => + a + b +``` +We concluded that this was visually less good since it looks too much like a function call `xs.foldLeft(z)(a, b)`. + +The second variant would always require `(...)` around function types in ascriptions (which is in fact what the official syntax requires). That would have completely eliminated the second ambiguity above since +```scala +val y = (xs.map: (Int => Int) => + Int) +``` +would then not be legal anyway. But it turned out that there were several community projects that were using function types in ascriptions without enclosing parentheses, so this change was deemed to break too much code. + +@sjrd proposed in a [feature request](https://github.com/lampepfl/dotty-feature-requests/issues/299) that the `:` could be left out when +followed by `case` on the next line. Example: +```scala + val xs = List((1, "hello"), (true, "bar"), (false, "foo")) + val ys = xs.collect // note: no ':' here + case (b: Boolean, s) if b => s + println(ys) +``` +This is a tradeoff between conciseness and consistency. In the interest of minimality, I would leave it out of the first version of the implementation. We can always add it later if we feel a need for it. + +## Related work + + - Doc page for proposed change: https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html#variant-indentation-marker--for-arguments + + - Merged PR implementing the proposal under experimental flag: https://github.com/scala/scala3/pull/15273/commits/421bdd660b0456c2ff1ae386f032c41bb1e0212a + + - Latest discussion on contributors (there were several before when we discussed indentation in general): https://contributors.scala-lang.org/t/make-fewerbraces-available-outside-snapshot-releases/5024/166 + +## FAQ + diff --git a/_sips/sips/2012-01-21-futures-promises.md b/_sips/sips/futures-promises.md similarity index 99% rename from _sips/sips/2012-01-21-futures-promises.md rename to _sips/sips/futures-promises.md index 1bc4670945..26e067bfaa 100644 --- a/_sips/sips/2012-01-21-futures-promises.md +++ b/_sips/sips/futures-promises.md @@ -1,8 +1,8 @@ --- layout: sip title: SIP-14 - Futures and Promises -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/futures-promises.html --- @@ -714,13 +714,13 @@ Examples: ## References -1. [The Task-Based Asynchronous Pattern, Stephen Toub, Microsoft, April 2011][1] +1. [The Task-Based Asynchronous Pattern, Stephen Toub, Microsoft, February 2012][1] 2. [Finagle Documentation][2] 3. [Akka Documentation: Futures][3] 4. [Scala Actors Futures][4] 5. [Scalaz Futures][5] - [1]: https://www.microsoft.com/download/en/details.aspx?id=19957 "NETAsync" + [1]: https://download.microsoft.com/download/5/B/9/5B924336-AA5D-4903-95A0-56C6336E32C9/TAP.docx "NETAsync" [2]: https://twitter.github.io/scala_school/finagle.html "Finagle" [3]: https://doc.akka.io/docs/akka/current/futures.html "AkkaFutures" [4]: https://web.archive.org/web/20140814211520/https://www.scala-lang.org/api/2.9.3/scala/actors/Future.html "SActorsFutures" diff --git a/_sips/sips/2011-10-12-implicit-classes.md b/_sips/sips/implicit-classes.md similarity index 97% rename from _sips/sips/2011-10-12-implicit-classes.md rename to _sips/sips/implicit-classes.md index 988d146267..495bcadf23 100644 --- a/_sips/sips/2011-10-12-implicit-classes.md +++ b/_sips/sips/implicit-classes.md @@ -1,8 +1,8 @@ --- layout: sip title: SIP-13 - Implicit classes -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/implicit-classes.html --- diff --git a/_sips/sips/implicit-macro-conversions.md b/_sips/sips/implicit-macro-conversions.md new file mode 100644 index 0000000000..82e7ab2ff5 --- /dev/null +++ b/_sips/sips/implicit-macro-conversions.md @@ -0,0 +1,7 @@ +--- +title: SIP-66 - Implicit macro conversions +status: under-review +pull-request-number: 86 +stage: design + +--- diff --git a/_sips/sips/implicit-source-locations.md b/_sips/sips/implicit-source-locations.md new file mode 100644 index 0000000000..a51a574f5d --- /dev/null +++ b/_sips/sips/implicit-source-locations.md @@ -0,0 +1,6 @@ +--- +title: SIP-19 - Implicit Source Locations +status: rejected +pull-request-number: 18 + +--- diff --git a/_sips/sips/improve-strictequality-feature-for-better-compatibility-with-existing-code-bases.md b/_sips/sips/improve-strictequality-feature-for-better-compatibility-with-existing-code-bases.md new file mode 100644 index 0000000000..29276bba12 --- /dev/null +++ b/_sips/sips/improve-strictequality-feature-for-better-compatibility-with-existing-code-bases.md @@ -0,0 +1,8 @@ +--- +title: SIP-67 - Improve strictEquality feature for better compatibility with existing + code bases +status: waiting-for-implementation +pull-request-number: 97 +stage: implementation + +--- diff --git a/_sips/sips/improved-lazy-vals-initialization.md b/_sips/sips/improved-lazy-vals-initialization.md new file mode 100644 index 0000000000..efb891962b --- /dev/null +++ b/_sips/sips/improved-lazy-vals-initialization.md @@ -0,0 +1,7 @@ +--- +title: SIP-20 - Improved Lazy Vals Initialization +status: shipped +pull-request-number: 19 +stage: completed + +--- diff --git a/_sips/sips/improving-binary-compatibility-with-stableabi.md b/_sips/sips/improving-binary-compatibility-with-stableabi.md new file mode 100644 index 0000000000..6e48e46553 --- /dev/null +++ b/_sips/sips/improving-binary-compatibility-with-stableabi.md @@ -0,0 +1,6 @@ +--- +title: SIP-34 - Improving binary compatibility with @stableABI +status: withdrawn +pull-request-number: 30 + +--- diff --git a/_sips/sips/inline-meta.md b/_sips/sips/inline-meta.md new file mode 100644 index 0000000000..c27ee3530a --- /dev/null +++ b/_sips/sips/inline-meta.md @@ -0,0 +1,6 @@ +--- +title: SIP-28 - Inline meta +status: withdrawn +pull-request-number: 28 + +--- diff --git a/_sips/sips/2010-01-27-internals-of-scala-annotations.md b/_sips/sips/internals-of-scala-annotations.md similarity index 74% rename from _sips/sips/2010-01-27-internals-of-scala-annotations.md rename to _sips/sips/internals-of-scala-annotations.md index a21defafcc..792f7ee8ee 100644 --- a/_sips/sips/2010-01-27-internals-of-scala-annotations.md +++ b/_sips/sips/internals-of-scala-annotations.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-5 - Internals of Scala Annotations -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/internals-of-scala-annotations.html --- diff --git a/_sips/sips/2018-07-31-interpolation-quote-escape.md b/_sips/sips/interpolation-quote-escape.md similarity index 93% rename from _sips/sips/2018-07-31-interpolation-quote-escape.md rename to _sips/sips/interpolation-quote-escape.md index fe6615f12f..6f0398ce33 100644 --- a/_sips/sips/2018-07-31-interpolation-quote-escape.md +++ b/_sips/sips/interpolation-quote-escape.md @@ -1,11 +1,14 @@ --- layout: sip -title: SIP-NN - Quote escapes for interpolations -vote-status: pending +title: SIP-37 - Quote escapes for interpolations +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/interpolation-quote-escape.html --- +> This proposal has been implemented in Scala 2.13.6 and Scala 3.0.0. + **By: Martijn Hoekstra** ## History @@ -35,7 +38,7 @@ escape a `"` character to represent a literal `"` withing a string. ## Motivating Example That the `"` character can't be easily escaped in interpolations has been an -open issue since at least 2012[^1], and how to deal with this issue is a +open issue since at least 2012[^1], and how to deal with this issue is a somewhat common SO question[^2][^3] {% highlight Scala %} @@ -126,4 +129,4 @@ proposals. [^4]: https://github.com/scala/bug/issues/6476#issuecomment-292412577 "@retronym said: +1 to s"$"". Because it doesn't compile today, we don't risk changing the meaning of existing programs." [^5]: https://github.com/Scala/Scala/pull/6953/files#diff-0023b3bfa053fb16603156b785efa7ad "" [^6]: https://github.com/Scala/Scala/pull/4308 "SI-6476 Accept escaped quotes in interp strings" -[^7]: https://github.com/lampepfl/dotty/pull/7486 "PR in dotty" +[^7]: https://github.com/scala/scala3/pull/7486 "PR in dotty" diff --git a/_sips/sips/match-types-spec.md b/_sips/sips/match-types-spec.md new file mode 100644 index 0000000000..049fad9415 --- /dev/null +++ b/_sips/sips/match-types-spec.md @@ -0,0 +1,590 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: completed +status: shipped +title: SIP-56 - Proper Specification for Match Types +--- + +**By: Sébastien Doeraene** + +## History + +| Date | Version | +|---------------|--------------------| +| Aug 11th 2023 | Initial Draft | + +## Summary + +Currently, match type reduction is not specified, and its implementation is by nature not specifiable. +This is an issue because match type reduction spans across TASTy files (unlike, for example, type inference or GADTs), which can and will lead to old TASTy files not to be linked again in future versions of the compiler. + +This SIP proposes a proper specification for match types, which does not involve type inference. +It is based on `baseType` computations and subtype tests involving only fully-defined types. +That is future-proof, because `baseType` and subtyping are defined in the specification of the language. + +The proposed specification defines a subset of current match types that are considered legal. +Legal match types use the new, specified reduction rules. +Illegal match types are rejected, which is a breaking change, and can be recovered under `-source:3.3`. + +In addition, the proposal gives a specification for `provablyDisjoint` and for the reduction algorithm. + +## Motivation + +Currently, match type reduction is implementation-defined. +The core of the logic is matching a scrutinee type `X` against a pattern `P` with captures `ts`. +Captures are similar to captures in term-level pattern matching: in the type-level `case List[t] =>`, `t` is a (type) capture, just like in the term-level `case List(x) =>`, `x` is a (term) capture. +Matching works as follows: + +1. we create new type variables for the captures `ts'` giving a pattern `P'`, +2. we ask the compiler's `TypeComparer` (the type inference black box) to "try and make it so" that `X <:< P'`, +3. if it manages to do so, we get constraints for `ts'`; we then have a match, and we instantiate the body of the pattern with the received constraints for `ts`. + +The problem with this approach is that, by essence, type inference is an unspecified black box. +There are *guidelines* about how it should behave in common cases, but no actual guarantee. +This is fine everywhere else in the language, because what type inference comes up with is stored once and for all in TASTy. +When we read TASTy files, we do not have to perform the work of type inference again; we reuse what was already computed. +When a new version of the compiler changes type inference, it does not change what was computed and stored in TASTy files by previous versions of the compiler. + +For match types, this is a problem, because reduction spans across TASTy files. +Given some match type `type M[X] = X match { ... }`, two codebases may refer to `M[SomeType]`, and they must reduce it in the same way. +If they don't, hard incompatibilities can appear, such as broken subtyping, broken overriding relationships, or `AbstractMethodError`s due to inconsistent erasure. + +In order to guarantee compatibility, we must ensure that, for any given match type: + +* if it reduces in a given way in version 1 of the compiler, it still reduces in the same way in version 2, and +* if it does not reduce in version 1 of the compiler, it still does not reduce in version 2. +* (it is possible for version 1 to produce an *error* while version 2 successfully reduces or does not reduce) + +Reduction depends on two decision produces: + +* *matching* a scrutinee `X` against a pattern `P` (which we mentioned above), and +* deciding whether a scrutinee `X` is *provably disjoint* from a pattern `P`. + +When a scrutinee does not match a given pattern and cannot be proven disjoint from it either, the match type is "stuck" and does not reduce. + +If matching is delegated to the `TypeComparer` black box, then it is impossible in practice to guarantee the first compatibility property. + +In addition to the compatibility properties above, there is also a soundness property that we need to uphold: + +* if `X match { ... }` reduces to `R` and `Y <: X`, then `Y match { ... }` also reduces to `R`. + +This requires both that *matching* with a tighter scrutinee gives the same result, and that `provablyDisjoint(X, P)` implies `provablyDisjoint(Y, P)`. +(Those properties must be relaxed when `Y <: Nothing`, in order not to create more problems; see Olivier Blanvillain's thesis section 4.4.2 for details.) +With the existing implementation for `provablyDisjoint`, there are reasonable, non-contrived examples that violate this property. + +In order to solve these problems, this SIP provides a specification for match type reduction that is independent of the `TypeComparer` black box. +It defines a subset of match type cases that are considered legal. +Legal cases get a specification for when and how they should reduce for any scrutinee. +Illegal cases are rejected as being outside of the language. + +For compatibility reasons, such now-illegal cases will still be accepted under `-source:3.3`; in that case, they reduce using the existing, unspecified (and prone to breakage) implementation. +Due to practical reasons (see "Other concerns"), doing so does *not* emit any warning. +Eventually, support for this fallback may be removed if the compiler team decides that its maintenance burden is too high. +As usual, this SIP does not by itself provide any specific timeline. +In particular, there is no relationship with 3.3 being an "LTS"; it just happens to be the latest "Next" as well at the time of this writing (the changes in this SIP will only ever apply to 3.4 onwards, so the LTS is not affected in any way). + +For legal cases, the proposed reduction specification should reduce in the same way as the current implementation for all but the most obscure cases. +Our tests, including the entire dotty CI and its community build, did not surface any such incompatibility. +It is however not possible to guarantee that property for *all* cases, since the existing implementation is not specified in the first place. + +## Proposed solution + +### Specification + +#### Preamble + +Some of the concepts mentioned here are defined in the existing Scala 3 specification draft. +That draft can be found in the dotty repository at https://github.com/scala/scala3/tree/main/docs/_spec. +It is not rendered anywhere yet, though. + +Here are some of the relevant concepts that are perhaps lesser-known: + +* Chapter 3, section "Internal types": concrete v abstract syntax of types. +* Chapter 3, section "Base Type": the `baseType` function. +* Chapter 3, section "Definitions": whether a type is concrete or abstract (unrelated to the concrete or abstract *syntax*). + +#### Syntax + +Syntactically, nothing changes. +The way that a pattern is parsed and type captures identified is kept as is. + +Once type captures are identified, we can represent the *abstract* syntax of a pattern as follows: + +``` +// Top-level pattern +MatchTypePattern ::= TypeWithoutCapture + | MatchTypeAppliedPattern + +// A type that does not contain any capture, such as `Int` or `List[String]` +TypeWithoutCapture ::= Type // `Type` is from the "Internal types" section of the spec + +// Applied type pattern with at least one capture, such as `List[Seq[t]]` or `*:[Int, t]` +MatchTypeAppliedPattern ::= TyconWithoutCapture ‘[‘ MatchTypeSubPattern { ‘,‘ MatchTypeSubPattern } ‘]‘ + +// The type constructor of a MatchTypeAppliedPattern never contains any captures +TyconWithoutCapture ::= Type + +// Type arguments can be captures, types without captures, or nested applied patterns +MatchTypeSubPattern ::= TypeCapture + | TypeWithoutCapture + | MatchTypeAppliedPattern + +TypeCapture ::= NamedTypeCapture // e.g., `t` + | WildcardTypeCapture // `_` (with inferred bounds) +``` + +In the concrete syntax, `MatchTypeAppliedPattern`s can take the form of `InfixType`s. +A common example is `case h *: t =>`, which is desugared into `case *:[h, t] =>`. + +The cases `MatchTypeAppliedPattern` are only chosen if they contain at least one `TypeCapture`. +Otherwise, they are considered `TypeWithoutCapture` instead. +Each named capture appears exactly once. + +#### Legal patterns + +A `MatchTypePattern` is legal if and only if one of the following is true: + +* It is a `TypeWithoutCapture`, or +* It is a `MatchTypeAppliedPattern` with a legal `TyconWithoutCapture` and each of its arguments is either: + * A `TypeCapture`, or + * A `TypeWithoutCapture`, or + * The type constructor is *covariant* in that parameter, and the argument is recursively a legal `MatchTypeAppliedPattern`. + +A `TyconWithoutCapture` is legal if one of the following is true: + +* It is a *class* type constructor, or +* It is the `scala.compiletime.ops.int.S` type constructor, or +* It is an *abstract* type constructor, or +* It is a refined type of the form `Base { type Y = t }` where: + * `Base` is a `TypeWithoutCapture`, + * There exists a type member `Y` in `Base`, and + * `t` is a `TypeCapture`. +* It is a type alias to a type lambda such that: + * Its bounds contain all possible values of its arguments, and + * When applied to the type arguments, it beta-reduces to a new legal `MatchTypeAppliedPattern` that contains exactly one instance of every type capture present in the type arguments. + +#### Examples of legal patterns + +Given the following definitions: + +```scala +class Inv[A] +class Cov[+A] +class Contra[-A] + +class Base { + type Y +} + +type YExtractor[t] = Base { type Y = t } +type ZExtractor[t] = Base { type Z = t } + +type IsSeq[t <: Seq[Any]] = t +``` + +Here are examples of legal patterns: + +```scala +// TypeWithoutCapture's +case Any => // also the desugaring of `case _ =>` when the _ is at the top-level +case Int => +case List[Int] => +case Array[String] => + +// Class type constructors with direct captures +case scala.collection.immutable.List[t] => // not Predef.List; it is a type alias +case Array[t] => +case Contra[t] => +case Either[s, t] => +case Either[s, Contra[Int]] => +case h *: t => +case Int *: t => + +// The S type constructor +case S[n] => + +// An abstract type constructor +// given a [F[_]] or `type F[_] >: L <: H` in scope +case F[t] => + +// Nested captures in covariant position +case Cov[Inv[t]] => +case Cov[Cov[t]] => +case Cov[Contra[t]] => +case Array[h] *: t => // sugar for *:[Array[h], t] +case g *: h *: EmptyTuple => + +// Type aliases +case List[t] => // which is Predef.List, itself defined as `type List[+A] = scala.collection.immutable.List[A]` + +// Refinements (through a type alias) +case YExtractor[t] => +``` + +The following patterns are *not* legal: + +```scala +// Type capture nested two levels below a non-covariant type constructor +case Inv[Cov[t]] => +case Inv[Inv[t]] => +case Contra[Cov[t]] => + +// Type constructor with bounds that do not contain all possible instantiations +case IsSeq[t] => + +// Type refinement where the refined type member is not a member of the parent +case ZExtractor[t] => +``` + +#### Matching + +Given a scrutinee `X` and a match type case `case P => R` with type captures `ts`, matching proceeds in three steps: + +1. Compute instantiations for the type captures `ts'`, and check that they are *specific* enough. +2. If successful, check that `X <:< [ts := ts']P`. +3. If successful, reduce to `[ts := ts']R`. + +The instantiations are computed by the recursive function `matchPattern(X, P, variance, scrutIsWidenedAbstract)`. +At the top level, `variance = 1` and `scrutIsWidenedAbstract = false`. + +`matchPattern` behaves according to what kind is `P`: + +* If `P` is a `TypeWithoutCapture`: + * Do nothing (always succeed). +* If `P` is a `WildcardCapture` `ti = _`: + * Instantiate `ti` so that the subtype test in Step (2) above always succeeds: + * If `X` is of the form `_ >: L <: H`, instantiate `ti := H` (resp. `L`, `X`) if `variance = 1` (resp. `-1`, `0`). + * Otherwise, instantiate `ti := X`. +* If `P` is a `TypeCapture` `ti`: + * If `X` is of the form `_ >: L <: H`, + * If `scrutIsWidenedAbstract` is `true`, fail as not specific. + * Otherwise, if `variance = 1`, instantiate `ti := H`. + * Otherwise, if `variance = -1`, instantiate `ti := L`. + * Otherwise, fail as not specific. + * Otherwise, if `variance = 0` or `scrutIsWidenedAbstract` is `false`, instantiate `ti := X`. + * Otherwise, fail as not specific. +* If `P` is a `MatchTypeAppliedPattern` of the form `T[Qs]`: + * Assert: `variance = 1` (from the definition of legal patterns). + * If `T` is a class type constructor of the form `p.C`: + * If `baseType(X, C)` is not defined, fail as not matching. + * Otherwise, it is of the form `q.C[Us]`. + * If `p =:= q` is false, fail as not matching. + * Let `innerScrutIsWidenedAbstract` be true if either `scrutIsWidenedAbstract` or `X` is not a concrete type. + * For each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, innerScrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + * If `T` is `scala.compiletime.ops.int.S`: + * If `n = natValue(X)` is undefined or `n <= 0`, fail as not matching. + * Otherwise, compute `matchPattern(n - 1, Q1, 1, scrutIsWidenedAbstract)`. + * If `T` is an abstract type constructor: + * If `X` is not of the form `F[Us]` or `F =:= T` is false, fail as not matching. + * Otherwise, for each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, scrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + * If `T` is a refined type of the form `Base { type Y = ti }`: + * Let `q` be `X` if `X` is a stable type, or the skolem type `∃α:X` otherwise. + * If `q` does not have a type member `Y`, fail as not matching (that implies that `X <:< Base` is false, because `Base` must have a type member `Y` for the pattern to be legal). + * If `q.Y` is abstract, fail as not specific. + * If `q.Y` is a class member: + * If `q` is a skolem type `∃α:X`, fail as not specific. + * Otherwise, compute `matchPattern(ti, q.Y, 0, scrutIsWidenedAbstract)`. + * Otherwise, the underlying type definition of `q.Y` is of the form `= U`: + * If `q` is not a skolem type `∃α:X`, compute `matchPattern(ti, U, 0, scrutIsWidenedAbstract)`. + * Otherwise, let `U' = dropSkolem(U)` be computed as follow: + * `dropSkolem(q)` is undefined. + * `dropSkolem(p.T) = p'.T` where `p' = dropSkolem(p)` if the latter is defined. Otherwise: + * If the underlying type of `p.T` is of the form `= V`, then `dropSkolem(V)`. + * Otherwise `dropSkolem(p.T)` is undefined. + * `dropSkolem(p.x) = p'.x` where `p' = dropSkolem(p)` if the latter is defined. Otherwise: + * If the dealiased underlying type of `p.x` is a singleton type `r.y`, then `dropSkolem(r.y)`. + * Otherwise `dropSkolem(p.x)` is undefined. + * For all other types `Y`, `dropSkolem(Y)` is the type formed by replacing each component `Z` of `Y` by `dropSkolem(Z)`. + * If `U'` is undefined, fail as not specific. + * Otherwise, compute `matchPattern(ti, U', 0, scrutIsWidenedAbstract)`. + * If `T` is a concrete type alias to a type lambda: + * Let `P'` be the beta-reduction of `P`. + * Compute `matchPattern(P', X, variance, scrutIsWidenedAbstract)`. + +#### Disjointness + +This proposal initially did not include a discussion of disjointness. +After initial review, it became apparent that it should also provide a specification for the "provably disjoint" test involved in match type reduction. +Additional study revealed that, while *specifiable*, the current algorithm is very ad hoc and severely lacks desirable properties such as preservation along subtyping (see towards the end of this section). + +Therefore, this proposal now also includes a proposed specification for "provably disjoint". +To the best of our knowledge, it is strictly stronger than what is currently implemented, with one exception. + +The current implementation considers that `{ type A = Int }` is provably disjoint from `{ type A = Boolean }`. +However, it is not able to prove disjointness between any of the following: + +* `{ type A = Int }` and `{ type A = Boolean; type B = String }` (adding another type member) +* `{ type A = Int; type B = Int }` and `{ type B = String; type A = String }` (switching the order of type members) +* `{ type A = Int }` and class `C` that defines a member `type A = String`. + +Therefore, we drop the very ad hoc case of one-to-one type member refinements. + +On to the specification. + +A scrutinee `X` is *provably disjoint* from a pattern `P` iff it is provably disjoint from the type `P'` obtained by replacing every type capture in `P` by a wildcard type argument with the same bounds. + +We note `X ⋔ Y` to say that `X` and `Y` are provably disjoint. +Intuitively, that notion is based on the following properties of the Scala language: + +* Single inheritance of classes +* Final classes cannot be extended +* Sealed traits have a known set of direct children +* Constant types with distinct values are nonintersecting +* Singleton paths to distinct `enum` case values are nonintersecting + +However, a precise definition of provably-disjoint is complicated and requires some helpers. +We start with the notion of "simple types", which are a minimal subset of Scala types that capture the concepts mentioned above. + +A "simple type" is one of: + +* `Nothing` +* `AnyKind` +* `p.C[...Xi]` a possibly parameterized class type, where `p` and `...Xi` are arbitrary types (not just simple types) +* `c` a literal type +* `p.C.x` where `C` is an `enum` class and `x` is one of its value `case`s +* `X₁ & X₂` where `X₁` and `X₂` are both simple types +* `X₁ | X₂` where `X₁` and `X₂` are both simple types +* `[...ai] =>> X₁` where `X₁` is a simple type + +We define `⌈X⌉` a function from a full Scala type to a simple type. +Intuitively, it returns the "smallest" simple type that is a supertype of `X`. +It is defined as follows: + +* `⌈X⌉ = X` if `X` is a simple type +* `⌈X⌉ = ⌈U⌉` if `X` is a stable type but not a simple type and its underlying type is `U` +* `⌈X⌉ = ⌈H⌉` if `X` is a non-class type designator with upper bound `H` +* `⌈X⌉ = ⌈η(X)⌉` if `X` is a polymorphic class type designator, where `η(X)` is its eta-expansion +* `⌈X⌉ = ⌈Y⌉` if `X` is a match type that reduces to `Y` +* `⌈X⌉ = ⌈H⌉` if `X` is a match type that does not reduce and `H` is its upper bound +* `⌈X[...Ti]⌉ = ⌈Y⌉` where `Y` is the beta-reduction of `X[...Ti]` if `X` is a type lambda +* `⌈X[...Ti]⌉ = ⌈⌈X⌉[...Ti⌉` if `X` is neither a type lambda nor a class type designator +* `⌈X @a⌉ = ⌈X⌉` +* `⌈X { R }⌉ = ⌈X⌉` +* `⌈{ α => X } = ⌈X⌉⌉` +* `⌈X₁ & X₂⌉ = ⌈X₁⌉ & ⌈X₂⌉` +* `⌈X₁ | X₂⌉ = ⌈X₁⌉ | ⌈X₂⌉` +* `⌈[...ai] =>> X₁⌉ = [...ai] =>> ⌈X₁⌉` + +The following properties hold about `⌈X⌉` (we have paper proofs for those): + +* `X <: ⌈X⌉` for all type `X`. +* If `S <: T`, and the subtyping derivation does not use the "lower-bound rule" of `<:` anywhere, then `⌈S⌉ <: ⌈T⌉`. + +The "lower-bound rule" states that `S <: T` if `T = q.X `and `q.X` is a non-class type designator and `S <: L` where `L` is the lower bound of the underlying type definition of `q.X`". +That rule is known to break transitivy of subtyping in Scala already. + +Second, we define the relation `⋔` on *classes* (including traits and hidden classes of objects) as: + +* `C ⋔ D` if `C ∉ baseClasses(D)` and `D` is `final` +* `C ⋔ D` if `D ∉ baseClasses(C)` and `C` is `final` +* `C ⋔ D` if there exists `class`es `C' ∈ baseClasses(C)` and `D' ∈ baseClasses(D)` such that `C' ∉ baseClasses(D')` and `D' ∉ baseClasses(C')`. +* `C ⋔ D` if `C` is `sealed` without anonymous child and `Ci ⋔ D` for all direct children `Ci` of `C` +* `C ⋔ D` if `D` is `sealed` without anonymous child and `C ⋔ Di` for all direct children `Di` of `D` + +We can now define `⋔` for *types*. + +For arbitrary types `X` and `Y`, we define `X ⋔ Y` as `⌈X⌉ ⋔ ⌈Y⌉`. + +Two simple types `S` and `T` are provably disjoint if there is a finite derivation tree for `S ⋔ T` using the following rules. +Most rules go by pair, which makes the whole relation symmetric: + +* `Nothing` is disjoint from everything (including itself): + * `Nothing ⋔ T` + * `S ⋔ Nothing` +* A union type is disjoint from another type if both of its parts are disjoint from that type: + * `S ⋔ T1 | T2` if `S ⋔ T1` and `S ⋔ T2` + * `S1 | S2 ⋔ T` if `S1 ⋔ T` and `S2 ⋔ T` +* An intersection type is disjoint from another type if at least one of its parts is disjoint from that type: + * `S ⋔ T1 & T2` if `S ⋔ T1` or `S ⋔ T2` + * `S1 & S2 ⋔ T` if `S1 ⋔ T` or `S1 ⋔ T` +* A type lambda is disjoint from any other type that is not a type lambda with the same number of parameters: + * `[...ai] =>> S1 ⋔ q.D.y` + * `[...ai] =>> S1 ⋔ d` + * `[...ai] =>> S1 ⋔ q.D[...Ti]` + * `p.C.x ⋔ [...bi] =>> T1` + * `c ⋔ [...bi] =>> T1` + * `p.C[...Si] ⋔ [...bi] =>> T1` + * `[a1, ..., an] =>> S1 ⋔ [b1, ..., bm] =>> T1` if `m != n` +* Two type lambdas with the same number of type parameters are disjoint if their result types are disjoint: + * `[a1, ..., an] =>> S1 ⋔ [b1, ..., bn] =>> T1` if `S1 ⋔ T1` +* An `enum` value case is disjoint from any other `enum` value case (identified by either not being in the same `enum` class, or having a different name): + * `p.C.x ⋔ q.D.y` if `C != D` or `x != y` +* Two literal types are disjoint if they are different: + * `c ⋔ d` if `c != d` +* An `enum` value case is always disjoint from a literal type: + * `c ⋔ q.D.y` + * `p.C.x ⋔ d` +* An `enum` value case or a constant is disjoint from a class type if it does not extend that class (because it's essentially final): + * `p.C.x ⋔ q.D[...Ti]` if `baseType(p.C.x, D)` is not defined + * `p.C[...Si] ⋔ q.D.y` if `baseType(q.D.y, C)` is not defined + * `c ⋔ q.D[...Ti]` if `baseType(c, D)` is not defined + * `p.C[...Si] ⋔ d` if `baseType(d, C)` is not defined +* Two class types are disjoint if the classes themselves are disjoint, or if there exist a common super type with conflicting type arguments. + * `p.C[...Si] ⋔ q.D[...Ti]` if `C ⋔ D` + * `p.C[...Si] ⋔ q.D[...Ti]` if there exists a class `E` such that `baseType(p.C[...Si], E) = a.E[...Ai]` and `baseType(q.D[...Ti], E) = b.E[...Bi]` and there exists a pair `(Ai, Bi)` such that + * `Ai ⋔ Bi` and it is in invariant position, or + * `Ai ⋔ Bi` and it is in covariant position and there exists a field of that type parameter in `E` + +It is worth noting that this definition disregards prefixes entirely. +`p.C` and `q.C` are never provably disjoint, even if `p` could be proven disjoint from `q`. +It also disregards type members. + +We have a proof sketch of the following property for `⋔`: + +* If `S <: T` and `T ⋔ U`, then `S ⋔ U`. + +This is a very desirable property, which does not hold at all for the current implementation of match types. +It means that if we make the scrutinee of a match type more precise (a subtype) through substitution, and the match type previously reduced, then the match type will still reduce to the same case. + +Note: if `⋔` were a "true" disjointness relationship, and not a *provably*-disjoint relationship, that property would trivially hold based on elementary set theoretic properties. +It would amount to proving that if `S ⊆ T` and `T ⋂ U = ∅`, then `S ⋂ U = ∅`. + +#### Reduction + +The final piece of the match types puzzle is to define the reduction as a whole. + +In addition to matching and `provablyDisjoint`, the existing algorithm relies on a `provablyEmpty` property for a single type. +It was added a safeguard against matching an empty (`Nothing`) scrutinee. +The problem with doing so is that an empty scrutinee will be considered *both* as matching the pattern *and* as provably disjoint from the pattern. +This can result in the match type reducing to different cases depending on the context. +To sidestep this issue, the current algorithm refuses to consider a scrutinee that is `provablyEmpty`. + +If we wanted to keep that strategy, we would also have to specify `provablyEmpty` and prove some properties about it. +Instead, we choose a much simpler and safer strategy: we always test both matching *and* `provablyDisjoint`. +When both apply, we deduce that the scrutinee is empty is refuse to reduce the match type. + +Therefore, in summary, the whole reduction algorithm works as follows. +The result of reducing `X match { case P1 => R1; ...; case Pn => Rn }` can be a type, undefined, or a compile error. +For `n >= 1`, it is specified as: + +* If `X` matches `P1` with type capture instantiations `[...ts => ts']`: + * If `X ⋔ P1`, do not reduce. + * Otherwise, reduce as `[...ts => ts']R1`. +* Otherwise, + * If `X ⋔ P1`, the result of reducing `X match { case P2 => R2; ...; case Pn => Rn }` (i.e., proceed with subsequent cases). + * Otherwise, do not reduce. + +The reduction of an "empty" match type `X match { }` (which cannot be written in user programs) is a compile error. + +#### Subtyping + +As is already the case in the existing system, match types tie into subtyping as follows: + +* `X match { ... } <: T` if `X match { ... }` reduces to `S1` and `S1 <: T` +* `S <: X match { ... }` if `X match { ... }` reduces to `T1` and `S <: T1` +* `X match { ... } <: T` if `X match { ... }` has the upper bound `H` and `H <: T` +* `X match { case P1 => A1; ...; case Pn => An } <: Y match { case Q1 => B1; ...; Qn => Bn }` if `X =:= Y` and `Pi =:= Qi` for each `i` and `Ai <: Bi` for each `i` + +### Compatibility + +Compatibility is inherently tricky to evaluate for this proposal, and even to define. +One could argue that, from a pure specification point of view, it breaks nothing since it only specifies things that were unspecified before. +However, that is not very practical. +In practice, this proposal definitely breaks some code that compiled before, due to making some patterns illegal. +In exchange, it promises that all the patterns that are considered legal will keep working in the future; which is not the case with the current implementation, even for the legal subset. + +In order to evaluate the practical impact of this proposal, we conducted a quantitative analysis of *all* the match types found in Scala 3 libraries published on Maven Central. +We used [Scaladex](https://index.scala-lang.org/) to list all Scala 3 libraries, [coursier](https://get-coursier.io/docs/api) to resolve their classpaths, and [tasty-query](https://github.com/scalacenter/tasty-query) to semantically analyze the patterns of all the match types they contain. + +Out of 4,783 libraries, 49 contained at least one match type definition. +These 49 libraries contained a total of 779 match type `case`s. +Of those, there were 8 `case`s that would be flagged as not legal by the current proposal. + +These can be categorized as follows: + +* 2 libraries with 1 type member extractor each where the `Base` does not contain `Y`; they are both to extract `SomeEnumClass#Value` (from Scala 2 `scala.Enumeration`-based "enums"). + * https://github.com/iheartradio/ficus/blob/dcf39d6cd2dcde49b093ba5d1507ca478ec28dac/src/main/scala-3/net/ceedubs/ficus/util/EnumerationUtil.scala#L4-L8 + * https://github.com/json4s/json4s/blob/5e0b92a0ca59769f3130e081d0f53089a4785130/ext/src/main/scala-3/org/json4s/ext/package.scala#L4-L8 + * the maintainers of `json4s` already accepted a PR with a workaround at https://github.com/json4s/json4s/pull/1347 +* 1 library used to have 2 cases of the form `case HKExtractor[f] =>` with `type KHExtractor[f[_, _]] = Base { type Y[a, b] = f[a, b] }`. + * Those used to be at https://github.com/7mind/idealingua-v1/blob/48d35d53ce1c517f9f0d5341871e48749644c105/idealingua-v1/idealingua-v1-runtime-rpc-http4s/src/main/scala-3/izumi/idealingua/runtime/rpc/http4s/package.scala#L10-L15 but they do not exist in the latest version of the library. +* 1 library used to have 1 `&`-type extractor (which "worked" who knows how?): + https://github.com/Katrix/perspective/blob/f1643ac7a4e6a0d8b43546bf7b9e6219cc680dde/dotty/derivation/src/main/scala/perspective/derivation/Helpers.scala#L15-L18 + but the author already accepted a change with a workaround at + https://github.com/Katrix/perspective/pull/1 +* 1 library has 3 occurrences of using an abstract type constructor too "concretely": + https://github.com/kory33/s2mc-test/blob/d27c6e85ad292f8a96d7d51af7ddc87518915149/protocol-core/src/main/scala/io/github/kory33/s2mctest/core/generic/compiletime/Tuple.scala#L16 + defined at https://github.com/kory33/s2mc-test/blob/d27c6e85ad292f8a96d7d51af7ddc87518915149/protocol-core/src/main/scala/io/github/kory33/s2mctest/core/generic/compiletime/Generic.scala#L12 + It could be replaced by a concrete `class Lock[A](phantom: A)` instead. + +All the existing use cases therefore have either already disappeared or have a workaround. + +### Other concerns + +Ideally, this proposal would be first implemented as *warnings* about illegal cases, and only later made errors. +Unfortunately, the presence of the abstract type constructor case makes that impossible. +Indeed, because of it, a pattern that is legal at definition site may become illegal after some later substitution. + +Consider for example the standard library's very own `Tuple.InverseMap`: + +```scala +/** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ +type InverseMap[X <: Tuple, F[_]] <: Tuple = X match { + case F[x] *: t => x *: InverseMap[t, F] + case EmptyTuple => EmptyTuple +} +``` + +If we instantiate `InverseMap` with a class type parameter, such as `InverseMap[X, List]`, the first case gets instantiated to +```scala +case List[x] *: t => x *: InverseMap[t, List] +``` +which is legal. + +However, nothing prevents us a priori to instantiate `InverseMap` with an illegal type constructor, for example +```scala +type IsSeq[t <: Seq[Any]] = t +InverseMap[X, IsSeq] +``` +which gives +```scala +case IsSeq[x] *: t => x *: InverseMap[t, IsSeq] +``` + +These instantiatiations happen deep inside the type checker, during type computations. +Since types are cached, shared and reused in several parts of the program, by construction, we do not have any source code position information at that point. +That means that we cannot report *warnings*. + +We can in fact report *errors* by reducing to a so-called `ErrorType`, which is aggressively propagated. +This is what we do in the proposed implementation (unless using `-source:3.3`). + +### Open questions + +None at this point. + +## Alternatives + +The specification is more complicated than we initially wanted. +At the beginning, we were hoping that we could restrict match cases to class type constructors only. +The quantitative study however revealed that we had to introduce support for abstract type constructors and for type member extractors. + +As already mentioned, the standard library itself contains an occurrence of an abstract type constructor in a pattern. +If we made that an error, we would have a breaking change to the standard library itself. +Some existing libraries would not be able to retypecheck again. +Worse, it might not be possible for them to change their code in a way that preserves their own public APIs. + +We tried to restrict abstract type constructors to never match on their own. +Instead, we wanted them to stay "stuck" until they could be instantiated to a concrete type constructor. +However, that led some existing tests to fail even for match types that were declared legal, because they did not reduce anymore in some places where they reduced before. + +Type member extractors are our biggest pain point. +Their specification is complicated, and the implementation as well. +Our quantitative study showed that they were however used at least somewhat often (10 occurrences spread over 4 libraries). +In each case, they seem to be a way to express what Scala 2 type projections (`A#T`) could express. +While not quite as powerful as type projections (which were shown to be unsound), match types with type member extractors delay things enough for actual use cases to be meaningful. + +As far as we know, those use cases have no workaround if we make type member extractors illegal. + +## Related work + +Notable prior work related to this proposal includes: + +- [Current reference page for Scala 3 match types](https://dotty.epfl.ch/docs/reference/new-types/match-types.html) +- [Abstractions for Type-Level Programming](https://infoscience.epfl.ch/record/294024), Olivier Blanvillain, Chapter 4 (Match Types) +- ["Pre-Sip" discussion in the Contributors forum](https://contributors.scala-lang.org/t/pre-sip-proper-specification-for-match-types/6265) (submitted at the same time as this SIP document) +- [PR with the proposed implementation](https://github.com/scala/scala3/pull/18262) + +## FAQ + +None at this point. diff --git a/_sips/sips/2012-03-17-modularizing-language-features.md b/_sips/sips/modularizing-language-features.md similarity index 86% rename from _sips/sips/2012-03-17-modularizing-language-features.md rename to _sips/sips/modularizing-language-features.md index b3178ba83f..4de88d18be 100644 --- a/_sips/sips/2012-03-17-modularizing-language-features.md +++ b/_sips/sips/modularizing-language-features.md @@ -1,9 +1,8 @@ --- layout: sip title: SIP-18 - Modularizing Language Features - -vote-status: complete -vote-text: This SIP has already been accepted and completed. Let the record show Paul is against it. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/modularizing-language-features.html --- diff --git a/_sips/sips/multi-source-extension-overloads.md b/_sips/sips/multi-source-extension-overloads.md new file mode 100644 index 0000000000..fdcfd7050b --- /dev/null +++ b/_sips/sips/multi-source-extension-overloads.md @@ -0,0 +1,233 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: completed +status: shipped +title: SIP-54 - Multi-Source Extension Overloads +--- + +**By: Sébastien Doeraene and Martin Odersky** + +## History + +| Date | Version | +|---------------|--------------------| +| Mar 10th 2023 | Initial Draft | + +## Summary + +We propose to allow overload resolution of `extension` methods with the same name but imported from several sources. +For example, given the following definitions: + +```scala +class Foo +class Bar + +object A: + extension (foo: Foo) def meth(): Foo = foo + def normalMeth(foo: Foo): Foo = foo + +object B: + extension (bar: Bar) def meth(): Bar = bar + def normalMeth(bar: Bar): Bar = bar +``` + +and the following use site: + +```scala +import A.* +import B.* + +val foo: Foo = ??? +foo.meth() // works with this SIP; "ambiguous import" without it + +// unchanged: +meth(foo)() // always ambiguous, just like +normalMeth(foo) // always ambiguous +``` + +## Motivation + +Extension methods are a great, straightforward way to extend external classes with additional methods. +One classical example is to add a `/` operation to `Path`: + +```scala +import java.nio.file.* + +object PathExtensions: + extension (path: Path) + def /(child: String): Path = path.resolve(child).nn + +def app1(): Unit = + import PathExtensions.* + val projectDir = Paths.get(".") / "project" +``` + +However, as currently specified, they do not compose, and effectively live in a single flat namespace. +This is understandable from the spec--the *mechanism**, which says that they are just regular methods, but is problematic from an intuitive point of view--the *intent*. + +For example, if we also use another extension that provides `/` for `URI`s, we can use it in a separate scope as follows: + +```scala +import java.net.URI + +object URIExtensions: + extension (uri: URI) + def /(child: String): URI = uri.resolve(child) + +def app2(): Unit = + import URIExtensions.* + val rootURI = new URI("https://www.example.com/") + val projectURI = rootURI / "project/" +``` + +The above does not work anymore if we need to use *both* extensions in the same scope. +The code below does not compile: + +```scala +def app(): Unit = + import PathExtensions.* + import URIExtensions.* + + val projectDir = Paths.get(".") / "project" + val rootURI = new URI("https://www.example.com/") + val projectURI = rootURI / "project/" + println(s"$projectDir -> $projectURI") +end app +``` + +*Both* attempts to use `/` result in error messages of the form + +``` +Reference to / is ambiguous, +it is both imported by import PathExtensions._ +and imported subsequently by import URIExtensions._ +``` + +### Workarounds + +The only workarounds that exist are unsatisfactory. + +We can avoid using extensions with the same name in the same scope. +In the above example, that would be annoying enough to defeat the purpose of the extensions in the first place. + +Another possibility is to *define* all extension methods of the same name in the same `object` (or as top-level definitions in the same file). +This is possible, although cumbersome, if they all come from the same library. +However, it is impossible to combine extension methods coming from separate libraries in this way. + +Finally, there exists a trick with `given`s of empty refinements: + +```scala +object PathExtensions: + given pathExtensions: {} with + extension (path: Path) + def /(child: String): Path = path.resolve(child).nn + +object URIExtensions: + given uriExtensions: {} with + extension (uri: URI) + def /(child: String): URI = uri.resolve(child) +``` + +The empty refinement `: {}` prevents those `given`s from polluting the actual implicit scope. +`extension`s defined inside `given`s that are in scope can be used, so this trick allows to use `/` with the imports of `PathExtensions.*` and `URIExtensions.*`. +The `given`s must still have different names for the trick to work. +This workaround is however quite obscure. +It hides intent behind a layer of magic (and an additional indirection at run-time). + +### Problem for migrating off of implicit classes + +Scala 2 implicit classes did not suffer from the above issues, because they were disambiguated by the name of the implicit class (not the name of the method). +This means that there are libraries that cannot migrate off of implicit classes to use `extension` methods without significantly degrading their usability. + +## Proposed solution + +We propose to relax the resolution of extension methods, so that they can be resolved from multiple imported sources. +Instead of rejecting the `/` call outright because of ambiguous imports, the compiler should try the resolution from all the imports, and keep the only one (if any) for which the receiver type matches. + +Practically speaking, this means that the above `app()` example would compile and behave as expected. + +### Non-goals + +It is *not* a goal of this proposal to allow resolution of arbitrary overloads of regular methods coming from multiple imports. +Only `extension` method calls are concerned by this proposal. +The complexity budget of relaxing *all* overloads in this way is deemed too high, whereas it is acceptable for `extension` method calls. + +For the same reason, we do not propose to change regular calls of methods that happen to be `extension` methods. + +### Specification + +We make two changes to the [specification of extension methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html). + +In the section [Translation of Extension Methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html#translation-of-extension-methods), we make it clearer that the "desugared" version of the call site may require an explicit qualifier. +This is not strictly a novelty of this SIP, since it could already happen with `given`s and implicit scopes, but this SIP adds one more case where this can happen. + +Previously: + +> So, the definition of circumference above translates to the following method, and can also be invoked as such: +> +> ` def circumference(c: Circle): Double = c.radius * math.Pi * 2` +> +> `assert(circle.circumference == circumference(circle))` + +With this SIP: + +> So, the definition of circumference above translates to the following method, and can also be invoked as such: +> +> ` def circumference(c: Circle): Double = c.radius * math.Pi * 2` +> +> `assert(circle.circumference == circumference(circle))` +> +> or +> +> `assert(circle.circumference == qualifierPath.circumference(circle))` +> +> for some `qualifierPath` in which `circumference` is actually declared. +> Explicit qualifiers may be required when the extension method is resolved through `given` instances, implicit scopes, or disambiguated from several imports. + +--- + +In the section [Translation of Calls to Extension Methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html#translation-of-calls-to-extension-methods), we amend step 1. of "The precise rules for resolving a selection to an extension method are as follows." + +Previously: + +> Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, and where `T` is the expected type. +> The following two rewritings are tried in order: +> +> 1. The selection is rewritten to `m[Ts](e)`. + +With this SIP: + +> 1. The selection is rewritten to `m[Ts](e)` and typechecked, using the following slight modification of the name resolution rules: +> +> - If `m` is imported by several imports which are all on the same nesting level, try each import as an extension method instead of failing with an ambiguity. +> If only one import leads to an expansion that typechecks without errors, pick that expansion. +> If there are several such imports, but only one import which is not a wildcard import, pick the expansion from that import. +> Otherwise, report an ambiguous reference error. + +### Compatibility + +The proposal only alters situations where the previous specification would reject the program with an ambiguous import. +Therefore, we expect it to be backward source compatible. + +The resolved calls could previously be spelled out by hand (with fully-qualified names), so binary compatibility and TASTy compatibility are not affected. + +### Other concerns + +With this SIP, some calls that would be reported as *ambiguous* in their "normal" form can actually be written without ambiguity if used as extensions. +That may be confusing to some users. +Although specific error messages are not specified and therefore outside the SIP scope, we encourage the compiler implementation to enhance the "ambiguous" error message to address this confusion. +If some or all of the involved ambiguous targets are `extension` methods, the compiler should point out that the call might be resolved unambiguously if used as an extension. + +## Alternatives + +A number of alternatives were mentioned in [the Contributors thread](https://contributors.scala-lang.org/t/change-shadowing-mechanism-of-extension-methods-for-on-par-implicit-class-behavior/5831), but none that passed the bar of "we think this is actually implementable". + +## Related work + +- [Contributors thread acting as de facto Pre-SIP](https://contributors.scala-lang.org/t/change-shadowing-mechanism-of-extension-methods-for-on-par-implicit-class-behavior/5831) +- [Pull Request in dotty](https://github.com/scala/scala3/pull/17050) to support it under an experimental import + +## FAQ + +This section will probably initially be empty. As discussions on the proposal progress, it is likely that some questions will come repeatedly. They should be listed here, with appropriate answers. diff --git a/_sips/sips/multiple-assignments.md b/_sips/sips/multiple-assignments.md new file mode 100644 index 0000000000..b55044b7d9 --- /dev/null +++ b/_sips/sips/multiple-assignments.md @@ -0,0 +1,331 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +presip-thread: https://contributors.scala-lang.org/t/pre-sip-multiple-assignments/6425 +title: SIP-59 - Multiple Assignments +--- + +**By: Dimi Racordon** + +## History + +| Date | Version | +|---------------|--------------------| +| Jan 17th 2024 | Initial Draft | + +## Summary + +This proposal discusses the syntax and semantics of a construct to assign multiple variables with a single expression. +This feature would simplify the implementation of operations expressed in terms of relationships between multiple variables, such as [`std::swap`](https://en.cppreference.com/w/cpp/algorithm/swap) in C++. + +## Motivation + +It happens that one has to assign multiple variables "at once" in an algorithm. +For example, let's consider the Fibonacci sequence: + +```scala +class FibonacciIterator() extends Iterator[Int]: + + private var a: Int = 0 + private var b: Int = 1 + + def hasNext = true + def next() = + val r = a + val n = a + b + a = b + b = n + r +``` + +The same iterator could be rewritten more concisely if we could assign multiple variables at once. +For example, we can write the following in Swift: + +```swift +struct FibonacciIterator: IteratorProtocol { + + private var a: Int = 0 + private var b: Int = 1 + init() {} + + mutating func next() -> Int? { + defer { (a, b) = (b, a + b) } + return a + } + +} +``` + +Though the differences may seem frivolous at first glance, they are in fact important. +If we look at a formal definition of the Fibonacci sequence (e.g., on [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_sequence)), we might see something like: + +> The Fibonacci sequence is given by *F(n) = F(n-1) + F(n+1)* where *F(0) = 0* and *F(1) = 1*. + +Although this declarative description says nothing about an evaluation order, it becomes a concern in our Scala implementation as we must encode the relationship into multiple operational steps. +This decomposition offers opportunities to get things wrong: + +```scala +def next() = + val r = a + a = b + b = a + b // invalid semantics, the value of `a` changed "too early" + r +``` + +In contrast, our Swift implementation can remain closer to the formal definition and is therefore more legible and less error-prone. + +Multiple assignments show up in many general-purpose algorithms (e.g., insertion sort, partition, min-max element, ...). +But perhaps the most fundamental one is `swap`, which consists of exchanging two values. + +We often swap values that are stored in some collection. +In this particular case, all is well in Scala because we can ask the collection to swap elements at given positions: + +```scala +extension [T](self: mutable.ArrayBuffer[T]) + def swapAt(i: Int, j: Int) = + val t = self(i) + self(i) = self(j) + self(j) = t + +val a = mutable.ArrayBuffer(1, 2, 3) +a.swapAt(0, 2) +println(a) // ArrayBuffer(3, 2, 1) +``` + +Sadly, one can't implement a generic swap method that wouldn't rely on the ability to index a container. +The only way to express this operation in Scala is to "inline" the pattern implemented by `swapAt` every time we need to swap two values. + +Having to rewrite this boilerplate is unfortunate. +Here is an example in a realistic algorithm: + +```scala +extension [T](self: Seq[T])(using Ordering[T]) + def minMaxElements: Option[(T, T)] = + import math.Ordering.Implicits.infixOrderingOps + + // Return None for collections smaller than 2 elements. + var i = self.iterator + if (!i.hasNext) { return None } + var l = i.next() + if (!i.hasNext) { return None } + var h = i.next() + + // Confirm the initial bounds. + if (h < l) { val t = l; l = h; h = l } + + // Process the remaining elements. + def loop(): Option[(T, T)] = + if (i.hasNext) { + val n = i.next() + if (n < l) { l = n } else if (n > h) { h = n } + loop() + } else { + Some((l, h)) + } + loop() +``` + +*Note: implementation shamelessly copied from [swift-algorithms](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/MinMax.swift).* + +The swap occurs in the middle of the method with the sequence of expressions `val t = l; l = h; h = l`. +To borrow from the words of Edgar Dijskstra [1, Chapter 11]: + +> [that] is combersome and ugly compared with the [multiple] assignment. + +While `swap` is a very common operation, it's only an instance of a more general class of operations that are expressed in terms of relationships between multiple variables. +The definition of the Fibonacci sequence is another example. + +## Proposed solution + +The proposed solution is to add a language construct to assign multiple variables in a single expression. +Using this construct, swapping two values can be written as follows: + +```scala +var a = 2 +var b = 4 +(a, b) = (b, a) +println(s"$a$b") // 42 +``` + +The above Fibonacci iterator can be rewritten as follows: + +```scala +class FibonacciIterator() extends Iterator[Int]: + + private var a: Int = 0 + private var b: Int = 1 + + def hasNext = true + def next() = + val r = a + (a, b) = (b, a + b) + r +``` + +Multiple assignments also alleviate the need for a swap method on collections, as the same idiomatic pattern can be reused to exchange elements at given indices: + +```scala +val a = mutable.ArrayBuffer(1, 2, 3) +(a(0), a(2)) = (a(2), a(0)) +println(a) // ArrayBuffer(3, 2, 1) +``` + +### Specification + +A multiple assignment is an expression of the form `AssignTarget ‘=’ Expr` where: + +``` +AssignTarget ::= ‘(’ AssignTargetNode {‘,’ AssignTargetNode} ‘)’ +AssignTargetNode ::= Expr | AssignTarget +``` + +An assignment target describes a structural pattern that can only be matched by a compatible composition of tuples. +For example, the following program is legal. + +```scala +def f: (Boolean, Int) = (true, 42) +val a = mutable.ArrayBuffer(1, 2, 3) +def b = a +var x = false + +(x, a(0)) = (false, 1337) +(x, a(1)) = f +((x, a(1)), b(2)) = (f, 9000) +(x) = Tuple1(false) +``` + +A mismatch between the structure of a multiple assignment's target and the result of its RHS is a type error. +It cannot be detected during parsing because at this stage the compiler would not be able to determine the shape of an arbitrary expression's result. +For example, all multiple assignments in the following program are ill-typed: + +```scala +def f: (Boolean, Int) = (true, 42) +val a = mutable.ArrayBuffer(1, 2, 3) +def b = a +var x = false + +(a(1), x) = f // type mismatch +(x, a(1), b(2)) = (f, 9000) // structural mismatch +(x) = false // structural mismatch +(x) = (1, 2) // structural mismatch +``` + +Likewise, `(x) = Tuple1(false)` is _not_ equivalent to `x = Tuple1(false)`. +The former is a multiple assignment while the latter is a regular assignment, as described by the [current grammar](https://docs.scala-lang.org/scala3/reference/syntax.html) (see `Expr1`). +Though this distinction is subtle, multiple assignments involving unary tuples should be rare. + +The operational semantics of multiple assignments (aka concurrent assignments) have been studied extensively in scienific literature (e.g., [1, 2]). +A first intuition is that the most desirable semantics can be achieved by fully evaluating the RHS of the assignment before assigning any expression in the LHS [1]. +However, additional considerations must be given w.r.t. the independence of the variables on the LHS to guarantee deterministic results. +For example, consider the following expression: + +```scala +(x, x) = (1, 2) +``` + +While one may conclude that such an expression should be an error [1], it is in general difficult to guarantee value independence in a language with pervasive reference semantics. +Further, it is desirable to write expressions of the form `(a(0), a(2)) = (a(2), a(0))`, as shown in the previous section. +Another complication is that multiple assignments should uphold the general left-to-right evaluation semantics of the Scala language. +For example, `a.b = c` requires `a` to be evaluated _before_ `c`. + +Note that regular assignments desugar to function calls (e.g., `a(b) = c` is sugar for `a.update(b, c)`). +One property of these desugarings is always the last expression being evaluated before the method performing the assignment is called. +Given this observation, we address the abovementioned issues by defining the following algorithm: + +1. Traverse the LHS structure in inorder and for each leaf: + - Evaluate each outermost subexpression to its value + - Form a closure capturing these values and accepting a single argument to perform the desugared assignment + - Associate that closure to the leaf +2. Compute the value of the RHS, which forms a tree +3. Traverse the LHS and RHS structures pairwise in inorder and for each leaf: + - Apply the closure formerly associated to the LHS on RHS value + +For instance, consider the following definitions. + +```scala +def f: (Boolean, Int) = (true, 42) +val a = mutable.ArrayBuffer(1, 2, 3) +def b = a +var x = false +``` + +The evaluation of the expression `((x, a(a(0))), b(2)) = (f, 9000)` is as follows: + +1. form a closure `f0 = (rhs) => x_=(rhs)` +2. evaluate `a(0)`; result is `1` +3. form a closure `f1 = (rhs) => a.update(1, rhs)` +4. evaluate `b`; result is `a` +5. evaluate `2` +6. form a closure `f2 = (rhs) => a.update(2, rhs)` +7. evaluate `(f, 9000)`; result is `((true, 42), 9000)` +8. evaluate `f0(true)` +9. evaluate `f1(42)` +10. evaluate `f2(9000)` + +After the assignment, `x == true` and `a == List(1, 42, 9000)`. + +The compiler is allowed to ignore this procedure and generate different code for optimization purposes as long as it can guarantee that such a change is not observable. +For example, given two local variables `x` and `y`, their assignments in `(x, y) = (1, 2)` can be reordered or even performed in parallel. + +### Compatibility + +This proposal is purely additive and have no backward binary or TASTy compatibility consequences. +The semantics of the proposed new construct is fully expressible in terms of desugaring into current syntax, interpreteted with current semantics. + +The proposed syntax is not currently legal Scala. +Therefore no currently existing program could be interpreted with different semantics using a newer compiler version supporting multiple assignments. + +### Other concerns + +One understandable concern of the proposed syntax is that the semantics of multiple assignments resembles that of pattern matching, yet it has different semantics. +For example: + +```scala +val (a(x), b) = (true, "!") // 1 + +(a(x), b) = (true, "!") // 2 +``` + +If `a` is instance of a type with a companion extractor object, the two lines above have completely different semantics. +The first declares two local bindings `x` and `b`, applying pattern matching to determine their value from the tuple `(true, "!")`. +The second is assigning `a(x)` and `b` to the values `true` and `"!"`, respectively. + +Though possibly surprising, the difference in behavior is easy to explain. +The first line applies pattern matching because it starts with `val`. +The second doesn't because it involves no pattern matching introducer. +Further, note that a similar situation can already be reproduced in current Scala: + +```scala +val a(x) = true // 1 + +a(x) = true // 2 +``` + +## Alternatives + +The current proposal supports arbitrary tree structures on the LHS of the assignment. +A simpler alternative would be to only support flat sequences, allowing the syntax to dispense with parentheses. + +```scala +a, b = b, a +``` + +While this approach is more lightweight, the reduced expressiveness inhibits potentially interesting use cases. +Further, consistently using tuple syntax on both sides of the equality operator clearly distinguishes regular and multiple assignments. + +## Related work + +A Pre-SIP discussion took place prior to this proposal (see [here](https://contributors.scala-lang.org/t/pre-sip-multiple-assignments/6425/1)). + +Multiple assignments are present in many contemporary languages. +This proposal already illustrated them in Swift, but they are also commonly used in Python. +Multiple assigments have also been studied extensively in scienific literature (e.g., [1, 2]). + +## FAQ + +## References + +1. Edsger W. Dijkstra: A Discipline of Programming. Prentice-Hall 1976, ISBN 013215871X +2. Ralph-Johan Back, Joakim von Wright: Refinement Calculus - A Systematic Introduction. Graduate Texts in Computer Science, Springer 1998, ISBN 978-0-387-98417-9 diff --git a/_sips/sips/name-based-xml-literals.md b/_sips/sips/name-based-xml-literals.md new file mode 100644 index 0000000000..1b3e699b01 --- /dev/null +++ b/_sips/sips/name-based-xml-literals.md @@ -0,0 +1,6 @@ +--- +title: SIP-40 - Name Based XML Literals +status: rejected +pull-request-number: 42 + +--- diff --git a/_sips/sips/2010-01-22-named-and-default-arguments.md b/_sips/sips/named-and-default-arguments.md similarity index 99% rename from _sips/sips/2010-01-22-named-and-default-arguments.md rename to _sips/sips/named-and-default-arguments.md index 6b8fb55dd4..3d56605ad7 100644 --- a/_sips/sips/2010-01-22-named-and-default-arguments.md +++ b/_sips/sips/named-and-default-arguments.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-1 Named and Default Arguments -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/named-and-default-arguments.html --- diff --git a/_sips/sips/named-tuples.md b/_sips/sips/named-tuples.md new file mode 100644 index 0000000000..431107cd14 --- /dev/null +++ b/_sips/sips/named-tuples.md @@ -0,0 +1,777 @@ +--- +layout: sip +permalink: /sips/named-tuples.html +stage: completed +status: shipped +presip-thread: https://contributors.scala-lang.org/t/pre-sip-named-tuples/6403/164 +title: SIP-58 - Named Tuples +--- + +**By: Martin Odersky** + +## History + +| Date | Version | +|---------------|--------------------| +| Jan 13th 2024 | Initial Draft | + +## Summary + +We propose to add new form of tuples where the elements are named. +Named tuples can be types, terms, or patterns. Syntax examples: +```scala +type Person = (name: String, age: Int) +val Bob: Person = (name = "Bob", age = 33) + +Bob match + case (name = n, age = 22) => ... +``` + +We also propose to revive SIP 43 to support patterns with named fields. Named pattern fields for case classes are analogous to named patterns for tuple elements. User-defined named pattern matching is supported since named tuples can be results of extractor methods. + +## Motivation + + 1. Named tuples are a convenient lightweight way to return multiple results from a function. But the absence of names obscures their meaning, and makes decomposition with _1, _2 ugly and hard to read. The existing alternative is to define a class instead. This does name fields, but is more heavy-weight, both in terms of notation and generated bytecode. Named tuples give the same convenience of definition as regular tuples at far better readability. + + 1. Named tuples are an almost ideal substrate on which to implement relational algebra and other database oriented operations. They are a good representation of database rows and allow the definition of generic operations such as projections and joins since they can draw on Scala 3’s existing generic machinery for tuples based on match types. + + 1. Named tuples make named pattern matching trivial to implement. The discussion on SIP 43 showed that without them it’s unclear how to implement named pattern matching at all. + +## Proposed solution + +The elements of a tuple can now be named. Example: +```scala +type Person = (name: String, age: Int) +val Bob: Person = (name = "Bob", age = 33) + +Bob match + case (name, age) => + println(s"$name is $age years old") + +val persons: List[Person] = ... +val minors = persons.filter: p => + p.age < 18 +``` +Named bindings in tuples are similar to function parameters and arguments. We use `name: Type` for element types and `name = value` for element values. It is illegal to mix named and unnamed elements in a tuple, or to use the same same +name for two different elements. + +Fields of named tuples can be selected by their name, as in the line `p.age < 18` above. + +Example: + +~~~ scala +// This is an @main method +@main def foo(x: Int): Unit = + println(x) +~~~ + +### Conformance and Convertibility + +The order of names in a named tuple matters. For instance, the type `Person` above and the type `(age: Int, name: String)` would be different, incompatible types. + +Values of named tuple types can also be be defined using regular tuples. For instance: +```scala +val Laura: Person = ("Laura", 25) + +def register(person: Person) = ... +register(person = ("Silvain", 16)) +register(("Silvain", 16)) +``` +This follows since a regular tuple `(T_1, ..., T_n)` is treated as a subtype of a named tuple `(N_1 = T_1, ..., N_n = T_n)` with the same element types. + +In the other direction, one can convert a named tuple to an unnamed tuple with the `toTuple` method. Example: +```scala +val x: (String, Int) = Bob.toTuple // ok +``` +`toTuple` is defined as an extension method in the `NamedTuple` object. +It returns the given tuple unchanged and simply "forgets" the names. + +A `.toTuple` selection is inserted implicitly by the compiler if it encounters a named tuple but the expected type is a regular tuple. So the following works as well: +```scala +val x: (String, Int) = Bob // works, expanded to Bob.toTuple +``` +The difference between subtyping in one direction and automatic `.toTuple` conversions in the other is relatively minor. The main difference is that `.toTuple` conversions don't work inside type constructors. So the following is OK: +```scala + val names = List("Laura", "Silvain") + val ages = List(25, 16) + val persons: List[Person] = names.zip(ages) +``` +But the following would be illegal. +```scala + val persons: List[Person] = List(Bob, Laura) + val pairs: List[(String, Int)] = persons // error +``` +We would need an explicit `_.toTuple` selection to express this: +```scala + val pairs: List[(String, Int)] = persons.map(_.toTuple) +``` +Note that conformance rules for named tuples are analogous to the rules for named parameters. One can assign parameters by position to a named parameter list. +```scala + def f(param: Int) = ... + f(param = 1) // OK + f(2) // Also OK +``` +But one cannot use a name to pass an argument to an unnamed parameter: +```scala + val f: Int => T + f(2) // OK + f(param = 2) // Not OK +``` +The rules for tuples are analogous. Unnamed tuples conform to named tuple types, but the opposite requires a conversion. + +### Pattern Matching + +When pattern matching on a named tuple, the pattern may be named or unnamed. +If the pattern is named it needs to mention only a subset of the tuple names, and these names can come in any order. So the following are all OK: +```scala +Bob match + case (name, age) => ... + +Bob match + case (name = x, age = y) => ... + +Bob match + case (age = x) => ... + +Bob match + case (age = x, name = y) => ... +``` + +### Expansion + +Named tuples are in essence just a convenient syntax for regular tuples. In the internal representation, a named tuple type is represented at compile time as a pair of two tuples. One tuple contains the names as literal constant string types, the other contains the element types. The runtime representation of a named tuples consists of just the element values, whereas the names are forgotten. This is achieved by declaring `NamedTuple` +in package `scala` as an opaque type as follows: +```scala + opaque type NamedTuple[N <: Tuple, +V <: Tuple] >: V = V +``` +For instance, the `Person` type would be represented as the type +```scala +NamedTuple[("name", "age"), (String, Int)] +``` +`NamedTuple` is an opaque type alias of its second, value parameter. The first parameter is a string constant type which determines the name of the element. Since the type is just an alias of its value part, names are erased at runtime, and named tuples and regular tuples have the same representation. + +A `NamedTuple[N, V]` type is publicly known to be a supertype (but not a subtype) of its value paramater `V`, which means that regular tuples can be assigned to named tuples but not _vice versa_. + +The `NamedTuple` object contains a number of extension methods for named tuples hat mirror the same functions in `Tuple`. Examples are +`apply`, `head`, `tail`, `take`, `drop`, `++`, `map`, or `zip`. +Similar to `Tuple`, the `NamedTuple` object also contains types such as `Elem`, `Head`, `Concat` +that describe the results of these extension methods. + +The translation of named tuples to instances of `NamedTuple` is fixed by the specification and therefore known to the programmer. This means that: + + - All tuple operations also work with named tuples "out of the box". + - Macro libraries can rely on this expansion. + +### Computed Field Names + +The `Selectable` trait now has a `Fields` type member that can be instantiated +to a named tuple. + +```scala +trait Selectable: + type Fields <: NamedTuple.AnyNamedTuple +``` + +If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type, +then the available fields and their types will be defined by that type. Assume `n: T` +is an element of the `Fields` type in some class `C` that implements `Selectable`, +that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`. +Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`. + +It is the task of the implementation of `selectDynamic` in `C` to ensure that its +computed result conforms to the predicted type `T` + +As an example, assume we have a query type `Q[T]` defined as follows: + +```scala +trait Q[T] extends Selectable: + type Fields = NamedTuple.Map[NamedTuple.From[T], Q] + def selectDynamic(fieldName: String) = ... +``` + +Assume in the user domain: +```scala +case class City(zipCode: Int, name: String, population: Int) +val city: Q[City] +``` +Then +```scala +city.zipCode +``` +has type `Q[Int]` and it expands to +```scala +city.selectDynamic("zipCode").asInstanceOf[Q[Int]] +``` + +### The NamedTuple.From Type + +The `NamedTuple` object contains a type definition +```scala + type From[T] <: AnyNamedTuple +``` +`From` is treated specially by the compiler. When `NamedTuple.From` is applied to +an argument type that is an instance of a case class, the type expands to the named +tuple consisting of all the fields of that case class. +Here, _fields_ means: elements of the first parameter section. For instance, assuming +```scala +case class City(zip: Int, name: String, population: Int) +``` +then `NamedTuple.From[City]` is the named tuple +```scala +(zip: Int, name: String, population: Int) +``` +The same works for enum cases expanding to case classes, abstract types with case classes as upper bound, alias types expanding to case classes +and singleton types with case classes as underlying type (in terms of the implementation, the `classSymbol` of a type must be a case class). + +`From` is also defined on named tuples. If `NT` is a named tuple type, then `From[NT] = NT`. + +### Pattern Matching with Named Fields in General + +We allow named patterns not just for named tuples but also for case classes. For instance: +```scala +city match + case c @ City(name = "London") => println(c.population) + case City(name = n, zip = 1026, population = pop) => println(pop) +``` + +Named constructor patterns are analogous to named tuple patterns. In both cases + + - every name must match the name some field of the selector, + - names can come in any order, + - not all fields of the selector need to be matched. + +This revives SIP 43, with a much simpler desugaring than originally proposed. +Named patterns are compatible with extensible pattern matching simply because +`unapply` results can be named tuples. + +### Operations on Named Tuples + +The operations on named tuples are defined in object `scala.NamedTuple`. The current version of this object is listed in Appendix A. + +### Restrictions + +The following restrictions apply to named tuples and named pattern arguments: + + 1. Either all elements of a tuple or constructor pattern are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error: + ```scala + val illFormed1 = ("Bob", age = 33) // error + ``` + 2. Each element name in a named tuple or constructor pattern must be unique. For instance, the following is in error: + ```scala + val illFormed2 = (name = "", age = 0, name = true) // error + ``` + 3. Named tuples and case classes can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error: + ```scala + (tuple: Tuple) match + case (age = x) => // error + ``` + +### Use Case + +As a a use case showing some advanced capabilities of named tuples (including computed field names and the `From` type), +we show an implementation of embedded queries in Scala. For expressions that look like working with collections are instead +used to directly generate a query AST that can be further optimized and mapped to a variety of query languages. The code +is given in Appendix B. + +### Syntax Changes + +The syntax of Scala is extended as follows to support named tuples and +named constructor arguments: +``` +SimpleType ::= ... + | ‘(’ NameAndType {‘,’ NameAndType} ‘)’ +NameAndType ::= id ':' Type + +SimpleExpr ::= ... + | '(' NamedExprInParens {‘,’ NamedExprInParens} ')' +NamedExprInParens ::= id '=' ExprInParens + +Patterns ::= Pattern {‘,’ Pattern} + | NamedPattern {‘,’ NamedPattern} +NamedPattern ::= id '=' Pattern +``` + +### Compatibility + +Named tuple types and expressions are simply desugared to types and trees already known to Scala. The desugaring happens before the checking, so does not influence Tasty generation. + +Pattern matching with named fields requires some small additions to Typer and the PatternMatcher phase. It does not change the Tasty format, though. + +Backward source compatibility is partially preserved since additions to types and patterns come with new syntax that was not expressible before. When looking at tuple expressions, we have two instances of a source incompatibility: + +```scala +var age: Int +(age = 1) +``` +This was an assignment in parentheses before, and is a named tuple of arity one now. It is however not idiomatic Scala code, since assignments are not usually enclosed in parentheses. + +Also, if we have +```scala +class C: + infix def f(age: Int) +val c: C +``` +then +```scala +c f (age = 1) +``` +will now construct a tuple as second operand instead of passing a named parameter. + +These problems can be detected and diagnosed fairly straightforwardly: When faced with a unary named tuple, try to interpret it as an assignment, and if that succeeds, issue a migration error and suggest a workaround of these kinds: +```scala + {age = 1} // ok + c.f(age = 1) // ok +``` + +### Open questions + + 1. What is the precise set of types and operations we want to add to `NamedTuple`. This could also evolve after this SIP is completed. + + 2. Should there be an implicit conversion from named tuples to ordinary tuples? + +## Alternatives + +### Structural Types + +We also considered to expand structural types. Structural types allow to abstract over existing classes, but require reflection or some other library-provided mechanism for element access. By contrast, named tuples have a separate representation as tuples, which can be manipulated directly. Since elements are ordered, traversals can be defined, and this allows the definition of type generic algorithms over named tuples. Structural types don’t allow such generic algorithms directly. Be could define mappings between structural types and named tuples, which could be used to implement such algorithms. These mappings would certainly become simpler if they map to/from named tuples than if they had to map to/from user-defined "HMap"s. + +By contrast to named tuples, structural types are unordered and have width subtyping. This comes with the price that no natural element ordering exist, and that one usually needs some kind of dictionary structure for access. We believe that the following advantages of named tuples over structural types outweigh the loss of subtyping flexibility: + + - Better integration since named tuples and normal tuples share the same representation. + - Better efficiency, since no dictionary is needed. + - Natural traversal order allows the formulation of generic algorithms such as projections and joins. + +### Conformance + +A large part of Pre-SIP discussion centered around subtyping rules, whether ordinary tuples should subtype named-tuples (as in this proposal) or _vice versa_ or maybe no subtyping at all. + +Looking at precedent in other languages it feels like we we do want some sort of subtyping for easy convertibility and an implicit conversion in the other direction. This proposal picks _unnamed_ <: _named_ for the subtyping and _named_ -> _unnamed_ for the conversion. + +The discussion established that both forms of subtyping are sound. My personal opinion is that the subtyping of this proposal is both more useful and safer than the one in the other direction. There is also the problem that changing the subtyping direction would be incompatible with the current structure of `Tuple` and `NamedTuple` since for instance `zip` is already an inline method on `Tuple` so it could not be overridden in `NamedTuple`. To make this work requires a refactoring of `Tuple` to use more extension methods, and the questions whether this is feasible and whether it can be made binary backwards compatible are unknown. I personally will not work on this, if others are willing to make the effort we can discuss the alternative subtyping as well. + +_Addendum:_ Turning things around, adopting _named_ <: _unnamed_ for the subtyping and `_unnamed_ -> _named_ for the conversion leads to weaker typing with undetected errors. Consider: +```scala +type Person = (name: String, age: Int) +val bob: Person +bob.zip((firstName: String, agee: Int)) +``` +This should report a type error. +But in the alternative scheme, we'd have `(firstName: String, agee: Int) <: (String, Int)` by subtyping and then +`(String, Int) -> (name: String, age: Int)` by implicit naming conversion. This is clearly not what we want. + +By contrast, in the implemented scheme, we will not convert `(firstName: String, agee: Int)` to `(String, Int)` since a conversion is only attempted if the expected type is a regular tuple, and in our scenario it is a named tuple instead. + +My takeaway is that these designs have rather subtle consequences and any alterations would need a full implementation before they can be judged. For instance, the situation with `zip` was a surprise to me, which came up since I first implemented `_.toTuple` as a regular implicit conversion instead of a compiler adaptation. + +A possibly simpler design would be to drop all conformance and conversion rules. The problem with this approach is worse usability and problems with smooth migration. Migration will be an issue since right now everything is a regular tuple. If we make it hard to go from there to named tuples, everything will tend to stay a regular tuple and named tuples will be much less used than we would hope for. + + +### Spread Operator + +An idea I was thinking of but that I did not include in this proposal highlights another potential problem with subtyping. Consider adding a _spread_ operator `*` for tuples and named tuples. if `x` is a tuple then `f(x*)` is `f` applied to all fields of `x` expanded as individual arguments. Likewise, if `y` is a named tuple, then `f(y*)` is `f` applied to all elements of `y` as named arguments. +Now, if named tuples would be subtypes of tuples, this would actually be ambiguous since widening `y` in `y*` to a regular tuple would yield a different call. But with the subtyping direction we have, this would work fine. + +I believe tuple spread is a potentially useful addition that would fit in well with Scala. But it's not immediately relevant to this proposal, so is left out for now. + + +## Related work + +This section should list prior work related to the proposal, notably: + +- [Pre-SIP Discussion](https://contributors.scala-lang.org/t/pre-sip-named-tuples/6403) + +- [SIP 43 on Pattern Matching with Named Fields](https://github.com/scala/improvement-proposals/pull/44) + +- [Experimental Implementation](https://github.com/scala/scala3/pull/19174) + +## FAQ + +## Appendix A: NamedTuple Definition + +Here is the current definition of `NamedTuple`. This is part of the library and therefore subject to future changes and additions. + +```scala +package scala +import annotation.experimental +import compiletime.ops.boolean.* + +@experimental +object NamedTuple: + + opaque type AnyNamedTuple = Any + opaque type NamedTuple[N <: Tuple, +V <: Tuple] >: V <: AnyNamedTuple = V + + def apply[N <: Tuple, V <: Tuple](x: V): NamedTuple[N, V] = x + + def unapply[N <: Tuple, V <: Tuple](x: NamedTuple[N, V]): Some[V] = Some(x) + + extension [V <: Tuple](x: V) + inline def withNames[N <: Tuple]: NamedTuple[N, V] = x + + export NamedTupleDecomposition.{Names, DropNames} + + extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) + + /** The underlying tuple without the names */ + inline def toTuple: V = x + + /** The number of elements in this tuple */ + inline def size: Tuple.Size[V] = toTuple.size + + // This intentionally works for empty named tuples as well. I think NnEmptyTuple is a dead end + // and should be reverted, justy like NonEmptyList is also appealing at first, but a bad idea + // in the end. + + /** The value (without the name) at index `n` of this tuple */ + inline def apply(n: Int): Tuple.Elem[V, n.type] = + inline toTuple match + case tup: NonEmptyTuple => tup(n).asInstanceOf[Tuple.Elem[V, n.type]] + case tup => tup.productElement(n).asInstanceOf[Tuple.Elem[V, n.type]] + + /** The first element value of this tuple */ + inline def head: Tuple.Elem[V, 0] = apply(0) + + /** The tuple consisting of all elements of this tuple except the first one */ + inline def tail: Tuple.Drop[V, 1] = toTuple.drop(1) + + /** The last element value of this tuple */ + inline def last: Tuple.Last[V] = apply(size - 1).asInstanceOf[Tuple.Last[V]] + + /** The tuple consisting of all elements of this tuple except the last one */ + inline def init: Tuple.Init[V] = toTuple.take(size - 1).asInstanceOf[Tuple.Init[V]] + + /** The tuple consisting of the first `n` elements of this tuple, or all + * elements if `n` exceeds `size`. + */ + inline def take(n: Int): NamedTuple[Tuple.Take[N, n.type], Tuple.Take[V, n.type]] = + toTuple.take(n) + + /** The tuple consisting of all elements of this tuple except the first `n` ones, + * or no elements if `n` exceeds `size`. + */ + inline def drop(n: Int): NamedTuple[Tuple.Drop[N, n.type], Tuple.Drop[V, n.type]] = + toTuple.drop(n) + + /** The tuple `(x.take(n), x.drop(n))` */ + inline def splitAt(n: Int): NamedTuple[Tuple.Split[N, n.type], Tuple.Split[V, n.type]] = + toTuple.splitAt(n) + + /** The tuple consisting of all elements of this tuple followed by all elements + * of tuple `that`. The names of the two tuples must be disjoint. + */ + inline def ++ [N2 <: Tuple, V2 <: Tuple](that: NamedTuple[N2, V2])(using Tuple.Disjoint[N, N2] =:= true) + : NamedTuple[Tuple.Concat[N, N2], Tuple.Concat[V, V2]] + = toTuple ++ that.toTuple + + // inline def :* [L] (x: L): NamedTuple[Append[N, ???], Append[V, L] = ??? + // inline def *: [H] (x: H): NamedTuple[??? *: N], H *: V] = ??? + + /** The named tuple consisting of all element values of this tuple mapped by + * the polymorphic mapping function `f`. The names of elements are preserved. + * If `x = (n1 = v1, ..., ni = vi)` then `x.map(f) = `(n1 = f(v1), ..., ni = f(vi))`. + */ + inline def map[F[_]](f: [t] => t => F[t]): NamedTuple[N, Tuple.Map[V, F]] = + toTuple.map(f).asInstanceOf[NamedTuple[N, Tuple.Map[V, F]]] + + /** The named tuple consisting of all elements of this tuple in reverse */ + inline def reverse: NamedTuple[Tuple.Reverse[N], Tuple.Reverse[V]] = + toTuple.reverse + + /** The named tuple consisting of all elements values of this tuple zipped + * with corresponding element values in named tuple `that`. + * If the two tuples have different sizes, + * the extra elements of the larger tuple will be disregarded. + * The names of `x` and `that` at the same index must be the same. + * The result tuple keeps the same names as the operand tuples. + */ + inline def zip[V2 <: Tuple](that: NamedTuple[N, V2]): NamedTuple[N, Tuple.Zip[V, V2]] = + toTuple.zip(that.toTuple) + + /** A list consisting of all element values */ + inline def toList: List[Tuple.Union[V]] = toTuple.toList.asInstanceOf[List[Tuple.Union[V]]] + + /** An array consisting of all element values */ + inline def toArray: Array[Object] = toTuple.toArray + + /** An immutable array consisting of all element values */ + inline def toIArray: IArray[Object] = toTuple.toIArray + + end extension + + /** The size of a named tuple, represented as a literal constant subtype of Int */ + type Size[X <: AnyNamedTuple] = Tuple.Size[DropNames[X]] + + /** The type of the element value at position N in the named tuple X */ + type Elem[X <: AnyNamedTuple, N <: Int] = Tuple.Elem[DropNames[X], N] + + /** The type of the first element value of a named tuple */ + type Head[X <: AnyNamedTuple] = Elem[X, 0] + + /** The type of the last element value of a named tuple */ + type Last[X <: AnyNamedTuple] = Tuple.Last[DropNames[X]] + + /** The type of a named tuple consisting of all elements of named tuple X except the first one */ + type Tail[X <: AnyNamedTuple] = Drop[X, 1] + + /** The type of the initial part of a named tuple without its last element */ + type Init[X <: AnyNamedTuple] = + NamedTuple[Tuple.Init[Names[X]], Tuple.Init[DropNames[X]]] + + /** The type of the named tuple consisting of the first `N` elements of `X`, + * or all elements if `N` exceeds `Size[X]`. + */ + type Take[X <: AnyNamedTuple, N <: Int] = + NamedTuple[Tuple.Take[Names[X], N], Tuple.Take[DropNames[X], N]] + + /** The type of the named tuple consisting of all elements of `X` except the first `N` ones, + * or no elements if `N` exceeds `Size[X]`. + */ + type Drop[X <: AnyNamedTuple, N <: Int] = + NamedTuple[Tuple.Drop[Names[X], N], Tuple.Drop[DropNames[X], N]] + + /** The pair type `(Take(X, N), Drop[X, N]). */ + type Split[X <: AnyNamedTuple, N <: Int] = (Take[X, N], Drop[X, N]) + + /** Type of the concatenation of two tuples `X` and `Y` */ + type Concat[X <: AnyNamedTuple, Y <: AnyNamedTuple] = + NamedTuple[Tuple.Concat[Names[X], Names[Y]], Tuple.Concat[DropNames[X], DropNames[Y]]] + + /** The type of the named tuple `X` mapped with the type-level function `F`. + * If `X = (n1 : T1, ..., ni : Ti)` then `Map[X, F] = `(n1 : F[T1], ..., ni : F[Ti])`. + */ + type Map[X <: AnyNamedTuple, F[_ <: Tuple.Union[DropNames[X]]]] = + NamedTuple[Names[X], Tuple.Map[DropNames[X], F]] + + /** A named tuple with the elements of tuple `X` in reversed order */ + type Reverse[X <: AnyNamedTuple] = + NamedTuple[Tuple.Reverse[Names[X]], Tuple.Reverse[DropNames[X]]] + + /** The type of the named tuple consisting of all element values of + * named tuple `X` zipped with corresponding element values of + * named tuple `Y`. If the two tuples have different sizes, + * the extra elements of the larger tuple will be disregarded. + * The names of `X` and `Y` at the same index must be the same. + * The result tuple keeps the same names as the operand tuples. + * For example, if + * ``` + * X = (n1 : S1, ..., ni : Si) + * Y = (n1 : T1, ..., nj : Tj) where j >= i + * ``` + * then + * ``` + * Zip[X, Y] = (n1 : (S1, T1), ..., ni: (Si, Ti)) + * ``` + * @syntax markdown + */ + type Zip[X <: AnyNamedTuple, Y <: AnyNamedTuple] = + Tuple.Conforms[Names[X], Names[Y]] match + case true => + NamedTuple[Names[X], Tuple.Zip[DropNames[X], DropNames[Y]]] + + type From[T] <: AnyNamedTuple + +end NamedTuple + +/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */ +@experimental +object NamedTupleDecomposition: + import NamedTuple.* + + /** The names of a named tuple, represented as a tuple of literal string values. */ + type Names[X <: AnyNamedTuple] <: Tuple = X match + case NamedTuple[n, _] => n + + /** The value types of a named tuple represented as a regular tuple. */ + type DropNames[NT <: AnyNamedTuple] <: Tuple = NT match + case NamedTuple[_, x] => x +``` + +## Appendix B: Embedded Queries Case Study + +```scala +import language.experimental.namedTuples +import NamedTuple.{NamedTuple, AnyNamedTuple} + +/* This is a demonstrator that shows how to map regular for expressions to + * internal data that can be optimized by a query engine. It needs NamedTuples + * and type classes but no macros. It's so far very provisional and experimental, + * intended as a basis for further exploration. + */ + +/** The type of expressions in the query language */ +trait Expr[Result] extends Selectable: + + /** This type is used to support selection with any of the field names + * defined by Fields. + */ + type Fields = NamedTuple.Map[NamedTuple.From[Result], Expr] + + /** A selection of a field name defined by Fields is implemented by `selectDynamic`. + * The implementation will add a cast to the right Expr type corresponding + * to the field type. + */ + def selectDynamic(fieldName: String) = Expr.Select(this, fieldName) + + /** Member methods to implement universal equality on Expr level. */ + def == (other: Expr[?]): Expr[Boolean] = Expr.Eq(this, other) + def != (other: Expr[?]): Expr[Boolean] = Expr.Ne(this, other) + +object Expr: + + /** Sample extension methods for individual types */ + extension (x: Expr[Int]) + def > (y: Expr[Int]): Expr[Boolean] = Gt(x, y) + def > (y: Int): Expr[Boolean] = Gt(x, IntLit(y)) + extension (x: Expr[Boolean]) + def &&(y: Expr[Boolean]): Expr[Boolean] = And(x, y) + def || (y: Expr[Boolean]): Expr[Boolean] = Or(x, y) + + // Note: All field names of constructors in the query language are prefixed with `$` + // so that we don't accidentally pick a field name of a constructor class where we want + // a name in the domain model instead. + + // Some sample constructors for Exprs + case class Gt($x: Expr[Int], $y: Expr[Int]) extends Expr[Boolean] + case class Plus(x: Expr[Int], y: Expr[Int]) extends Expr[Int] + case class And($x: Expr[Boolean], $y: Expr[Boolean]) extends Expr[Boolean] + case class Or($x: Expr[Boolean], $y: Expr[Boolean]) extends Expr[Boolean] + + // So far Select is weakly typed, so `selectDynamic` is easy to implement. + // Todo: Make it strongly typed like the other cases + case class Select[A]($x: Expr[A], $name: String) extends Expr + + case class Single[S <: String, A]($x: Expr[A]) + extends Expr[NamedTuple[S *: EmptyTuple, A *: EmptyTuple]] + + case class Concat[A <: AnyNamedTuple, B <: AnyNamedTuple]($x: Expr[A], $y: Expr[B]) + extends Expr[NamedTuple.Concat[A, B]] + + case class Join[A <: AnyNamedTuple](a: A) + extends Expr[NamedTuple.Map[A, StripExpr]] + + type StripExpr[E] = E match + case Expr[b] => b + + // Also weakly typed in the arguents since these two classes model universal equality */ + case class Eq($x: Expr[?], $y: Expr[?]) extends Expr[Boolean] + case class Ne($x: Expr[?], $y: Expr[?]) extends Expr[Boolean] + + /** References are placeholders for parameters */ + private var refCount = 0 + + case class Ref[A]($name: String = "") extends Expr[A]: + val id = refCount + refCount += 1 + override def toString = s"ref$id(${$name})" + + /** Literals are type-specific, tailored to the types that the DB supports */ + case class IntLit($value: Int) extends Expr[Int] + + /** Scala values can be lifted into literals by conversions */ + given Conversion[Int, IntLit] = IntLit(_) + + /** The internal representation of a function `A => B` + * Query languages are ususally first-order, so Fun is not an Expr + */ + case class Fun[A, B](param: Ref[A], f: B) + + type Pred[A] = Fun[A, Expr[Boolean]] + + /** Explicit conversion from + * (name_1: Expr[T_1], ..., name_n: Expr[T_n]) + * to + * Expr[(name_1: T_1, ..., name_n: T_n)] + */ + extension [A <: AnyNamedTuple](x: A) def toRow: Join[A] = Join(x) + + /** Same as _.toRow, as an implicit conversion */ + given [A <: AnyNamedTuple]: Conversion[A, Expr.Join[A]] = Expr.Join(_) + +end Expr + +/** The type of database queries. So far, we have queries + * that represent whole DB tables and queries that reify + * for-expressions as data. + */ +trait Query[A] + +object Query: + import Expr.{Pred, Fun, Ref} + + case class Filter[A]($q: Query[A], $p: Pred[A]) extends Query[A] + case class Map[A, B]($q: Query[A], $f: Fun[A, Expr[B]]) extends Query[B] + case class FlatMap[A, B]($q: Query[A], $f: Fun[A, Query[B]]) extends Query[B] + + // Extension methods to support for-expression syntax for queries + extension [R](x: Query[R]) + + def withFilter(p: Ref[R] => Expr[Boolean]): Query[R] = + val ref = Ref[R]() + Filter(x, Fun(ref, p(ref))) + + def map[B](f: Ref[R] => Expr[B]): Query[B] = + val ref = Ref[R]() + Map(x, Fun(ref, f(ref))) + + def flatMap[B](f: Ref[R] => Query[B]): Query[B] = + val ref = Ref[R]() + FlatMap(x, Fun(ref, f(ref))) +end Query + +/** The type of query references to database tables */ +case class Table[R]($name: String) extends Query[R] + +// Everything below is code using the model ----------------------------- + +// Some sample types +case class City(zipCode: Int, name: String, population: Int) +type Address = (city: City, street: String, number: Int) +type Person = (name: String, age: Int, addr: Address) + +@main def Test = + + val cities = Table[City]("cities") + + val q1 = cities.map: c => + c.zipCode + val q2 = cities.withFilter: city => + city.population > 10_000 + .map: city => + city.name + + val q3 = + for + city <- cities + if city.population > 10_000 + yield city.name + + val q4 = + for + city <- cities + alt <- cities + if city.name == alt.name && city.zipCode != alt.zipCode + yield + city + + val addresses = Table[Address]("addresses") + val q5 = + for + city <- cities + addr <- addresses + if addr.street == city.name + yield + (name = city.name, num = addr.number) + + val q6 = + cities.map: city => + (name = city.name, zipCode = city.zipCode) + + def run[T](q: Query[T]): Iterator[T] = ??? + + def x1: Iterator[Int] = run(q1) + def x2: Iterator[String] = run(q2) + def x3: Iterator[String] = run(q3) + def x4: Iterator[City] = run(q4) + def x5: Iterator[(name: String, num: Int)] = run(q5) + def x6: Iterator[(name: String, zipCode: Int)] = run(q6) +``` diff --git a/_sips/sips/2010-07-20-new-collection-classes.md b/_sips/sips/new-collection-classes.md similarity index 73% rename from _sips/sips/2010-07-20-new-collection-classes.md rename to _sips/sips/new-collection-classes.md index ea7371be48..d8bb9b8047 100644 --- a/_sips/sips/2010-07-20-new-collection-classes.md +++ b/_sips/sips/new-collection-classes.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-3 - New Collection classes -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/new-collection-classes.html --- diff --git a/_sips/sips/2017-09-20-opaque-types.md b/_sips/sips/opaque-types.md similarity index 99% rename from _sips/sips/2017-09-20-opaque-types.md rename to _sips/sips/opaque-types.md index 9847baea50..a33083181d 100644 --- a/_sips/sips/2017-09-20-opaque-types.md +++ b/_sips/sips/opaque-types.md @@ -1,12 +1,14 @@ --- layout: sip title: SIP-35 - Opaque types - -vote-status: accepted +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/opaque-types.html --- +> This proposal has been implemented with some changes in Scala 3.0.0. + **Authors: Erik Osheim and Jorge Vicente Cantero** **Supervisor and advisor: Sébastien Doeraene** @@ -50,11 +52,8 @@ For a definition of boxing and previous state-of-the-art, we recommend reading [ ### Implementation note -The proposal is currently in an early stage. -[It’s being implemented](https://github.com/scalacenter/scala/tree/opaque-type), -but the proposed implementation strategy is not detailed enough to be able -to predict with certainty that it will work as specified. Consequently, -details of the proposal might change driven by implementation concerns. +Opaque types have been implemented in Scala 3.0.0 with +[some changes compared to this proposal]({{ site.scala3ref }}/other-new-features/opaques-details.html#relationship-to-sip-35). ## Opaque types diff --git a/_sips/sips/pattern-matching-with-named-fields.md b/_sips/sips/pattern-matching-with-named-fields.md new file mode 100644 index 0000000000..c1c304a61f --- /dev/null +++ b/_sips/sips/pattern-matching-with-named-fields.md @@ -0,0 +1,6 @@ +--- +title: SIP-43 - Pattern matching with named fields +status: withdrawn +pull-request-number: 44 + +--- diff --git a/_sips/sips/2010-06-01-picked-signatures.md b/_sips/sips/picked-signatures.md similarity index 75% rename from _sips/sips/2010-06-01-picked-signatures.md rename to _sips/sips/picked-signatures.md index 974dfdbd34..12f205a4e1 100644 --- a/_sips/sips/2010-06-01-picked-signatures.md +++ b/_sips/sips/picked-signatures.md @@ -1,8 +1,8 @@ --- 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. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/picked-signatures.html --- diff --git a/_sips/sips/polymorphic-eta-expansion.md b/_sips/sips/polymorphic-eta-expansion.md new file mode 100644 index 0000000000..4099e05cc8 --- /dev/null +++ b/_sips/sips/polymorphic-eta-expansion.md @@ -0,0 +1,429 @@ +--- +layout: sip +title: SIP-49 - Polymorphic Eta-Expansion +stage: implementation +status: waiting-for-implementation +permalink: /sips/:title.html +--- + +**By: Quentin Bernet and Guillaume Martres** + +## History + +| Date | Version | +|---------------|--------------------| +| Sep 23th 2022 | Initial Draft | + +## Summary + + + +We propose to extend eta-expansion to polymorphic methods. +This means automatically transforming polymorphic methods into corresponding polymorphic functions when required, for example: + +~~~ scala +def f1[A](x: A): A = ??? +val v1_1: [B] => B => B = f1 // f1 becomes [B'] => (y: B') => f1[B'](y) +~~~ + +Returning readers, for a quick glance at a wide array of examples illustrated like the above, go to [High-level overview](#high-level-overview). + +In the following, "Note" never introduces new concepts, it points out a non-obvious consequence, and/or reminds the reader of a pertinent fact about Scala. + +## Motivation + + + +Regular eta-expansion is so ubiquitous that most users are not aware of it, for them it is intuitive and obvious that methods can be passed where functions are expected. + +When manipulating polymorphic methods, we wager that most users find it confusing not to be able to do the same. +This is the main motivation of this proposal. + +It however remains to be demonstrated that such cases appear often enough for time and maintenance to be devoted to fixing it. +To this end, the remainder of this section will show a manufactured example with tuples, as well as real-world examples taken from the [Shapeless-3](https://index.scala-lang.org/typelevel/shapeless-3) and [kittens](https://index.scala-lang.org/typelevel/kittens) libraries. + + +#### Tuples + +~~~ scala +List(1, 2, 3).map(Some.apply) // works + +("Hello", 2, 'u').map(Some.apply) // error: +// Found: Any => Some[Any], Required: [t] => (t) => Nothing +~~~ + +As tuples are becoming a powerful part of metaprogramming through `Mirror` instances, we expect these kinds of cases to become more and more frequent. + +#### Shapeless ([source](https://github.com/typelevel/shapeless-3/blob/8b1bbc651618e77e0bd7c2433b79e46adafa4506/modules/deriving/src/test/scala/shapeless3/deriving/type-classes.scala#L651-L665)) + + +In the shapeless library, polymorphic functions are used relatively often, but they are usually small and unique, making them not very suitable for refactoring. +There is however the following case, where a function is very large: + +~~~ scala +... + def readElems(s: String): Option[(T, String)] = { + type Acc = (String, Seq[String], Boolean) + inst.unfold[Acc]((s, labelling.elemLabels, true))( + [t] => (acc: Acc, rt: Read[t]) => { + val (s, labels, first) = acc + (for { + (_, tl0) <- if(first) Some(("", s)) else head(s, "(,)(.*)".r) + (_, tl1) <- head(tl0, s"(${labels.head}):(.*)".r) + (t, tl2) <- rt.read(tl1) + } yield (t, tl2)) match { + case Some(t, tl2) => ((tl2, labels.tail, false), Some(t)) + case None => ((s, labels, first), None) + } + } + ) match { + case (s, None) => None + case (acc, Some(t)) => Some((t, acc._1)) + } + } +~~~ + +By factoring out the function, it is possible to make the code more readable: + +~~~ scala +... + def readElems(s: String): Option[(T, String)] = { + type Acc = (String, Seq[String], Boolean) + val unfolder = [t] => (acc: Acc, rt: Read[t]) => { + val (s, labels, first) = acc + (for { + (_, tl0) <- if(first) Some(("", s)) else head(s, "(,)(.*)".r) + (_, tl1) <- head(tl0, s"(${labels.head}):(.*)".r) + (t, tl2) <- rt.read(tl1) + } yield (t, tl2)) match { + case Some(t, tl2) => ((tl2, labels.tail, false), Some(t)) + case None => ((s, labels, first), None) + } + } + inst.unfold[Acc]((s, labelling.elemLabels, true))(unfolder) match { + case (s, None) => None + case (acc, Some(t)) => Some((t, acc._1)) + } + } +~~~ + +It is natural at this point to want to transform the function into a method, as the syntax for the latter is more familiar, and more readable: + +~~~ scala +... + def readElems(s: String): Option[(T, String)] = { + type Acc = (String, Seq[String], Boolean) + def unfolder[T](acc: Acc, rt: Read[T]): Acc = { + val (s, labels, first) = acc + (for { + (_, tl0) <- if(first) Some(("", s)) else head(s, "(,)(.*)".r) + (_, tl1) <- head(tl0, s"(${labels.head}):(.*)".r) + (t, tl2) <- rt.read(tl1) + } yield (t, tl2)) match { + case Some(t, tl2) => ((tl2, labels.tail, false), Some(t)) + case None => ((s, labels, first), None) + } + } + inst.unfold[Acc]((s, labelling.elemLabels, true))(unfolder) match { + case (s, None) => None + case (acc, Some(t)) => Some((t, acc._1)) + } + } +~~~ + +However, this does not compile. +Only monomorphic eta-expansion is applied, leading to the same issue as with our previous `Tuple.map` example. + +#### Kittens ([source](https://github.com/typelevel/kittens/blob/e10a03455ac3dd52096a1edf0fe6d4196a8e2cad/core/src/main/scala-3/cats/derived/DerivedTraverse.scala#L44-L48)) + +In `Kittens`, there is a case of particularly obvious eta-expansion done by hand (comments by me): + +~~~ scala +... + final override def traverse[G[_], A, B](fa: F[A])(f: A => G[B]) + (using G: Applicative[G]): G[F[B]] = + val pure = [a] => (x: a) => G.pure(x) // eta-expansion + val map = [a, b] => (ga: G[a], f: a => b) => G.map(ga)(f) // ~eta-expansion + val ap = [a, b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga) // ~eta-expansion + inst.traverse[A, G, B](fa)(map)(pure)(ap)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f)) + +~~~ + +Sadly since `map` and `ap` are curried, assuming this proposal, only `pure` can be eliminated: + +~~~ scala +... + final override def traverse[G[_], A, B](fa: F[A])(f: A => G[B]) + (using G: Applicative[G]): G[F[B]] = + val map = [a, b] => (ga: G[a], f: a => b) => G.map(ga)(f) + val ap = [a, b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga) + inst.traverse[A, G, B](fa)(map)(G.pure)(ap)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f)) + +~~~ + +This already helps with readability, but we can postulate that given cases like this, an uncurried variant of `map` and `ap` would be implemented, allowing us to write: + +~~~ scala +... + final override def traverse[G[_], A, B](fa: F[A])(f: A => G[B]) + (using G: Applicative[G]): G[F[B]] = + inst.traverse[A, G, B](fa)(G.map)(G.pure)(G.ap)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f)) +~~~ + +If wanted, we can then factor the function into a method: + +~~~ scala +... + final override def traverse[G[_], A, B](fa: F[A])(f: A => G[B]) + (using G: Applicative[G]): G[F[B]] = + def traverser[F[_]](tf: T[F], fa: F[A]) = tf.traverse(fa)(f) + inst.traverse[A, G, B](fa)(G.map)(G.pure)(G.ap)(traverser) +~~~ + +## Proposed solution + + + +### High-level overview + +As the previous section already describes how polymorphic eta-expansion affects the examples, we will use this section to give quantity of small, illustrative, examples, that should cover most of the range of this proposal. + +In the following `id'` means a copy of `id` with a fresh name, and `//reminder:` sections are unchanged by this proposal. +#### Explicit parameters: +~~~ scala +def f1[A](x: A): A = ??? +val v1_1: [B] => B => B = f1 // f1 becomes [B'] => (y: B') => f1[B'](y) + +def f2[A]: A => A = ??? +val v2_1: [B] => B => B = f2 // f2 becomes [B'] => (y: B') => f2[B'](y) + +type F[C] = C => C +def f3[A]: F[A] = ??? +val v3_1: [B] => B => B = f3 // f3 becomes [B'] => (y: B') => f3[B'](y) + +//reminder: +val vErr: [B] => B = ??? // error: polymorphic function types must have a value parameter +~~~ + +#### Extension/Interleaved method: +~~~ scala +extension (x: Int) + def extf1[A](x: A): A = ??? + +val extv1_1: [B] => B => B = extf1(4) // extf1(4) becomes [B'] => (y: B') => extf1(4)[B'](y) + +val extv1_3: Int => [B] => B => B = extf1 // extf1 becomes (i: Int) => [B'] => (y: B') => extf1(i)[B'](y) + +// See https://docs.scala-lang.org/sips/clause-interleaving.html +def interleaved(key: Key)[V >: key.Value](default: V): V = ??? +val someKey: Key = ??? +val interleaved_1: [A >: someKey.Value] => A => A = interleaved(someKey) +// interleaved(someKey) becomes [A' >: someKey.Value] => (default: A') => interleaved(someKey)[A'](default) +~~~ + +#### Implicit parameters: +~~~ scala +def uf1[A](using x: A): A = ??? +val vuf1_1: [B] => B ?=> B = uf1 // uf1 becomes [B'] => (y: B') ?=> uf1[B'] + + +def uf2[A]: A = ??? +val vuf2: [B] => B ?=> B = uf2 // uf2 becomes [B'] => (y: B') ?=> uf2[B'] + +//reminder: +val get: (String) ?=> Int = 22 // 22 becomes (s: String) ?=> 22 +val err: () ?=> Int = ?? // error: context functions require at least one parameter +~~~ + +### Specification + + + +Before we go on, it is important to clarify what we mean by "polymorphic method", we do not mean, as one would expect, "a method taking at least one type parameter clause", but rather "a (potentially partially applied) method whose next clause is a type clause", here is an example to illustrate: + +~~~ scala +extension (x: Int) + def poly[T](x: T): T = x +// signature: (Int)[T](T): T + +poly(4) // polymorphic method: takes a [T] +poly // monomorphic method: takes an (Int) +~~~ + +Note: Since typechecking is recursive, eta-expansion of a monomorphic method like `poly` can still trigger polymorphic eta-expansion, for example: + +~~~ scala +val voly: Int => [T] => T => T = poly +// poly expands to: (x: Int) => [T] => (y: T) => poly(x)[T](y) +~~~ + +As this feature only provides a shortcut to express already definable objects, the only impacted area is the type system. + +When typing a polymorphic method `m` there are two cases to consider: + +#### Polymorphic expected type +If the expected type is a polymorphic function taking `[T_1 <: U_1 >: L_1, ..., T_n <: U_n >: L_n]` as type parameters, `(A_1, ..., A_k)` as term parameters and returning `R`, we proceed as follows: + +Note: Polymorphic functions always take term parameters (but `k` can equal zero if the clause is explicit: `[T] => () => T`). + +1. Copies of `T_i`s are created, and replaced in `U_i`s, `L_i`s, `A_i`s and `R`, noted respectively `T'_i`, `U'_i`, `L'_i`, `A'_i` and `R'`. + +2. Is the expected type a polymorphic context function ? +* 1. If yes then `m` is replaced by the following: +~~~ scala +[T'_1 <: U'_1 >: L'_1, ... , T'_n <: U'_n >: L'_n] + => (a_1: A'_1 ..., a_k: A'_k) + ?=> m[T'_1, ..., T'_n] +~~~ +* 2. If no then `m` is replaced by the following: +~~~ scala +[T'_1 <: U'_1 >: L'_1, ... , T'_n <: U'_n >: L'_n] + => (a_1: A'_1 ..., a_k: A'_k) + => m[T'_1, ..., T'_n](a_1, ..., a_k) +~~~ + +3. the application of `m` is type-checked with expected type `R'` +* 1. If it succeeds, the above is the created tree. +* 2. If it fails, go to Default. + +At 3.ii. if the cause of the error is such that [Non-polymorphic expected type](#non-polymorphic-expected-type) will never succeed, we might return that error directly, this is at the discretion of the implementation, to make errors as clear as possible. + +Note: Type checking will be in charge of overloading resolution, as well as term inference, so the following will work: +~~~ scala +def f[A](using Int)(x: A)(using String): A +def f[B](x: B, y: B): B + +given i: Int = ??? +given s: String = ??? +val v: [T] => T => T = f +// f expands to: [T'] => (t: T') => f[T'](t) +// and then to: [T'] => (t: T') => f[T'](using i)(t)(using s) + +def g[C](using C): C +val vg: [T] => T ?=> T = g +// g expands to: [T'] => (t: T') ?=> g[T'] +// and then to: [T'] => (t: T') ?=> g[T'](using t) +~~~ + +Note: Type checking at 3. will have to recursively typecheck `m[T'_1, ..., T'_n](a_1, ..., a_k)` with expected type `R`, this can lead to further eta-expansion: +~~~ scala +extension [A](x: A) + def foo[B](y: B) = (x, y) + +val voo: [T] => T => [U] => U => (T, U) = foo +// foo expands to: +// [T'] => (t: T') => ( foo[T'](t) with expected type [U] => U => (T', U) ) +// [T'] => (t: T') => [U'] => (u: U') => foo[T'](t)[U'](u) +~~~ + +#### Non-polymorphic expected type + +No polymorphic eta-expansion is performed, this corresponds to the old behaviour, written here as a reminder: + +Fresh variables are applied to `m`, typing constraints are generated, and typing continues, for example: + +~~~ scala +def ident[T](x: T): T = x + +val idInt: Int => Int = ident +// ident becomes: +// ident[X] with expected type Int => Int +// (x: X) => ident[X](x) of type X => X with expected type Int => Int +// therefore X := Int +// (x: Int) => ident[Int](x) +~~~ + +### Compatibility + + + +#### Binary and TASTy + +As this proposal never generates code that couldn't have been written by hand before, these changes are binary and TASTy compatible. + +#### Source + +This proposal conserves source compatibility when a non-polymorphic expected type is present, or when there is no expected type, since by definition the behaviour is the same. + +In the case the expected type is polymorphic, either the code did not compile before, or there was an implicit conversion from the inferred monomorphic function to the expected polymorphic function. In the latter case, source compatibility is broken, since polymorphic eta-expansion will apply before search for implicit conversions, for example: + +```scala +import scala.language.implicitConversions + +given conv: Conversion[Any => Any, [T] => T => T] = f => ([T] => (x: T) => x) + +def method[T](x: T): T = x + +val function: [T] => T => T = method +// before: method is eta-expanded to Any => Any, and then converted using conv to [T] => T => T +// now: method is eta-expanded to [T] => T => T (conv is not called) +``` + +### Restrictions + +Not included in this proposal are: + +* Applying polymorphic eta-expansion when there is no return type +* Expanding `[T] => T => T` to `[T] => T => Id[T]` to make `tuple.map(identity)` work (might work out of the box anyways, but not guaranteed) +* Expanding `x => x` to `[T] => (x: T) => x` if necessary (and generalizations) +* Expanding `_` to `[T] => (x: T) => x` if necessary (and generalizations) +* Polymorphic SAM conversion +* Polymorphic functions from wildcard: `foo[_](_)` + +While all of the above could be argued to be valuable, we deem they are out of the scope of this proposal. + +We encourage the creation of follow-up proposals to motivate their inclusion. + + + +### Open questions + + + + + + +## Related work + + + +* Pre-SIP: https://contributors.scala-lang.org/t/polymorphic-eta-expansion/5516 +* A naive implementation can be found at https://github.com/scala/scala3/pull/14015 (it is more general than this proposal and thus breaks compatibility) +* A compatibility-preserving implementation is in development. + + +## FAQ + + diff --git a/_sips/sips/precise-type-modifier.md b/_sips/sips/precise-type-modifier.md new file mode 100644 index 0000000000..9c316bbd8c --- /dev/null +++ b/_sips/sips/precise-type-modifier.md @@ -0,0 +1,6 @@ +--- +title: SIP-48 - Precise Type Modifier +status: withdrawn +pull-request-number: 48 + +--- diff --git a/_sips/sips/2017-02-07-priority-based-infix-type-precedence.md b/_sips/sips/priority-based-infix-type-precedence.md similarity index 94% rename from _sips/sips/2017-02-07-priority-based-infix-type-precedence.md rename to _sips/sips/priority-based-infix-type-precedence.md index 295f047dec..a67d84dbb5 100644 --- a/_sips/sips/2017-02-07-priority-based-infix-type-precedence.md +++ b/_sips/sips/priority-based-infix-type-precedence.md @@ -1,7 +1,8 @@ --- layout: sip title: SIP-33 - Priority-based infix type precedence -vote-status: complete +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/priority-based-infix-type-precedence.html --- @@ -17,7 +18,7 @@ redirect_from: /sips/pending/priority-based-infix-type-precedence.html | Feb 10th 2017 | Updates from feedback | | Aug 8th 2017 | Numbered SIP, improve view, fixed example, and added related issues | | Oct 20th 2017 | Added implementation link | -| Oct 25th 2017 | Moved prefix types to [another SIP](https://docs.scala-lang.org/sips/adding-prefix-types.html), changed title and PR | +| Oct 25th 2017 | Moved prefix types to [another SIP](https://github.com/scala/improvement-proposals/pull/35), changed title and PR | | Nov 29th 2017 | Updated SIP according to feedback in the PR | @@ -120,9 +121,9 @@ A PR for this SIP is available at: [https://github.com/scala/scala/pull/6147](ht ### Interactions with other language features -#### Star `*` infix type interaction with repeated parameters -The [repeated argument symbol `*`](https://www.scala-lang.org/files/archive/spec/2.12/04-basic-declarations-and-definitions.html#repeated-parameters) may create confusion with the infix type `*`. -Please note that this feature interaction already exists within the current specification. +#### Star `*` infix type interaction with repeated parameters +The [repeated argument symbol `*`](https://www.scala-lang.org/files/archive/spec/2.12/04-basic-declarations-and-definitions.html#repeated-parameters) may create confusion with the infix type `*`. +Please note that this feature interaction already exists within the current specification. ```scala trait +[N1, N2] @@ -136,11 +137,11 @@ However, it is very unlikely that such interaction would occur. **Related Issues** -* [Dotty Issue #1961](https://github.com/lampepfl/dotty/issues/1961) +* [Dotty Issue #1961](https://github.com/scala/scala3/issues/1961) ## Backward Compatibility -Changing infix type associativity and precedence affects code that uses type operations and conforms to the current specification. +Changing infix type associativity and precedence affects code that uses type operations and conforms to the current specification. Note: changing the infix precedence didn't fail any scalac test. diff --git a/_sips/sips/quote-pattern-type-variable-syntax.md b/_sips/sips/quote-pattern-type-variable-syntax.md new file mode 100644 index 0000000000..64f58fafb8 --- /dev/null +++ b/_sips/sips/quote-pattern-type-variable-syntax.md @@ -0,0 +1,145 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: completed +status: shipped +title: SIP-53 - Quote pattern explicit type variable syntax +--- + +**By: Nicolas Stucki** + +## History + +| Date | Version | +|---------------|--------------------| +| Feb 28th 2022 | Initial Draft | +| Oct 6th 2022 | [Stabilize implementation](https://github.com/scala/scala3/pull/18574) | +| Feb 29th 2023 | Available in [Scala 3.4.0](https://www.scala-lang.org/blog/2024/02/29/scala-3.4.0-and-3.3.3-released.html) | + +## Summary + +This SIP proposes additions to the syntax of type variable definitions to bring quoted type matching to par with quoted expression matching. +Specifically, the ability to declare type variables explicitly and define them with bounds. + +It also proposes some enhancements to make the use of type variables simpler. +The idea is to reduce the number of cases where we need to write backticks around type variable name references. +Namely when using explicit type variable definitions. + +## Motivation + +### Background + +* [Reference Documentation](http://dotty.epfl.ch/docs/reference/metaprogramming/macros.html#type-variables) + +Quoted expressions support two ways of defining type variables: explicit and nested. + +##### Explicit type variables +The initial `type` declarations in the pattern with a type variable name (lowercase names as with normal pattern type variables) are type variable definitions. Type variable references need to be in backticks. Otherwise, we assume they are nested type variables and emit an error. These definitions can have bounds defined on them. +```scala +case '{ type t; $x: `t` } => f[t](x: Expr[t]) +case '{ type u; ($ls: List[`u`]).map($f: `u` => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int]) +case '{ type tail <: Tuple; $x: *:[Int, `tail`] } => h[tail](x: Expr[*:[Int, tail]) +``` + +##### Nested type variable +Types with a type variable name introduce a new type variable. These cannot be references with a backticked reference due to their scope. We cannot add explicit bounds to them, but in certain cases we can infer their some bounds. These variables become explicit type variables in the internal representation after typing. +```scala +case '{ $x: t } => f[t](x: Expr[t]) +``` + + +##### Type Patterns +Quoted type patterns only support nested type variable definitions. Explicit type variables are not supported in the source due to an oversight. These variables become explicit type variables in the internal representation after typing. The bounds of the type variable are `Any` and `Nothing`. +```scala +case '[ t ] => f[t] +case '[ List[t] ] => g[t] +``` + +### Support type bounds in quoted type patterns + +We want to be able to set the bounds of type variables to be able to match against type constructors that have type bounds. For example, the tuple `*:` type. +```scala +case '[ head *: tail ] => h[tail] +``` +See [https://github.com/scala/scala3/issues/11738](https://github.com/scala/scala3/issues/11738). + +### Support matching on any kind of type +We want to match against higher-kinded (or `AnyKind`) types. This is not possible due to the default upper bound of `Any`. +See [https://github.com/scala/scala3/issues/10864](https://github.com/scala/scala3/issues/10864). + +### Support multiple references to the same type in quoted type patterns +We want to be able to match using several references to the same type variable. +```scala +case '[ (t, t, t) ] => f[t] // t is going to match the glb of the tuple T1, T2, T3 +``` + +### Simplify the use of explicit type variables +It is inconsistent to need to use backticks for references to explicit type variables in the quote but not outside. +We want to be able to refer to the variable by its non-backticked name uniformly. +```diff +- case '{ type u; ($ls: List[`u`]).map($f: `u` => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int]) ++ case '{ type u; ($ls: List[u]).map($f: u => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int]) +``` + +## Proposed solution + +### High-level overview + +We first want to introduce syntax for explicit type variable definitions in quoted type patterns that aligns with expression quoted patterns. We can use the syntax described in [explicit type variables](#explicit-type-variables). + +```scala +case '[ type t; List[`t`] ] => f[t] +case '[ type tail <: Tuple; *:[Int, `tail`] ] => g[tail] +``` + +Second, we want the remove the need for backticks for references to explicit type variable definitions. If we have an explicit type variable definition and a type variable with the same name, we can syntactically assume these are the same and not introduce a new nested type variable. +```scala +case '{ type t; $x: t } => f[t](x: Expr[t]) +case '{ type u; ($ls: List[u]).map($f: u => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int]) +case '{ type tail <: Tuple; $x: *:[Int, tail] } => h[tail](x: Expr[*:[Int, tail]) +``` +```scala +case '[ type t; List[t] ] => f[t] +case '[ type tail <: Tuple; *:[Int, tail] ] => g[tail] +``` + +### Specification + +Adding the explicit type variable definition to quoted type patterns is relatively straightforward as nested type variables become explicit ones internally. We would only need to update the parser to accept a new kind of type in the syntax. This type would only be used in the quote type pattern syntax, which is self-contained in the language's grammar. + +```diff +Quoted ::= ‘'’ ‘{’ Block ‘}’ +- | ‘'’ ‘[’ Type ‘]’ ++ | ‘'’ ‘[’ TypeBlock ‘]’ ++TypeBlock ::= {TypeBlockStat semi} Type ++TypeBlockStat ::= ‘type’ {nl} TypeDcl +``` + +Allowing non-backticked references to explicit type variable definitions would not create any conflict, as these would currently cause a double definition error. The grammar would not need to change. This would only interact with the process of typing quoted patterns. + +### Compatibility + +There are no compatibility issues because the parser or typer rejected all these cases. + +TASTy only contains explicit type variable definitions, and this encoding would not change. Note that TASTy supports _type blocks_ using the regular `Block` AST. These contain type declaration in their statements and a type instead of the expression. + +### Other concerns + +* Tools that parse Scala code must be updated with this new grammar. +* Tools that use TASTy would not be affected. + + + +## Alternatives + +* We could find a different syntax for explicit type variables in quoted type patterns. The drawback is that we need to specify and explain a secondary syntax. +* Don't include the backticks improvements. + +## Related work + +* Proof of concept of type variable syntax: [https://github.com/scala/scala3/pull/16910](https://github.com/scala/scala3/pull/16910) +* Proof of concept of backticks (only interested in the first bullet point): [https://github.com/scala/scala3/pull/16935](https://github.com/scala/scala3/pull/16935) +* Implementation: [https://github.com/scala/scala3/pull/17362](https://github.com/scala/scala3/pull/17362) +* Stabilized implementation: [https://github.com/scala/scala3/pull/18574](https://github.com/scala/scala3/pull/18574) + + diff --git a/_sips/sips/repeated-by-name-parameters.md b/_sips/sips/repeated-by-name-parameters.md new file mode 100644 index 0000000000..6949fcf87c --- /dev/null +++ b/_sips/sips/repeated-by-name-parameters.md @@ -0,0 +1,6 @@ +--- +title: SIP-24 - Repeated By Name Parameters +status: withdrawn +pull-request-number: 23 + +--- diff --git a/_sips/sips/replace-nonsensical-unchecked-annotation.md b/_sips/sips/replace-nonsensical-unchecked-annotation.md new file mode 100644 index 0000000000..3bb8609940 --- /dev/null +++ b/_sips/sips/replace-nonsensical-unchecked-annotation.md @@ -0,0 +1,260 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +presip-thread: https://contributors.scala-lang.org/t/pre-sip-replace-non-sensical-unchecked-annotations/6342 +title: SIP-57 - Replace non-sensical @unchecked annotations +--- + +**By: Martin Odersky and Jamie Thompson** + +## History + +| Date | Version | +|---------------|--------------------| +| Dec 8th 2023 | Initial Draft | +| Jan 19th 2024 | Clarification about current @unchecked behavior | + +## Summary + +We propose to replace the mechanism to silence warnings for "unchecked" patterns, in the cases where silencing the warning will still result in the pattern being checked at runtime. + +Currently, a user can silence warnings that a scrutinee may not be matched by a pattern, by annotating the scrutinee with the `@unchecked` annotation. This SIP proposes to use a new annotation `@RuntimeCheck` to replace `@unchecked` for this purpose. For convenience, an extension method will be added to `Predef` that marks the receiver with the annotation (used as follows: `foo.runtimeCheck`). Functionally it behaves the same as the old annotation, but improves readability at the callsite. + +## Motivation + +As described in [Scala 3 Reference: Pattern Bindings](https://docs.scala-lang.org/scala3/reference/changed-features/pattern-bindings.html), under `-source:future` it is an error for a pattern definition to be refutable. For instance, consider: +```scala +def xs: List[Any] = ??? +val y :: ys = xs +``` + +This compiled without warning in 3.0, became a warning in 3.2, and we would like to make it an error by default in a future 3.x version. +As an escape hatch we recommend to use `@unchecked`: +``` +-- Warning: ../../new/test.scala:6:16 ------------------------------------------ +6 | val y :: ys = xs + | ^^ + |pattern's type ::[Any] is more specialized than the right hand side expression's type List[Any] + | + |If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, + |which may result in a MatchError at runtime. +``` +Similarly for non-exhaustive `match` expressions, where we also recommend to put `@unchecked` on the scrutinee. + +But `@unchecked` has several problems. First, it is ergonomically bad. For instance to fix the exhaustivity warning in +```scala +xs match + case y :: ys => ... +``` +we'd have to write +``` +(xs: @unchecked) match + case y :: ys => ... +``` +Having to wrap the `@unchecked` in parentheses requires editing in two places, and arguably harms readability: both due to the churn in extra symbols, and because in this use case the `@unchecked` annotation poorly communicates intent. + +Nominally, the purpose of the annotation is to silence warnings (_from the [API docs](https://www.scala-lang.org/api/3.3.1/scala/unchecked.html#)_): +> An annotation to designate that the annotated entity should not be considered for additional compiler checks. + +_The exact meaning of this description is open to interpretation, leading to differences between Scala 2.13 and Scala 3.x. See the [misinterpretation](#misinterpretation-of-unchecked) annex for more._ + + +In the following code however, the word `unchecked` is a misnomer, so could be confused for another meaning by an inexperienced user: + +```scala +def xs: List[Any] = ??? +val y :: ys = xs: @unchecked +``` + After all, the pattern `y :: ys` _is_ checked, but it is done at runtime (by looking at the runtime class), rather than statically. + +As a direct contradiction, in the following usage of `unchecked`, the meaning is the opposite: +```scala +xs match + case ints: List[Int @unchecked] => +``` +Here, `@unchecked` means that the `Int` parameter will _not_ be checked at runtime: The compiler instead trusts the user that `ints` is a `List[Int]`. This could lead to a `ClassCastException` in an unrelated piece of code that uses `ints`, possibly without leaving a clear breadcrumb trail of where the faulty cast originally occurred. + +## Proposed solution + +### High-level overview + +This SIP proposes to fix the ergnomics and readability of `@unchecked` in the usage where it means "checked at runtime", by instead adding a new annotation `scala.internal.RuntimeCheck`. + +```scala +package scala.annotation.internal + +final class RuntimeCheck extends Annotation +``` + +In all usages where the compiler looks for `@unchecked` for this purpose, we instead change to look for `@RuntimeCheck`. + +By placing the annotation in the `internal` package, we communicate that the user is not meant to directly use the annotation. + +Instead, for convenience, we provide an extension method `Predef.runtimeCheck`, which can be applied to any expression. + +The new usage to assert that a pattern is checked at runtime then becomes as follows: +```scala +def xs: List[Any] = ??? +val y :: ys = xs.runtimeCheck +``` + +We also make `runtimeCheck` a transparent inline method. This ensures that the elaboration of the method defines its semantics. (i.e. `runtimeCheck` is not meaningful because it is immediately inlined at type-checking). + +### Specification + +The addition of a new `scala.Predef` method: + +```scala +package scala + +import scala.annotation.internal.RuntimeCheck + +object Predef: + extension [T](x: T) + transparent inline def runtimeCheck: x.type = + x: @RuntimeCheck +``` + +### Compatibility + +This change carries the usual backward binary and TASTy compatibility concerns as any other standard library addition to the Scala 3 only library. + +Considering backwards source compatibility, the following situation will change: + +```scala +// source A.scala +package example + +extension (predef: scala.Predef.type) + transparent inline def runtimeCheck[T](x: T): x.type = + println("fake runtimeCheck") + x +``` +```scala +// source B.scala +package example + +@main def Test = + val xs = List[Any](1,2,3) + val y :: ys = Predef.runtimeCheck(xs) + assert(ys == List(2, 3)) +``` + +Previously this code would print `fake runtimeCheck`, however with the proposed change then recompiling this code will _succeed_ and no longer will print. + +Potentially we could mitigate this if necessary with a migration warning when the new method is resolved (`@experimental` annotation would be a start) + + +In general however, the new `runtimeCheck` method will not change any previously linking method without causing an ambiguity compilation error. + +### Other concerns + +In 3.3 we already require the user to put `@unchecked` to avoid warnings, there is likely a significant amount of existing code that will need to migrate to the new mechanism. (We can leverage already exisiting mechanisms help migrate code automatically). + +### Open questions + +1) A large question was should the method or annotation carry semantic weight in the language. In this proposal we weigh towards the annotation being the significant element. +The new method elaborates to an annotated expression before the associated pattern exhaustivity checks occur. +2) Another point, where should the helper method go? In Predef it requires no import, but another possible location was the `compiletime` package. Requiring the extra import could discourage usage without consideration - however if the method remains in `Predef` the name itself (and documentation) should signal danger, like with `asInstanceOf`. + +3) Should the `RuntimeCheck` annotation be in the `scala.annotation.internal` package? + +### Misinterpretation of unchecked + +We would further like to highlight that the `unchecked` annotation is unspecified except for its imprecise API documentation. This leads to a crucial difference in its behavior between Scala 2.13 and the latest Scala 3.3.1 release. + +#### Scala 3 semantics + +Say you have the following: +```scala +val xs = List(1: Any) +``` + +The following expression in Scala 3.3.1 yields two warnings: +```scala +xs match { + case is: ::[Int] => is.head +} +``` + +```scala +2 warnings found +-- [E029] Pattern Match Exhaustivity Warning: ---------------------------------- +1 |xs match { + |^^ + |match may not be exhaustive. + | + |It would fail on pattern case: List(_, _*), Nil + | + | longer explanation available when compiling with `-explain` +val res0: Int = 1 +-- Unchecked Warning: ---------------------------------------------------------- +2 | case is: ::[Int] => is.head + | ^ + |the type test for ::[Int] cannot be checked at runtime because its type arguments can't be determined from List[Any] +``` + +using `@unchecked` on `xs` has the effect of silencing any warnings that depend on checking `xs`, so no warnings will be emitted for the following change: + +```scala +(xs: @unchecked) match { + case is: ::[Int] => is.head +} +``` + +#### Scala 2.13 semantics + +However, in Scala 2.13, this will only silence the `match may not be exhaustive` warning, and the user will still see the `type test for ::[Int] cannot be checked at runtime` warning: + +```scala +scala> (xs: @unchecked) match { + | case is: ::[Int] => is.head + | } ^ +On line 2: warning: non-variable type argument Int in type pattern scala.collection.immutable.::[Int] (the underlying of ::[Int]) is unchecked since it is eliminated by erasure +val res2: Int = 1 +``` + +#### Aligning to Scala 2.13 semantics with runtimeCheck + +with `xs.runtimeCheck` we should still produce an unchecked warning for `case is: ::[Int] =>` +```scala +scala> xs.runtimeChecked match { + | case is: ::[Int] => is.head + | } +1 warning found +-- Unchecked Warning: ---------------------------------------------------------- +2 | case is: ::[Int] => is.head + | ^ + |the type test for ::[Int] cannot be checked at runtime because its type arguments can't be determined from List[Any] +val res13: Int = 1 +``` +This is because `xs.runtimeChecked` means trust the user as long as the pattern can be checked at runtime. + +To fully avoid warnings, the `@unchecked` will be put on the type argument: +```scala +scala> xs.runtimeChecked match { + | case is: ::[Int @unchecked] => is.head + | } +val res14: Int = 1 +``` +This has a small extra migration cost because if the scrutinee changes from `(xs: @unchecked)` to `xs.runtimeCheck` now some individual cases might need to add `@unchecked` on type arguments to avoid creating new warnings - however this cost is offset by perhaps revealing unsafe patterns previously unaccounted for. + +Once again `@nowarn` can be used to fully restore any old behavior + +## Alternatives + +1) make `runtimeCheck` a method on `Any` that returns the receiver (not inline). The compiler would check for presence of a call to this method when deciding to perform static checking of pattern exhaustivity. This idea was criticised for being brittle with respect to refactoring, or automatic code transformations via macro. + +2) `runtimeCheck` should elaborate to code that matches the expected type, e.g. to heal `t: Any` to `Int` when the expected type is `Int`. The problem is that this is not useful for patterns that can not be runtime checked by type alone. Also, it implies a greater change to the spec, because now `runtimeCheck` would have to be specially treated. + +## Related work + +- [Pre SIP thread](https://contributors.scala-lang.org/t/pre-sip-replace-non-sensical-unchecked-annotations/6342) +- [Scala 3 Reference: Pattern Bindings](https://docs.scala-lang.org/scala3/reference/changed-features/pattern-bindings.html), +- None of OCaml, Rust, Swift, or Java offer explicit escape hatches for non-exhaustive pattern matches (Haskell does not even warn by default). Instead the user must add a default case, (making it exhaustive) or use the equivalent of `@nowarn` when they exist. + +## FAQ + +N/A so far. diff --git a/_sips/sips/2017-07-12-right-associative-by-name-operators.md b/_sips/sips/right-associative-by-name-operators.md similarity index 96% rename from _sips/sips/2017-07-12-right-associative-by-name-operators.md rename to _sips/sips/right-associative-by-name-operators.md index 6b9ae3d0a6..3d247af33d 100644 --- a/_sips/sips/2017-07-12-right-associative-by-name-operators.md +++ b/_sips/sips/right-associative-by-name-operators.md @@ -1,11 +1,14 @@ --- layout: sip -title: SIP-34 - Right-Associative By-Name Operators -vote-status: accepted +title: SIP-39 - Right-Associative By-Name Operators +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/right-associative-by-name-operators.html --- +> This proposal has been implemented in Scala 2.13.0 and Scala 3.0.0. + **By: Stefan Zeiger** ## History diff --git a/_sips/sips/2010-01-22-scala-2-8-arrays.md b/_sips/sips/scala-2-8-arrays.md similarity index 99% rename from _sips/sips/2010-01-22-scala-2-8-arrays.md rename to _sips/sips/scala-2-8-arrays.md index f733a6de60..7f5999d651 100644 --- a/_sips/sips/2010-01-22-scala-2-8-arrays.md +++ b/_sips/sips/scala-2-8-arrays.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-7 - Scala 2.8 Arrays -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/scala-2-8-arrays.html --- diff --git a/_sips/sips/scala-3-macro-annotations.md b/_sips/sips/scala-3-macro-annotations.md new file mode 100644 index 0000000000..497a56896e --- /dev/null +++ b/_sips/sips/scala-3-macro-annotations.md @@ -0,0 +1,7 @@ +--- +title: SIP-63 - Scala 3 Macro Annotations +status: under-review +pull-request-number: 80 +stage: design + +--- diff --git a/_sips/sips/scala-cli.md b/_sips/sips/scala-cli.md new file mode 100644 index 0000000000..de9aaece39 --- /dev/null +++ b/_sips/sips/scala-cli.md @@ -0,0 +1,681 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: completed +status: shipped +title: SIP-46 - Scala CLI as default Scala command +--- + +**By: Krzysztof Romanowski and Scala CLI team** + +## History + +| Date | Version | +|---------------|--------------------| +| July 15th 2022 | Initial Draft | + +## Summary + +We propose to replace current script that is installed as `scala` with Scala CLI - a batteries included tool to interact with Scala. Scala CLI brings all the features that the commands above provide and expand them with incremental compilation, dependency management, packaging and much more. + +Even though Scala CLI could replace `scaladoc` and `scalac` commands as well for now, we do not propose to replace them. + + +## Motivation + +The current default `scala` script is quite limited since it can only start repl or run pre-compile Scala code. + +The current script are lacking basic features such as support for resolving dependencies, incremental compilation or support for outputs other than JVM. This forces any user that wants to do anything more than just basic things to learn and use SBT, Mill or an other build tool and that adds to the complexity of learning Scala. + +We observe that the current state of tooling in Scala is limiting creativity, with quite a high cost to create e.g. an application or a script with some dependencies that target Node.js. Many Scala developers are not choosing Scala for their personal projects, scripts, or small applications and we believe that the complexity of setting up a build tool is one of the reasons. + +With this proposal our main goal is to turn Scala into a language with "batteries included" that will also respect the community-first aspect of our ecosystem. + +### Why decided to work on Scala CLI rather then improve existing tools like sbt or Mill? + +Firstly, Scala CLI is in no way an actual replacement for SBT or Mill - nor was it ever meant to be. We do not call it a build tool, even though it does share some similarities with build tools. It doesn't aim at supporting multi-module +projects, nor to be extended via a task system. The main advantages of SBT and Mill: multi-module support and plugin ecosystem in the use cases for Scala CLI and scala command can often be disadvantages as it affects performance: configuration needs to be compiled, plugins resolved etc. + +Mill and SBT uses turing complete configuration for build so the complexity of build scripts in theory is unlimited. Scala CLI is configuration-only and that limits the complexity what put a hard cap how complex Scala CLI builds can be. + +`scala` command should be first and foremost a command line tool. Requirements for a certain project structure or presence configuration files limit SBT and Mill usability certain use cases related to command line. + +One of the main requirements for the new `scala` commands was speed, flexibility and focus on command-line use cases. Initially, we were considering improving SBT or Mill as well as building Scala CLI on top one. We have quickly realized that getting Mill or SBT to reply within Milliseconds (for cases where no hard work like compilation is require) would be pretty much out of reach. Mill and SBT's codebases are too big to compile them to native image using GraalVM, not to mention problems with dynamic loading and reflection. Adding flexibility when in comes to input sources (e.g. support for Gists) and making the tool that can accept most of the configuration using simple command-line parameters would involve writhing a lot of glue code. That is why we decided to build the tool from scratch based on existing components like coursier, bloop or scalafmt. + +## Proposed solution + +We propose to gradually replace the current `scala`, `scalac` and `scaladoc` commands by single `scala` command that under the hood will be `scala-cli`. We could also add wrapper scripts for `scalac` and `scaladoc` that will mimic the functionality that will use `scala-cli` under the hood. + +The complete set of `scala-cli` features can be found in [its documentation](https://scala-cli.virtuslab.org/docs/overview). + +Scala CLI brings many features like testing, packaging, exporting to sbt / Mill or upcoming support for publishing micro-libraries. Initially, we propose to limit the set of features available in the `scala` command by default. Scala CLI is a relatively new project and we should battle-proof some of its features before we commit to support them as part of the official `scala` command. + +Scala CLI offers [multiple native ways to be installed](https://scala-cli.virtuslab.org/install#advanced-installation) so most users should find a suitable method. We propose that these packages to become the default `scala` package in most repositories, often replacing existing `scala` packages but the fact how new `scala` command would be installed is not intended to be a part of this SIP. + +### High-level overview + +Let us show a few examples where adopting Scala CLI as `scala` command would be a significant improvement over current scripts. For this, we have assumed a minimal set of features (described as MUST have and SHOULD have). Each additional Scala CLI feature included in the future, such as `package`, would add more and more use cases. + +**Using REPL with a 3rd-party dependency** + +Currently, to start a Scala REPL with a dependency on the class path, users need to resolve this dependency with all its transitive dependencies (coursier can help here) and pass those to the `scala` command using the `--cp` option. Alternatively, one can create an sbt project including a single dependency and use the `sbt console` task. Ammonite gives a better experience with its magic imports. + +With Scala CLI, starting a REPL with a given dependency is as simple as running: + +``` +scala-cli repl --dep com.lihaoyi::os-lib:0.7.8 +``` + +Compared to Ammonite, default Scala REPLs provided by Scala 2 and 3 - that Scala CLI uses by default - are somewhat limited. However, Scala CLI also offers to start Ammonite instead of the default Scala REPL, by passing `--ammonite` (or `--amm`) option to `scala-cli repl` but we do not propose to include `--ammonite` to the `scala` command not to commit to its maintenance. + +Additionally, `scala-cli repl` can also put code from given files / directories / snippets on the class path by just providing their locations as arguments. Running `scala-cli repl foo.scala baz` will compile code from `foo.scala` and the `baz` directory, and put their classes on the REPL class path (including their dependencies, scalac options etc. defined within those files). + +Compilation (and running scaladoc as well) benefit in a similar way from the ability to manage dependencies. + +** Providing reproductions of bugs ** + +Currently, when reporting a bug in the compiler (or any other Scala-related) repository, users need to provide dependencies, compiler options etc. in comments, create a repository containing a projet with a Mill / sbt configuration to reproduce. In general, testing the reproduction or working on further minimization is not straightforward. + +"Using directives", provided by Scala CLI give the ability to include the whole configuration in single file, for example: + +```scala +//> using platform "native" +//> using "com.lihaoyi::os-lib:0.7.8" +//> using options "-Xfatal-warnings" + +def foo = println("") +``` + +The snippet above when run with Scala CLI without any configuration provided will use Scala Native, resolve and include `os-lib` and provide `-Xfatal-warnings` to the compiler. Even things such as the runtime JVM version can be configured with using directives. + +Moreover, Scala CLI provides the ability to run GitHub gists (including multi-file ones), and more. + +** Watch mode ** + +When working on a piece of code, it is often useful to have it compiled/run every time the file is changed, and build tools offer a watch mode for that. This is how most people are using watch mode through a build tool. Scala CLI offers a watch mode for most of its commands (by using `--watch` / `-w` flags). + + +### Specification + + In order to be able to expand the functionality of Scala CLI and yet use the same core to power the `scala` command, we propose to include both `scala` and `scala-cli` commands in the installation package. Scala CLI already has a feature to limit accessible sub-commands based the binary name (all sub-commands in `scala-cli`, and a curated list in `scala`). On later date, more features from `scala-cli` could be included into `scala` command by additional SIPs or similar processes. + +These sub-commands MUST be included in the the specification of the new `scala` command: + + - compile: Compile Scala code + - doc: Generate Scaladoc documentation + - repl: Fire-up a Scala REPL + - run: Compile and run Scala code. + - shebang: Like `run`, but more handy from shebang scripts + +These sub-commands SHOULD be included in the the specification of the new `scala` command: + + - fmt: Format Scala code + - test: Compile and test Scala code + - version: Print `scala-cli` version + + +The subcommand that MAY be included in the specification of the new `scala` command. Those sub-commands are specific to implementation of Scala CLI and provide important, user-facing features like integration with IDE or cleaning up incremental compilation state: + + - about: Print details about this application + - bsp: Start BSP server + - clean: Clean the workspace + - doctor: Print details about this application + - help: Print help message + - install-completions: Installs completions into your shell + - install-home: Install `scala-cli` in a sub-directory of the home directory + - setup-ide: Generate a BSP file that you can import into your IDE + - uninstall: Uninstall scala-cli - only works when installed by the installation script + - uninstall-completions: Uninstalls completions from your shell + - update: Update scala-cli - only works when installed by the installation script + +Last section of this proposal is the list of options that each sub-command MUST HAVE and SHOULD HAVE for each sub-commands that MUST or SHOULD be included in the specification of the new `scala` command. The options that are specific to the implementation (MAY have) as well as options for implementation specific sub-commands (MAY have) are included in [full specification](https://romanowski.github.io/scala-cli/docs/reference/scala-command/runner-specification). + +Scala CLI can also be configured with ["using directives"](https://scala-cli.virtuslab.org/docs/guides/introduction/using-directives) - a comment-based configuration syntax that should be placed at the top of Scala files. This allows for self-containing examples within one file since most of the configuration can be provided either from the command line or via using directives (command line has precedence). This is a game changer for use cases like scripting, reproduction, or within the academic scope. + +We have described the motivation, syntax and implementation basis in the [dedicated pre-SIP](https://contributors.scala-lang.org/t/pre-sip-using-directives/5700). Currently, we recommend to write using directives as comments, so making them part of the language specification is not necessary at this stage. Moreover, the new `scala` command could ignore using directives in the initial version, however we strongly suggest to include comment-based using directives from the start. + +Last section of this proposal contains a sumamry of Using Directives syntax as well as list of directives that MUST and SHOULD be supported. + +### Compatibility + +Adopting Scala CLI as the new `scala` command, as is, will change some of the behavior of today's scripts. Some examples: + +- Scala CLI recognizes tests based on the extension used (`*.test.scala`) so running `scala compile a.scala a.test.scala` will only compile `a.scala` +- Scala CLI has its own versioning scheme, that is not related to the Scala compiler. Default version used may dynamically change when new Scala version is released. Similarly to Scala 3, we intend for Scala CLI to be backward compatible and this should help mitigate this risk. +- By default, Scala CLI manages its own dependencies (e.g. scalac, zinc, Bloop) and resolves them lazily. This means that the first run of Scala CLI resolves quite some dependencies. Moreover, Scala CLI periodically checks for updates, new defaults accessing online resources (but it is not required to work, so Scala CLI can work in offline environment once setup) +- Scala CLI can also be configured via using directives. Command line options have precedence over using directives, however using directives override defaults. Compiling a file starting with `//> using scala 2.13.8`, without providing a Scala version on the command line, will result in using `2.13.8` rather than the default Scala version. We consider this a feature. However, technically, this is a breaking change. + +### Other concerns + +Scala CLI brings [using directives](https://scala-cli.virtuslab.org/docs/guides/introduction/using-directives) and [conventions to mark the test files](https://scala-cli.virtuslab.org/docs/commands/test#test-sources). We suggest to accept both accepted as a part of this SIP but we are ready to open dedicated SIPs for both (we have opened a [pre-SIP for using directives](https://contributors.scala-lang.org/t/pre-sip-using-directives/5700/15)) + +Scala CLI is an ambitious project and may seem hard to maintain in the long-run. + + +### Open questions + +The release cadence: should the new `scala` command follow the current release cadence for Scala CLI (every 2 weeks) or stick to Scala one (every 6 weeks)? + +## Alternatives + +Scala CLI has many alternatives. The most obvious ones are sbt, Mill, or other build tools. However, these are more complicated than Scala CLI, and what is more important they are not designed as command-line first tools. Ammonite, is another alternative, however it covers only part of the Scala CLI features (REPL and scripting), and lacks many of the Scala CLI features (incremental compilation, Scala version selection, support for Scala.js and Scala Native, just to name a few). + +## Related work + +- [Scala CLI website](https://scala-cli.virtuslab.org/) and [road map](https://github.com/VirtusLab/scala-cli/discussions/1101) +- [Pre-SIP](https://contributors.scala-lang.org/t/pre-sip-scala-cli-as-new-scala-command/5628/22) +- [leiningen](https://leiningen.org/) - a similar tool from Closure, but more configuration-oriented + +## FAQ + +This section will probably initially be empty. As discussions on the proposal progress, it is likely that some questions will come repeatedly. They should be listed here, with appropriate answers. + +# Scala Runner Specification + +This section describes proposed Scala Runner specification and was generated from Scala CLI documentation. It contains `MUST` have and `SHOULD` have commands (each with complete list of MUST have and SHOULD have options) followed by a list of using directives. + +## Scalac options + +Scala Runner MUST support following options from Scala Compiler directly: + - `-encoding` + - `-release` + - `-color` + - `-nowarn` + - `-feature` + - `-deprecation` + + + Additionally, all options that start with: +- `-g` +- `-language` +- `-opt` +- `-P` +- `-target` +- `-V` +- `-W` +- `-X` +- `-Y` + +SHOULD be treated as be Scala compiler options and be propagated to Scala Compiler. This applies to all commands that uses compiler directly or indirectly. + +# MUST have commands + +## `compile` command + +Compile Scala code + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--watch`, `-w`: Watch source files for changes +- `--restart`, `--revolver`: Run your application in background and automatically restart if sources have been changed +- `--test`: Compile test scope +--- + +## `doc` command + +Generate Scaladoc documentation + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +- `--output`, `-o`: Set the destination path +- `--force`, `-f`: Overwrite the destination directory, if it exists +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--default-scaladoc-options`, `--default-scaladoc-opts`: Control if scala CLI should use default options for scaladoc, true by default. Use `--default-scaladoc-opts:false` to not include default options. +--- + +## `repl` command + +Aliases: `console` + +Fire-up a Scala REPL + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +- `--java-opt`, `-J`: Set Java options, such as `-Xmx1g` +- `--java-prop`: Set Java properties + +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--watch`, `-w`: Watch source files for changes +- `--restart`, `--revolver`: Run your application in background and automatically restart if sources have been changed + +--- + +## `run` command + +Compile and run Scala code. + +To pass arguments to the application, just add them after `--`, like: + +```sh +scala-cli MyApp.scala -- first-arg second-arg +``` + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +- `--java-opt`, `-J`: Set Java options, such as `-Xmx1g` +- `--java-prop`: Set Java properties +- `--main-class`, `-M`: Specify which main class to run + +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--watch`, `-w`: Watch source files for changes +- `--restart`, `--revolver`: Run your application in background and automatically restart if sources have been changed +- `--main-class-ls`, `--main-class-list`, `--list-main-class`, `--list-main-classes`: List main classes available in the current context +- `--command`: Print the command that would have been run (one argument per line), rather than running it + +--- + +## `shebang` command + +Like `run`, but more handy from shebang scripts + +This command is equivalent to `run`, but it changes the way +`scala-cli` parses its command-line arguments in order to be compatible +with shebang scripts. + +Normally, inputs and scala-cli options can be mixed. Program have to be specified after `--` + +```sh +scala-cli [command] [scala_cli_options | input]... -- [program_arguments]... +``` + +Contrary, for shebang command, only a single input file can be set, all scala-cli options +have to be set before the input file, and program arguments after the input file +```sh +scala-cli shebang [scala_cli_options]... input [program_arguments]... +``` + +Using this, it is possible to conveniently set up Unix shebang scripts. For example: +```sh +#!/usr/bin/env -S scala-cli shebang --scala-version 2.13 +println("Hello, world) +``` + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +- `--java-opt`, `-J`: Set Java options, such as `-Xmx1g` +- `--java-prop`: Set Java properties +- `--main-class`, `-M`: Specify which main class to run + +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--watch`, `-w`: Watch source files for changes +- `--restart`, `--revolver`: Run your application in background and automatically restart if sources have been changed +- `--main-class-ls`, `--main-class-list`, `--list-main-class`, `--list-main-classes`: List main classes available in the current context +- `--command`: Print the command that would have been run (one argument per line), rather than running it + +--- + +# SHOULD have commands + +## `fmt` command + +Aliases: `format`, `scalafmt` + +Format Scala code + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--check`: Check if sources are well formatted + +--- + +## `test` command + +Compile and test Scala code + +### MUST have options + +- `--dependency`, `--dep`: Add dependencies +- `--compiler-plugin`, `-P`, `--plugin`: Add compiler plugin dependencies +- `--scala-version`, `--scala`, `-S`: Set the Scala version +- `--scala-binary-version`, `--scala-binary`, `--scala-bin`, `-B`: Set the Scala binary version +- `--extra-jars`, `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`: Add extra JARs and compiled classes to the class path +- `--resource-dirs`, `--resource-dir`: Add a resource directory +- `--compilation-output`, `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out`: Copy compilation results to output directory using either relative or absolute path +- `--java-opt`, `-J`: Set Java options, such as `-Xmx1g` +- `--java-prop`: Set Java properties +### SHOULD have options + +- `--js`: Enable Scala.js. To show more options for Scala.js pass `--help-js` +- `--js-version`: The Scala.js version +- `--js-mode`: The Scala.js mode, either `dev` or `release` +- `--js-module-kind`: The Scala.js module kind: commonjs/common, esmodule/es, nomodule/none +- `--js-check-ir`: +- `--js-emit-source-maps`: Emit source maps +- `--js-source-maps-path`: Set the destination path of source maps +- `--js-dom`: Enable jsdom +- `--js-header`: A header that will be added at the top of generated .js files +- `--js-es-version`: The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021 +- `--native`: Enable Scala Native. To show more options for Scala Native pass `--help-native` +- `--native-version`: Set the Scala Native version +- `--native-mode`: Set Scala Native compilation mode +- `--native-gc`: Set the Scala Native garbage collector +- `--native-linking`: Extra options passed to `clang` verbatim during linking +- `--native-compile`: List of compile options +- `--embed-resources`: Embed resources into the Scala Native binary (can be read with the Java resources API) +- `--repository`, `--repo`, `-r`: Add repositories +- `--debug`: Turn debugging on +- `--debug-port`: Debug port (5005 by default) +- `--debug-mode`: Debug mode (attach by default) +- `--java-home`: Set the Java home directory +- `--jvm`, `-j`: Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` +- `--javac-plugin`: Javac plugin dependencies or files +- `--javac-option`, `--javac-opt`: Javac options +- `--script-snippet`: Allows to execute a passed string as a Scala script +- `--execute-script`, `--execute-scala-script`, `--execute-sc`, `-e`: A synonym to --script-snippet, which defaults the sub-command to `run` when no sub-command is passed explicitly +- `--scala-snippet`: Allows to execute a passed string as Scala code +- `--extra-compile-only-jars`, `--compile-only-jar`, `--compile-only-jars`, `--extra-compile-only-jar`: Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath. +- `--extra-source-jars`, `--source-jar`, `--source-jars`, `--extra-source-jar`: Add extra source JARs +- `--platform`: Specify platform +- `--semantic-db`: Generate SemanticDBs +- `--watch`, `-w`: Watch source files for changes +- `--restart`, `--revolver`: Run your application in background and automatically restart if sources have been changed +- `--test-framework`: Name of the test framework's runner class to use while running tests +- `--require-tests`: Fail if no test suites were run +--- + +# Using Directives + + +As a part of this SIP we propose to introduce Using Directives, a special comments containing configuration. Withing Scala CLI and by extension `scala` command, the command line arguments takes precedence over using directives. + +Using directives can be place on only top of the file (above imports, package definition etx.) and can be proceed only by plain comments (e.g. to comment out an using directive) + +Comments containing directives needs to start by `//>`, for example: + +``` +//> using scala 3 +//> using platform scala-js +//> using options -Xasync +``` + +We propose following sytax for Using Directives (within special comments described above): + +``` +UsingDirective ::= "using" Setting +Setting ::= Ident ( Value | Values ) +Ident ::= ScalaIdent { "." ScalaIdent } +Values ::= Value { " " [","] Values } +Value ::= Ident | stringLiteral | numericLiteral | true | false +``` + +Where: + +- Ident is the standard Scala identifier or list of identifiers separated by dots: + +``` +foo + +foo.bar + +`foo-bar`.bazz +``` + +- String literals and numeric literals are similar to Scala. +- No value after the identifier is treated as true value: `using scalaSettings.fatalWarnings` +- Specifying a setting with the same path more than once and specifying the same setting with a list of values are equivalent + +The list of proposed directives split into MUST have and SHOULD have groups: + +## MUST have directives: + + - `option`, `options`: Add Scala compiler options + - `plugin`, `plugins`: Adds compiler plugins + - `lib`, `libs`: Add dependencies + - `javaOpt`, `javaOptions`, `java-opt`, `java-options`: Add Java options which will be passed when running an application. + - `javaProp`: Add Java properties + - `main-class`, `mainClass`: Specify default main class + - `scala`: Set the default Scala version +## SHOULD have directives: + + - `jar`, `jars`: Manually add JAR(s) to the class path + - `file`, `files`: Manually add sources to the Scala CLI project + - `java-home`, `javaHome`: Sets Java home used to run your application or tests + - `javacOpt`, `javacOptions`, `javac-opt`, `javac-options`: Add Javac options which will be passed when compiling sources. + - `platform`, `platforms`: Set the default platform to Scala.js or Scala Native + - `repository`, `repositories`: Add a repository for dependency resolution + - `resourceDir`, `resourceDirs`: Manually add a resource directory to the class path + - `native-gc`, `native-mode`, `native-version`, `native-compile`, `native-linking`, `native-clang`, `native-clang-pp`, `native-no-embed`, `nativeGc`, `nativeMode`, `nativeVersion`, `nativeCompile`, `nativeLinking`, `nativeClang`, `nativeClangPP`, `nativeEmbedResources`: Add Scala Native options + - `jsVersion`, `jsMode`, `jsModuleKind`, `jsCheckIr`, `jsEmitSourceMaps`, `jsSmallModuleForPackage`, `jsDom`, `jsHeader`, `jsAllowBigIntsForLongs`, `jsAvoidClasses`, `jsAvoidLetsAndConsts`, `jsModuleSplitStyleStr`, `jsEsVersionStr`: Add Scala.js options + - `test-framework`, `testFramework`: Set the test framework diff --git a/_sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md b/_sips/sips/scala-compiler-phase-plugin-in.md similarity index 76% rename from _sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md rename to _sips/sips/scala-compiler-phase-plugin-in.md index 8d854921c3..24f0330e91 100644 --- a/_sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md +++ b/_sips/sips/scala-compiler-phase-plugin-in.md @@ -1,8 +1,8 @@ --- 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. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/scala-compiler-phase-plugin-in.html --- diff --git a/_sips/sips/2010-05-06-scala-specialization.md b/_sips/sips/scala-specialization.md similarity index 72% rename from _sips/sips/2010-05-06-scala-specialization.md rename to _sips/sips/scala-specialization.md index dc3afd5caf..d9bd06e36e 100644 --- a/_sips/sips/2010-05-06-scala-specialization.md +++ b/_sips/sips/scala-specialization.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-9 - Scala Specialization -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/scala-specialization.html --- diff --git a/_sips/sips/2009-11-02-scala-swing-overview.md b/_sips/sips/scala-swing-overview.md similarity index 72% rename from _sips/sips/2009-11-02-scala-swing-overview.md rename to _sips/sips/scala-swing-overview.md index 497ae502e6..a539dc352c 100644 --- a/_sips/sips/2009-11-02-scala-swing-overview.md +++ b/_sips/sips/scala-swing-overview.md @@ -1,8 +1,8 @@ --- layout: sip title: SID-8 - Scala Swing Overview -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/scala-swing-overview.html --- diff --git a/_sips/sips/sealed-types.md b/_sips/sips/sealed-types.md new file mode 100644 index 0000000000..04754c6be7 --- /dev/null +++ b/_sips/sips/sealed-types.md @@ -0,0 +1,6 @@ +--- +title: SIP-41 - Sealed Types +status: withdrawn +pull-request-number: 43 + +--- diff --git a/_sips/sips/self-cleaning-macros.md b/_sips/sips/self-cleaning-macros.md new file mode 100644 index 0000000000..056a674a38 --- /dev/null +++ b/_sips/sips/self-cleaning-macros.md @@ -0,0 +1,6 @@ +--- +title: SIP-16 - Self-cleaning Macros +status: rejected +pull-request-number: 15 + +--- diff --git a/_sips/sips/spores.md b/_sips/sips/spores.md new file mode 100644 index 0000000000..2b9c51d5a3 --- /dev/null +++ b/_sips/sips/spores.md @@ -0,0 +1,6 @@ +--- +title: SIP-21 - Spores +status: withdrawn +pull-request-number: 20 + +--- diff --git a/_sips/sips/2016-01-11-static-members.md b/_sips/sips/static-members.md similarity index 98% rename from _sips/sips/2016-01-11-static-members.md rename to _sips/sips/static-members.md index f36cc89d7d..1c0df79d75 100644 --- a/_sips/sips/2016-01-11-static-members.md +++ b/_sips/sips/static-members.md @@ -1,13 +1,15 @@ --- layout: sip title: SIP-30 - @static fields and methods in Scala objects (SI-4581) -vote-status: "under-review" -vote-text: Authors need to update the proposal before the next review. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/static-members.html --- -__Dmitry Petrashko, Sébastien Doeraene and Martin Odersky__ +> This proposal has been implemented in Scala 3.0.0 + +**Authors: Dmitry Petrashko, Sébastien Doeraene and Martin Odersky** __first submitted 11 January 2016__ diff --git a/_sips/sips/2011-10-13-string-interpolation.md b/_sips/sips/string-interpolation.md similarity index 69% rename from _sips/sips/2011-10-13-string-interpolation.md rename to _sips/sips/string-interpolation.md index 69b807a6da..f1343fdb22 100644 --- a/_sips/sips/2011-10-13-string-interpolation.md +++ b/_sips/sips/string-interpolation.md @@ -1,9 +1,9 @@ --- layout: sip title: SIP-11 - String Interpolation - +stage: completed +status: shipped 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 redirect_from: /sips/pending/string-interpolation.html --- diff --git a/_sips/sips/struct-classes.md b/_sips/sips/struct-classes.md new file mode 100644 index 0000000000..a9109fd45b --- /dev/null +++ b/_sips/sips/struct-classes.md @@ -0,0 +1,6 @@ +--- +title: SIP-50 - Struct Classes +status: withdrawn +pull-request-number: 50 + +--- diff --git a/_sips/sips/2016-06-25-trailing-commas.md b/_sips/sips/trailing-commas.md similarity index 97% rename from _sips/sips/2016-06-25-trailing-commas.md rename to _sips/sips/trailing-commas.md index 1116621487..7eef93dd03 100644 --- a/_sips/sips/2016-06-25-trailing-commas.md +++ b/_sips/sips/trailing-commas.md @@ -1,12 +1,14 @@ --- -layout: inner-page-no-masthead +layout: sip title: SIP-27 - Trailing Commas -vote-status: complete -vote-text: This SIP has already been accepted and completed, and is a part of Scala 2.12.2. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/trailing-commas.html --- +> This proposal has been shipped in Scala 2.12.2. + **By: Dale Wijnand** ## History diff --git a/_sips/sips/2015-6-18-trait-parameters.md b/_sips/sips/trait-parameters.md similarity index 94% rename from _sips/sips/2015-6-18-trait-parameters.md rename to _sips/sips/trait-parameters.md index 6abef5c4b8..633f0c0b73 100644 --- a/_sips/sips/2015-6-18-trait-parameters.md +++ b/_sips/sips/trait-parameters.md @@ -1,12 +1,14 @@ --- layout: sip title: SIP-25 - Trait Parameters -vote-status: accepted -vote-text: Trait parameters are implemented in Scala 3.0. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/trait-parameters.html --- +> This proposal has been implemented in Scala 3.0. + __Martin Odersky__ __first submitted 18 June 2015__ @@ -67,4 +69,4 @@ the evaluation order would be `e1`, initializer of `T`, `e2`, initializer of `V` ## See Also ## -[Dotty Issue #640](https://github.com/lampepfl/dotty/issues/640) +[Dotty Issue #640](https://github.com/scala/scala3/issues/640) diff --git a/_sips/sips/2012-03-13-type-dynamic.md b/_sips/sips/type-dynamic.md similarity index 85% rename from _sips/sips/2012-03-13-type-dynamic.md rename to _sips/sips/type-dynamic.md index 37385c3c02..c359ec0627 100644 --- a/_sips/sips/2012-03-13-type-dynamic.md +++ b/_sips/sips/type-dynamic.md @@ -1,9 +1,8 @@ --- layout: sip title: SIP-17 - Type Dynamic - -vote-status: complete -vote-text: This SIP has already been accepted and completed. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/type-dynamic.html --- diff --git a/_sips/sips/typeclasses-syntax.md b/_sips/sips/typeclasses-syntax.md new file mode 100644 index 0000000000..38dcba9293 --- /dev/null +++ b/_sips/sips/typeclasses-syntax.md @@ -0,0 +1,685 @@ +--- +layout: sip +stage: completed +status: shipped +presip-thread: https://contributors.scala-lang.org/t/pre-sip-improve-syntax-for-context-bounds-and-givens/6576/97 +title: SIP-64 - Improve Syntax for Context Bounds and Givens +--- + +**By: Martin Odersky** + +## History + +| Date | Version | +|---------------|--------------------| +| March 11, 2024| Initial Draft | +| July 18, 2024 | Revised Draft | + +## Summary + +We propose some syntactic improvements that make context bounds and given clauses more +expressive and easier to read. The proposed additions and changes comprise: + + - naming context bounds, as in `A: Monoid as a`, + - a new syntax for multiple context bounds, as in `A: {Monoid, Ord}`, + - context bounds for type members, + - replacing abstract givens with a more powerful and convenient mechanism, + - a cleaner syntax for given definitions that eliminates some syntactic warts. + +## Motivation + +This SIP is part of an effort to get state-of-the art typeclasses and generic in Scala. It fixes several existing pain points: + + - The inability to name context bounds causes awkward and obscure workarounds in practice. + - The syntax for multiple context bounds is not very clear or readable. + - The existing syntax for givens is unfortunate, which hinders learning and adoption. + - Abstract givens are hard to specify and implement and their syntax is easily confused + with simple concrete givens. + +These pain points are worth fixing on their own, independently of any other proposed improvements to typeclass support. What's more, the changes +are time sensitive since they affect existing syntax that was introduced in 3.0, so it's better to make the change at a time when not that much code using the new syntax is written yet. + +## Proposed Solution + +### 1. Naming Context Bounds + +Context bounds are a convenient and legible abbreviation. A problem so far is that they are always anonymous, one cannot name the implicit parameter to which a context bound expands. For instance, consider the classical pair of type classes +```scala + trait SemiGroup[A]: + extension (x: A) def combine(y: A): A + + trait Monoid[A] extends SemiGroup[A]: + def unit: A +``` +and a `reduce` method defined like this: +```scala +def reduce[A : Monoid](xs: List[A]): A = ??? +``` +Since we don't have a name for the `Monoid` instance of `A`, we need to resort to `summon` in the body of `reduce`: +```scala +def reduce[A : Monoid](xs: List[A]): A = + xs.foldLeft(summon[Monoid[A]].unit)(_ `combine` _) +``` +That's generally considered too painful to write and read, hence people usually adopt one of two alternatives. Either, eschew context bounds and switch to using clauses: +```scala +def reduce[A](xs: List[A])(using m: Monoid[A]): A = + xs.foldLeft(m.unit)(_ `combine` _) +``` +Or, plan ahead and define a "trampoline" method in `Monoid`'s companion object: +```scala + trait Monoid[A] extends SemiGroup[A]: + def unit: A + object Monoid: + def unit[A](using m: Monoid[A]): A = m.unit + ... + def reduce[A : Monoid](xs: List[A]): A = + xs.foldLeft(Monoid.unit)(_ `combine` _) +``` +This is all accidental complexity which can be avoided by the following proposal. + +**Proposal:** Allow to name a context bound, like this: +```scala + def reduce[A : Monoid as m](xs: List[A]): A = + xs.foldLeft(m.unit)(_ `combine` _) +``` + +We use `as x` after the type to bind the instance to `x`. This is analogous to import renaming, which also introduces a new name for something that comes before. + +**Benefits:** The new syntax is simple and clear. It avoids the awkward choice between concise context bounds that can't be named and verbose using clauses that can. + +### 2. New Syntax for Aggregate Context Bounds + +Aggregate context bounds like `A : X : Y` are not obvious to read, and it becomes worse when we add names, e.g. `A : X as x : Y as y`. + +**Proposal:** Allow to combine several context bounds inside `{...}`, analogous +to import clauses. Example: + +```scala + trait A: + def showMax[X : {Ordering, Show}](x: X, y: X): String + class B extends A: + def showMax[X : {Ordering as ordering, Show as show}](x: X, y: X): String = + show.asString(ordering.max(x, y)) +``` + +The old syntax with multiple `:` should be phased out over time. There's more about migration at the end of this SIP. + + +### 3. Expansion of Context Bounds + +With named context bounds, we need a revision to how the witness parameters of such bounds are added. Context bounds are currently translated to implicit parameters in the last parameter list of a method or class. This is a problem if a context bound is mentioned in one of the preceding parameter types. For example, consider a type class of parsers with associated type members `Input` and `Result` describing the input type on which the parsers operate and the type of results they produce: +```scala +trait Parser[P]: + type Input + type Result +``` +Here is a method `run` that runs a parser on an input of the required type: +```scala +def run[P : Parser as p](in: p.Input): p.Result +``` +With the current translation this does not work since it would be expanded to: +```scala + def run[P](x: p.Input)(using p: Parser[P]): p.Result +``` +Note that the `p` in `p.Input` refers to the `p` introduced in the using clause, which comes later. So this is ill-formed. + +This problem would be fixed by changing the translation of context bounds so that they expand to using clauses immediately after the type parameter. But such a change is infeasible, for two reasons: + + 1. It would be a source- and binary-incompatible change. We cannot simply change the expansion of existing using clauses because + then clients that pass explicit using arguments would no longer work. + 2. Putting using clauses earlier can impair type inference. A type in + a using clause can be constrained by term arguments coming before that + clause. Moving the using clause first would miss those constraints, which could cause ambiguities in implicit search. + +But there is an alternative which is feasible: + +**Proposal:** Map the context bounds of a method or class as follows: + + 1. If one of the bounds is referred to by its term name in a subsequent parameter clause, the context bounds are mapped to a using clause immediately preceding the first such parameter clause. + 2. Otherwise, if the last parameter clause is a using (or implicit) clause, merge all parameters arising from context bounds in front of that clause, creating a single using clause. + 3. Otherwise, let the parameters arising from context bounds form a new using clause at the end. + +Rules (2) and (3) are the status quo, and match Scala 2's rules. Rule (1) is new but since context bounds so far could not be referred to, it does not apply to legacy code. Therefore, binary compatibility is maintained. + +**Discussion** More refined rules could be envisaged where context bounds are spread over different using clauses so that each comes as late as possible. But it would make matters more complicated and the gain in expressiveness is not clear to me. + + +### 4. Context Bounds for Type Members, Deferred Givens + +It's not very orthogonal to allow subtype bounds for both type parameters and abstract type members, but context bounds only for type parameters. What's more, we don't even have the fallback of an explicit using clause for type members. The only alternative is to also introduce a set of abstract givens that get implemented in each subclass. This is extremely heavyweight and opaque to newcomers. + +**Proposal**: Allow context bounds for type members. Example: + +```scala + class Collection: + type Element : Ord +``` + +The question is how these bounds are expanded. Context bounds on type parameters +are expanded into using clauses. But for type members this does not work, since we cannot refer to a member type of a class in a parameter type of that class. What we are after is an equivalent of using parameter clauses but represented as class members. + +**Proposal:** +Introduce a new way to implement a given definition in a trait like this: +```scala +given T = deferred +``` +`deferred` is a new method in the `scala.compiletime` package, which can appear only as the right hand side of a given defined in a trait. Any class implementing that trait will provide an implementation of this given. If a definition is not provided explicitly, it will be synthesized by searching for a given of type `T` in the scope of the inheriting class. Specifically, the scope in which this given will be searched is the environment of that class augmented by its parameters but not containing its members (since that would lead to recursive resolutions). If an implementation _is_ provided explicitly, it counts as an override of a concrete definition and needs an `override` modifier. + +Deferred givens allow a clean implementation of context bounds in traits, +as in the following example: +```scala +trait Sorted: + type Element : Ord + +class SortedSet[A : Ord] extends Sorted: + type Element = A +``` +The compiler expands this to the following implementation. +```scala +trait Sorted: + type Element + given Ord[Element] = compiletime.deferred + +class SortedSet[A](using evidence$0: Ord[A]) extends Sorted: + type Element = A + override given Ord[Element] = evidence$0 +``` + +The using clause in class `SortedSet` provides an implementation for the deferred given in trait `Sorted`. + +**Benefits:** + + - Better orthogonality, type parameters and abstract type members now accept the same kinds of bounds. + - Better ergonomics, since deferred givens get naturally implemented in inheriting classes, no need for boilerplate to fill in definitions of abstract givens. + +**Alternative:** It was suggested that we use a modifier for a deferred given instead of a `= deferred`. Something like `deferred given C[T]`. But a modifier does not suggest the concept that a deferred given will be implemented automatically in subclasses unless an explicit definition is written. In a sense, we can see `= deferred` as the invocation of a magic macro that is provided by the compiler. So from a user's point of view a given with `deferred` right hand side is not abstract. +It is a concrete definition where the compiler will provide the correct implementation. And if users want to provide their own overriding +implementations, they will need an explicit `override` modifier. + +### 5. Abolish Abstract Givens + +With `deferred` givens there is no need anymore to also define abstract givens. The two mechanisms are very similar, but the user experience for +deferred givens is generally more ergonomic. Abstract givens also are uncomfortably close to concrete class instances. Their syntax clashes +with the quite common case where we want to establish a given without any nested definitions. For instance, consider a given that constructs a type tag: +```scala +class Tag[T] +``` +Then this works: +```scala +given Tag[String]() +given Tag[String] with {} +``` +But the following more natural syntax fails: +```scala +given Tag[String] +``` +The last line gives a rather cryptic error: +``` +1 |given Tag[String] + | ^ + | anonymous given cannot be abstract +``` +The underlying problem is that abstract givens are very rare (and should become completely unnecessary once deferred givens are introduced), yet occupy a syntax that looks very close to the more common case of concrete +typeclasses without nested definitions. + +**Proposal:** In the future, let the `= deferred` mechanism be the only way to deliver the functionality of abstract givens. Deprecate the current version of abstract givens, and remove them in a future Scala version. + +**Benefits:** + + - Simplification of the language since a feature is dropped + - Eliminate non-obvious and misleading syntax. + +The only downside is that deferred givens are restricted to be used in traits, whereas abstract givens are also allowed in abstract classes. But I would be surprised if actual code relied on that difference, and such code could in any case be easily rewritten to accommodate the restriction. + + +### 6. Context Bounds for Polymorphic Functions + +Currently, context bounds can be used in methods, but not in function types or function literals. It would be nice propose to drop this irregularity and allow context bounds also in these places. Example: + +```scala +type Comparer = [X: Ord] => (x: X, y: X) => Boolean +val less: Comparer = [X: Ord as ord] => (x: X, y: X) => + ord.compare(x, y) < 0 +``` + +The expansion of such context bounds is analogous to the expansion in method types, except that instead of adding a using clause in a method, we insert a context function type. + +For instance, the `type` and `val` definitions above would expand to +```scala +type Comparer = [X] => (x: X, y: X) => Ord[X] ?=> Boolean +val less: Comparer = [X] => (x: X, y: X) => (ord: Ord[X]) ?=> + ord.compare(x, y) < 0 +``` + +The expansion of using clauses does look inside alias types. For instance, +here is a variation of the previous example that uses a parameterized type alias: +```scala +type Cmp[X] = (x: X, y: X) => Ord[X] ?=> Boolean +type Comparer2 = [X: Ord] => Cmp[X] +``` +The expansion of the right hand side of `Comparer2` expands the `Cmp[X]` alias +and then inserts the context function at the same place as what's done for `Comparer`. + +### 7. Cleanup of Given Syntax + +A good language syntax is like a Bach fugue: A small set of motifs is combined in a multitude of harmonic ways. Dissonances and irregularities should be avoided. + +When designing Scala 3, I believe that, by and large, we achieved that goal, except in one area, which is the syntax of givens. There _are_ some glaring dissonances, as seen in this code for defining an ordering on lists: +```scala +given [A](using Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... +``` +The `:` feels utterly foreign in this position. It's definitely not a type ascription, so what is its role? Just as bad is the trailing `with`. Everywhere else we use braces or trailing `:` to start a scope of nested definitions, so the need of `with` sticks out like a sore thumb. + +Sometimes unconventional syntax grows on you and becomes natural after a while. But here it was unfortunately the opposite. The longer I used given definitions in this style the more awkward they felt, in particular since the rest of the language seemed so much better put together by comparison. And I believe many others agree with me on this. Since the current syntax is unnatural and esoteric, this means it's difficult to discover and very foreign even after that. This makes it much harder to learn and apply givens than it need be. + +The previous conditional given syntax was inspired by method definitions. If we add the optional name to the previous example, we obtain something akin to an implicit method in Scala 2: +```scala +given listOrd[A](using Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... +``` +The anonymous syntax was then obtained by simply dropping the name. +But without a name, the syntax looks weird and inconsistent. + +This is a problem since at least for typeclasses, anonymous givens should be the norm. +Givens are like extends clauses. We state a _fact_, that a +type implements a type class, or that a value can be used implicitly. We don't need a name for that fact. It's analogous to extends clauses, where we state that a class is a subclass of some other class or trait. We would not think it useful to name an extends clause, it's simply a fact that is stated. +It's also telling that every other language that defines type classes uses anonymous syntax. Somehow, nobody ever found it necessary to name these instances. + +A more intuitive and in my opinion cleaner alternative is to decree that a given should always look like it _implements a type_. Conditional givens should look like they implement function types. The `Ord` typeclass instances for `Int` and `List` would then look like this: +```scala +given Ord[String]: + def compare(x: String, y: String) = ... + +given [A : Ord] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... +``` +The second, conditional instance looks like it implements the function type +```scala +[A : Ord] => Ord[List[A]] +``` +Another way to see this is as an implication: +If `A` is a type that is `Ord`, then `List[A]` is `Ord` (and the rest of the given clause gives the implementation that makes it so). +Equivalently, `A` is `Ord` _implies_ `List[A]` is `Ord`, hence the `=>`. + +Yet another related meaning is that the given clause establishes a _context function_ of type `[A: Ord] ?=> Ord[List[A]]` that is automatically applied to evidence arguments of type `Ord[A]` and that yields instances of type `Ord[List[A]]`. Since givens are in any case applied automatically to all their arguments, we don't need to specify that separately with `?=>`, a simple `=>` arrow is sufficiently clear and is easier to read. + +All these viewpoints are equivalent, in a deep sense. This is exactly the Curry Howard isomorphism, which equates function types and implications. + +**Proposal:** Change the syntax for given clauses so that a `given` clause consists of the following elements: + + - An optional name binding `id :` + - Zero or more _conditions_, which introduce type or value parameters. Each precondition ends in a `=>`. + - the implemented _type_, + - an implementation which consists of either an `=` and an expression, + or a template body. + +**Examples:** + +Here is an enumeration of common forms of given definitions in the new syntax. We show the following use cases: + + 1. A simple typeclass instance, such as `Ord[Int]`. + 2. A parameterized type class instance, such as `Ord` for lists. + 3. A type class instance with an explicit context parameter. + 4. A type class instance with a named eexplicit context parameter. + 4. A simple given alias. + 5. A parameterized given alias + 6. A given alias with an explicit context parameter. + 8. An abstract or deferred given + 9. A by-name given, e.g. if we have a given alias of a mutable variable, and we + want to make sure that it gets re-evaluated on each access. +```scala + // Simple typeclass + given Ord[Int]: + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass with context bound + given [A: Ord] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with context parameter + given [A] => Ord[A] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with named context parameter + given [A] => (ord: Ord[A]) => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given Ord[Int] = IntOrd() + + // Parameterized alias with context bound + given [A: Ord] => Ord[List[A]] = + ListOrd[A] + + // Parameterized alias with context parameter + given [A] => Ord[A] => Ord[List[A]] = + ListOrd[A] + + // Abstract or deferred given + given Context = deferred + + // By-name given + given () => Context = curCtx +``` +Here are the same examples, with optional names provided: +```scala + // Simple typeclass + given intOrd: Ord[Int]: + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass with context bound + given listOrd: [A: Ord] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with context parameter + given listOrd: [A] => Ord[A] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with named context parameter + given listOrd: [A] => (ord: Ord[A]) => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given intOrd: Ord[Int] = IntOrd() + + // Parameterized alias with context bound + given listOrd: [A: Ord] => Ord[List[A]] = + ListOrd[A] + + // Parameterized alias with context parameter + given listOrd: [A] => Ord[A] => Ord[List[A]] = + ListOrd[A] + + // Abstract or deferred given + given context: Context = deferred + + // By-name given + given context: () => Context = curCtx +``` + +**By Name Givens** + +We sometimes find it necessary that a given alias is re-evaluated each time it is called. For instance, say we have a mutable variable `curCtx` and we want to define a given that returns the current value of that variable. A normal given alias will not do since by default given aliases are mapped to +lazy vals. + +In general, we want to avoid re-evaluation of the given. But there are situations like the one above where we want to specify _by-name_ evaluation instead. The proposed new syntax for this is shown in the last clause above. This is arguably the a natural way to express by-name givens. We want to use a conditional given, since these map to methods, but the set of preconditions is empty, hence the `()` parameter. Equivalently, under the context function viewpoint, we are defining a context function of the form `() ?=> T`, and these are equivalent to by-name parameters. + +Compare with the current best way to do achieve this, which is to use a dummy type parameter. +```scala + given [DummySoThatItsByName]: Context = curCtx +``` +This has the same effect, but feels more like a hack than a clean solution. + +**Dropping `with`** + +In the new syntax, all typeclass instances introduce definitions like normal +class bodies, enclosed in braces `{...}` or following a `:`. The irregular +requirement to use `with` is dropped. In retrospect, the main reason to introduce `with` was since a definition like + +```scala +given [A](using Ord[A]): Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... +``` +was deemed to be too cryptic, with the double meaning of colons. But since that syntax is gone, we don't need `with` anymore. There's still a double meaning of colons, e.g. in +```scala +given intOrd: Ord[Int]: + ... +``` +but since now both uses of `:` are very familiar (type ascription _vs_ start of nested definitions), it's manageable. Besides, the problem occurs only for named typeclass instances, which should be the exceptional case anyway. + + +**Possible ambiguities** + +If one wants to define a given for an a actual function type (which is probably not advisable in practice), one needs to enclose the function type in parentheses, i.e. `given ([A] => F[A])`. This is true in the currently implemented syntax and stays true for all discussed change proposals. + +The double meaning of : with optional prefix names is resolved as usual. A : at the end of a line starts a nested definition block. If for some obscure reason one wants to define a named given on multiple lines, one has to format it as follows: +```scala + given intOrd + : Ord = ... +``` +**Comparison with Status Quo** + +To facilitate a systematic comparison, here is the listing of all 9x2 cases discussed previously with the current syntax. + +Unnamed: +```scala + // Simple typeclass + given Ord[Int] with + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass with context bound + given [A: Ord]: Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with context parameter + given [A](using Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with named context parameter + given [A](using ord: Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given Ord[Int] = IntOrd() + + // Parameterized alias with context bound + given [A: Ord]: Ord[List[A]] = + ListOrd[A] + + // Parameterized alias with context parameter + given [A](using Ord[A]): Ord[List[A]] = + ListOrd[A] + + // Abstract or deferred given: no unnamed form possible + + // By-name given + given [DummySoItsByName]: Context = curCtx +``` +Named: +```scala + // Simple typeclass + given intOrd: Ord[Int] with + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass with context bound + given listOrd[A: Ord]: Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with context parameter + given listOrd[A](using Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Parameterized typeclass with named context parameter + given listOrd[A](using ord: Ord[A]): Ord[List[A]] with + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given intOrd: Ord[Int] = IntOrd() + + // Parameterized alias with context bound + given listOrd[A: Ord]: Ord[List[A]] = + ListOrd[A] + + // Parameterized alias with context parameter + given listOrd[A](using Ord[A]): Ord[List[A]] = + ListOrd[A] + + // Abstract or deferred given + given context: Context + + // By-name given + given context[DummySoItsByName]: Context = curCtx +``` + +**Summary** + +This will be a fairly significant change to the given syntax. I believe there's still a possibility to do this. Not so much code has migrated to new style givens yet, and code that was written can be changed fairly easily. Specifically, there are about a 900K definitions of `implicit def`s +in Scala code on Github and about 10K definitions of `given ... with`. So about 1% of all code uses the Scala 3 syntax, which would have to be changed again. + +Changing something introduced just recently in Scala 3 is not fun, +but I believe these adjustments are preferable to let bad syntax +sit there and fester. The cost of changing should be amortized by improved developer experience over time, and better syntax would also help in migrating Scala 2 style implicits to Scala 3. But we should do it quickly before a lot more code +starts migrating. + +Migration to the new syntax is straightforward, and can be supported by automatic rewrites. For a transition period we can support both the old and the new syntax. It would be a good idea to backport the new given syntax to the LTS version of Scala so that code written in this version can already use it. The current LTS would then support old and new-style givens indefinitely, whereas new Scala 3.x versions would phase out the old syntax over time. + + +## Summary of Syntax Changes + +Here is the complete context-free syntax for all proposed features. +``` +TmplDef ::= 'given' GivenDef +GivenDef ::= [id ':'] GivenSig +GivenSig ::= GivenImpl + | '(' ')' '=>' GivenImpl + | GivenConditional '=>' GivenSig +GivenImpl ::= GivenType ([‘=’ Expr] | TemplateBody) + | ConstrApps TemplateBody +GivenConditional ::= DefTypeParamClause + | DefTermParamClause + | '(' FunArgTypes ')' + | GivenType +GivenType ::= AnnotType1 {id [nl] AnnotType1} + +TypeDef ::= id [TypeParamClause] TypeAndCtxBounds +TypeParamBounds ::= TypeAndCtxBounds +TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds] +ContextBounds ::= ContextBound | '{' ContextBound {',' ContextBound} '}' +ContextBound ::= Type ['as' id] + +FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type + | DefTypeParamClause '=>' Type +FunExpr ::= FunParams (‘=>’ | ‘?=>’) Expr + | DefTypeParamClause ‘=>’ Expr +``` + +## Compatibility + +All additions are fully compatible with existing Scala 3. The prototype implementation contains a parser that accepts both old and new idioms. That said, we would +want to deprecate and remove over time the following existing syntax: + + 1. Multiple context bounds of the form `X : A : B : C`. + 2. The previous syntax for given clauses which required a `:` in front of the implemented type and a `with` after it. + 3. Abstract givens + +The changes under (1) and (2) can be automated using existing rewrite technology in the compiler or Scalafix. The changes in (3) are more global in nature but are still straightforward. + +## Alternatives + +One alternative put forward in the Pre-SIP was to deprecate context bounds altogether and only promote using clauses. This would still be a workable system and arguably lead to a smaller language. On the other hand, dropping context bounds for using clauses worsens +some of the ergonomics of expressing type classes. First, it is longer. Second, it separates the introduction of a type name and the constraints on that type name. Typically, there can be many normal parameters between a type parameter and the using clause that characterized it. By contrast, context bounds follow the +general principle that an entity should be declared together with its type, and in a very concrete sense context bounds define types of types. So I think context bounds are here to stay, and improvements to the ergonomics of context bounds will be appreciated. + +The Pre-SIP also contained a proposal for a default naming convention of context bounds. If no explicit `as` clause is given, the name of the witness for +`X : C` would be `X`, instead of a synthesized name as is the case now. This led to extensive discussions how to accommodate multiple context bounds. +I believe that a default naming convention for witnesses will be very beneficial in the long run, but as of today there are several possible candidate solutions, including: + + 1. Use default naming for single bounds only. + 2. If there are multiple bounds, as in `X: {A, B, C}` create a synthetic companion object `X` where selections `X.m` translate into + witness selections `A.m`, `B.m`, or `C.m`. Disallow any references to the companion that remain after that expansion. + 3. Like (2), but use the synthetic companion approach also for single bounds. + 4. Create real aggregate given objects that represent multiple bounds. + +Since it is at present not clear what the best solution would be, I decided to defer the question of default names to a later SIP. + +This SIP proposed originally a different syntax for givens that made use +of postfix `as name` for optional names and still followed method syntax in some elements. The 9x2 variants of the original proposal are as follows. + +```scala + // Simple typeclass + given Ord[Int]: + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass + given [A: Ord] => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Typeclass with context parameter + given [A](using Ord[A]) => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Typeclass with named context parameter + given [A](using ord: Ord[A]) => Ord[List[A]]: + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given Ord[Int] = IntOrd() + + // Parameterized alias + given [A: Ord] => Ord[List[A]] = + ListOrd[A] + + // Alias with explicit context parameter + given [A](using Ord[A]) => Ord[List[A]] = + ListOrd[A] + + // Abstract or deferred given + given Context = deferred + + // By-name given + given => Context = curCtx +``` +Named: + +```scala + // Simple typeclass + given Ord[Int] as intOrd: + def compare(x: Int, y: Int) = ... + + // Parameterized typeclass + given [A: Ord] => Ord[List[A]] as listOrd: + def compare(x: List[A], y: List[A]) = ... + + // Typeclass with context parameter + given [A](using Ord[A]) => Ord[List[A]] as listOrd: + def compare(x: List[A], y: List[A]) = ... + + // Typeclass with named context parameter + given [A](using ord: Ord[A]) => Ord[List[A]] as listOrd: + def compare(x: List[A], y: List[A]) = ... + + // Simple alias + given Ord[Int] as intOrd = IntOrd() + + // Parameterized alias + given [A: Ord] => Ord[List[A]] as listOrd = + ListOrd[A] + + // Alias with using clause + given [A](using Ord[A]) => Ord[List[A]] as listOrd = + ListOrd[A] + + // Abstract or deferred given + given Context as context = deferred + + // By-name given + given => Context as context = curCtx +``` + +The discussion on contributors raised some concerns with that original proposal. One concern was that changing to postfix `as` for optional names +would be too much of a change, in particular for simple given aliases. Another concern was that the `=>` felt unfamiliar in this place since it resembled a function type yet other syntactic elements followed method syntax. The revised proposal given here addresses these points by +going back to the usual `name:` syntax for optional names and doubling down +on function syntax to reinforce the intuition that givens implement types. + + +## Summary + +The proposed set of changes removes awkward syntax and makes dealing with context bounds and givens a lot more regular and pleasant. In summary, the main proposed changes are: + + 1. Allow to name context bounds with `as` clauses. + 2. Introduce a less cryptic syntax for multiple context bounds. + 3. Refine the rules how context bounds are expanded to account for explicit names. + 4. Allow context bounds on type members which expand to deferred givens. + 5. Drop abstract givens since they are largely redundant with deferred givens. + 6. Allow context bounds for polymorphic functions. + 7. Introduce a more regular and clearer syntax for givens. + +These changes were implemented under the experimental language import +```scala +import language.experimental.modularity +``` +which also covers some other prospective changes slated to be proposed future SIPs. The new system has proven to work well and to address several fundamental issues people were having with +existing implementation techniques for type classes. + +The changes proposed in this SIP are time-sensitive since we would like to correct some awkward syntax choices in Scala 3 before more code migrates to the new constructs (so far, it seems most code still uses Scala 2 style implicits, which will eventually be phased out). It is easy to migrate to the new syntax and to support both old and new for a transition period. diff --git a/_sips/sips/uncluttering-abuse-of-match.md b/_sips/sips/uncluttering-abuse-of-match.md new file mode 100644 index 0000000000..49f004f812 --- /dev/null +++ b/_sips/sips/uncluttering-abuse-of-match.md @@ -0,0 +1,6 @@ +--- +title: SIP-39 - Uncluttering Abuse of Match +status: rejected +pull-request-number: 37 + +--- diff --git a/_sips/sips/uncluttering-scalas-syntax-for-control-structures.md b/_sips/sips/uncluttering-scalas-syntax-for-control-structures.md new file mode 100644 index 0000000000..a4c9d95a95 --- /dev/null +++ b/_sips/sips/uncluttering-scalas-syntax-for-control-structures.md @@ -0,0 +1,6 @@ +--- +title: SIP-12 - Uncluttering Scala’s syntax for control structures. +status: rejected +pull-request-number: 12 + +--- diff --git a/_sips/sips/unroll-default-arguments.md b/_sips/sips/unroll-default-arguments.md new file mode 100644 index 0000000000..6d90188b57 --- /dev/null +++ b/_sips/sips/unroll-default-arguments.md @@ -0,0 +1,934 @@ +--- +layout: sip +permalink: /sips/:title.html +stage: implementation +status: under-review +title: SIP-61 - Unroll Default Arguments for Binary Compatibility +--- + +**By: Li Haoyi** + +## History + +| Date | Version | +|---------------|--------------------| +| Feb 14th 2024 | Initial Draft | + +## Summary + +This SIP proposes an `@unroll` annotation lets you add additional parameters +to method `def`s,`class` construtors, or `case class`es, without breaking binary +compatibility. `@unroll` works by generating "unrolled" or "telescoping" forwarders: + +```scala +// Original +def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l + +// Generated +def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) +def foo(s: String, n: Int) = foo(s, n, true, 0) +``` + +In contrast to most existing or proposed alternatives that require you to contort your +code to become binary compatible (see [Major Alternatives](#major-alternatives)), +`@unroll` allows you to write Scala with vanilla `def`s/`class`es/`case class`es, add +a single annotation, and your code will maintain binary compatibility as new default +parameters and fields are added over time. + +`@unroll`'s only constraints are that: + +1. New parameters need to have a default value +2. New parameters can only be added on the right +3. The `@unroll`ed methods must be abstract or final + +These are both existing industry-wide standard when dealing with data and schema evolution +(e.g. [Schema evolution in Avro, Protocol Buffers and Thrift — Martin Kleppmann’s blog](https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html)), +and are also the way the new parameters interact with _source compatibility_ in +the Scala language. Thus these constraints should be immediately familiar to any +experienced programmers, and would be easy to follow without confusion. + +Prior Discussion can be found [here](https://contributors.scala-lang.org/t/can-we-make-adding-a-parameter-with-a-default-value-binary-compatible/6132) + +## Motivation + +Maintaining binary compatibility of Scala libraries as they evolve over time is +difficult. Although tools like https://github.com/lightbend/mima help _surface_ +issues, actually _resolving_ those issues is a different challenge. + +Some kinds of library changes are fundamentally impossible to make compatible, +e.g. removing methods or classes. But there is one big class of binary compatibility +issues that are "spurious": adding default parameters to methods, `class` constructors, +or `case class`es. + +Adding a default parameter is source-compatible, but not binary compatible: a user +downstream of a library that adds a default parameter does not need to make any +changes to their code, but _does_ need to re-compile it. This is "spurious" because +there is no _fundamental_ incompatibility here: semantically, a new default parameter +is meant to be optional! Old code invoking that method without a new default parameter +is exactly the user intent, and works just fine if the downstream code is re-compiled. + +Other languages, such as Python, have the same default parameter language feature but face +no such compatibility issues with their use. Even Scala codebases compiled from source +do not suffer these restrictions: adding a default parameter to the right side of a parameter +list is for all intents and purposes backwards compatible in a mono-repo setup. +The fact that such addition is binary incompatible is purely an implementation restriction +of Scala's binary artifact format and distribution strategy. + +**Binary compatibility is generally more important than Source compatibility**. When +you hit a source compatibility issue, you can always change the source code you are +compiling, whether manually or via your build tool. In contrast, when you hit binary +compatibility issues, it can come in the form of diamond dependencies that would require +_re-compiling all of your transitive dependencies_, a task that is far more difficult and +often impractical. + +There are many approaches to resolving these "spurious" binary compatibility issues, +but most of them involve either tremendous amounts of boilerplate writing +binary-compatibility forwarders, giving up on core language features like Case Classes +or Default Parameters, or both. Consider the following code snippet +([link](https://github.com/com-lihaoyi/mainargs/blob/1d04a6bd19aaca401d11fe26da31615a8bc9213c/mainargs/src/Parser.scala)) +from the [com-lihaoyi/mainargs](https://github.com/com-lihaoyi/mainargs) library, which +duplicates the parameters of `def constructEither` no less than five times in +order to maintain binary compatibility as the library evolves and more default +parameters are added to `def constructEither`: + +```scala + def constructEither( + args: Seq[String], + allowPositional: Boolean, + allowRepeats: Boolean, + totalWidth: Int, + printHelpOnExit: Boolean, + docsOnNewLine: Boolean, + autoPrintHelpAndExit: Option[(Int, PrintStream)], + customName: String, + customDoc: String, + sorted: Boolean, + ): Either[String, T] = constructEither( + args, + allowPositional, + allowRepeats, + totalWidth, + printHelpOnExit, + docsOnNewLine, + autoPrintHelpAndExit, + customName, + customDoc, + sorted, + ) + + def constructEither( + args: Seq[String], + allowPositional: Boolean = false, + allowRepeats: Boolean = false, + totalWidth: Int = 100, + printHelpOnExit: Boolean = true, + docsOnNewLine: Boolean = false, + autoPrintHelpAndExit: Option[(Int, PrintStream)] = Some((0, System.out)), + customName: String = null, + customDoc: String = null, + sorted: Boolean = true, + nameMapper: String => Option[String] = Util.kebabCaseNameMapper + ): Either[String, T] = ??? + + /** binary compatibility shim. */ + private[mainargs] def constructEither( + args: Seq[String], + allowPositional: Boolean, + allowRepeats: Boolean, + totalWidth: Int, + printHelpOnExit: Boolean, + docsOnNewLine: Boolean, + autoPrintHelpAndExit: Option[(Int, PrintStream)], + customName: String, + customDoc: String, + nameMapper: String => Option[String] + ): Either[String, T] = constructEither( + args, + allowPositional, + allowRepeats, + totalWidth, + printHelpOnExit, + docsOnNewLine, + autoPrintHelpAndExit, + customName, + customDoc, + sorted = true, + nameMapper = nameMapper + ) +``` + +Apart from being extremely verbose and full of boilerplate, like any boilerplate this is +also extremely error-prone. Bugs like [com-lihaoyi/mainargs#106](https://github.com/com-lihaoyi/mainargs/issues/106) +slip through when a mistake is made in that boilerplate. These bugs are impossible to catch +using a normal test suite, as they only appear in the presence of version skew. The above code +snippet actually _does_ have such a bug, that the test suite _did not_ catch. See if you can +spot it! + +Sebastien Doraene's talk [Designing Libraries for Source and Binary Compatibility](https://www.youtube.com/watch?v=2wkEX6MCxJs) +explores some of the challenges, and discusses the workarounds. + + +## Requirements + +### Backwards Compatibility + +Given: + +* Two libraries, **Upstream** and **Downstream**, where **Downstream** depends on **Upstream** + +* If we use a _newer_ version of **Upstream** which contains an added + default parameter together with an _older_ version of **Downstream** compiled + against an _older_ version of **Upstream** before that default parameter was added + +* The behavior should be binary compatible and semantically indistinguishable from using + a verion of **Downstream** compiled against the _newer_ version of **Upstream** + +**Note:** we do not aim for _Forwards_ compatibility. Using an _older_ +version of **Upstream** with a _newer_ version of **Downstream** compiled against a +_newer_ version of **Upstream** is not a use case we want to support. The vast majority +of OSS software does not promise forwards compatibility, including software such as +the JVM, so we should just follow suite + +### All Overrides Are Equivalent + +All versions of an `@unroll`ed method `def foo` should have the same semantics when called +with the same parameters. We must be careful to ensure: + +1. All our different method overrides point at the same underlying implementation +2. Abstract methods are properly implemented, and no method would fail with an + `AbstractMethodError` when called +3. We properly forward the necessary argument and default parameter values when + calling the respective implementation. + +## Proposed solution + + +The proposed solution is to provide a `scala.annotation.unroll` annotation, that +can be applied to methods `def`s, `class` constructors, or `case class`es to generate +"unrolled" or "telescoping" versions of a method that forward to the primary implementation: + +```scala + def constructEither( + args: Seq[String], + allowPositional: Boolean = false, + allowRepeats: Boolean = false, + totalWidth: Int = 100, + printHelpOnExit: Boolean = true, + docsOnNewLine: Boolean = false, + autoPrintHelpAndExit: Option[(Int, PrintStream)] = Some((0, System.out)), + customName: String = null, + customDoc: String = null, + @unroll sorted: Boolean = true, + @unroll nameMapper: String => Option[String] = Util.kebabCaseNameMapper + ): Either[String, T] = ??? +``` + +This allows the developer to write the minimal amount of code they _want_ to write, +and add a single annotation to allow binary compatibility to old versions. In this +case, we annotated `sorted` and `nameMapper` with `@unroll`, which generates forwarders that make +`def constructEither` binary compatible with older versions that have fewer parameters, +up to a version before `sorted` or `nameMapper` was added. Any existing method `def`, `class`, or +`case class` can be evolved in this way, by addition of `@unroll` the first time +a default argument is added to their signature after its initial definition. + +### Unrolling `def`s + +Consider a library that is written as follows: + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1) = s + n + b + l +} +``` + +If over time a new default parameter is added: + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, b: Boolean = true) = s + n + b + l +} +``` + +And another + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, b: Boolean = true, l: Long = 0) = s + n + b + l +} +``` + +This is a source-compatible change, but not binary-compatible: JVM bytecode compiled against an +earlier version of the library would be expecting to call `def foo(String, Int)`, but will fail +because the signature is now `def foo(String, Int, Boolean)` or `def foo(String, Int, Boolean, Long)`. +On the JVM this will result in a `MethodNotFoundError` at runtime, a common experience for anyone +who upgrading the versions of their dependencies. Similar concerns are present with Scala.js and +Scala-Native, albeit the failure happens at link-time rather than run-time + +`@unroll` is an annotation that can be applied as follows, to the first "additional" default +parameter that was added in each published version of the library (in this case, +`b: Boolean = true` and `l: Long = 0`) + + +```scala +import scala.annotation.unroll + +object Unrolled{ + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +} +``` + +The `@unroll` annotation takes `def foo` and generates synthetic forwarders for the purpose +of maintaining binary compatibility for old callers who may be expecting the previous signature. +These forwarders do nothing but forward the call to the current implementation, using the +given default parameter values: + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l + + def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) + def foo(s: String, n: Int) = foo(s, n, true, 0) +} +``` + +As a result, old callers who expect `def foo(String, Int, Boolean)` or `def foo(String, Int, Boolean, Long)` +can continue to work, even as new parameters are added to `def foo`. The only restriction is that +new parameters can only be added on the right, and they must be provided with a default value. + +If multiple default parameters are added at once (e.g. `b` and `l` below) you can also +choose to only `@unroll` the first default parameter of each batch, to avoid generating +unnecessary forwarders: + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, l: Long = 0) = s + n + b + l + + def foo(s: String, n: Int) = foo(s, n, true, 0) +} +``` + +If there are multiple parameter lists (e.g. for curried methods or methods taking implicits) only one +parameter list can be unrolled (though it does not need to be the first one). e.g. this works: + +```scala +object Unrolled{ + def foo(s: String, + n: Int = 1, + @unroll b: Boolean = true, + @unroll l: Long = 0) + (implicit blah: Blah) = s + n + b + l +} +``` + +As does this + +```scala +object Unrolled{ + def foo(blah: Blah) + (s: String, + n: Int = 1, + @unroll b: Boolean = true, + @unroll l: Long = 0) = s + n + b + l +} +``` + +`@unroll`ed methods can be defined in `object`s, `class`es, or `trait`s. Other cases are shown below. + +### Unrolling `class`es + +Class constructors and secondary constructors are treated by `@unroll` just like any +other method: + +```scala +class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0){ + def foo = s + n + b + l +} +``` + +Unrolls to: + +```scala +class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0){ + def foo = s + n + b + l + + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0) + def this(s: String, n: Int) = this(s, n, true, 0) +} +``` + +### Unrolling `class` secondary constructors + +```scala +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = { + this() + foo = s + n + b + l + } +} +``` + +Unrolls to: + +```scala +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = { + this() + foo = s + n + b + l + } + + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0) + def this(s: String, n: Int) = this(s, n, true, 0) +} +``` + +### Case Classes + +`case class`es can also be `@unroll`ed. Unlike normal `class` constructors +and method `def`s, `case class`es have several generated methods (`apply`, `copy`) +that need to be kept in sync with their primary constructor. `@unroll` thus +generates forwarders for those methods as well, based on the presence of the +`@unroll` annotation in the primary constructor: + +```scala +case class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true){ + def foo = s + n + b +} +``` + +Unrolls to: + +```scala +case class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0L){ + def this(s: String, n: Int) = this(s, n, true, 0L) + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0L) + + def copy(s: String, n: Int) = copy(s, n, this.b, this.l) + def copy(s: String, n: Int, b: Boolean) = copy(s, n, b, this.l) + + def foo = s + n + b +} +object Unrolled{ + def apply(s: String, n: Int) = apply(s, n, true, 0L) + def apply(s: String, n: Int, b: Boolean) = apply(s, n, b, 0L) +} +``` + +Notes: + +1. `@unroll`ed `case class`es are fully binary and backwards compatible in Scala 3, but not in Scala 2 + +2. `.unapply` does not need to be duplicated in Scala 3.x, as its signature + `def unapply(x: Unrolled): Unrolled` does not change when new `case class` fields are + added. + +3. Even in Scala 2.x, where `def unapply(x: Unrolled): Option[TupleN]` is not + binary compatible, pattern matching on `case class`es is already binary compatible + to addition of new fields due to + [Option-less Pattern Matching](https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html). + Thus, only calls to `.tupled` or `.curried` on the `case class` companion `object`, or direct calls + to `.unapply` on an unrolled `case class` in Scala 2.x (shown below) + will cause a crash if additional fields were added: + +```scala +def foo(t: (String, Int)) = println(t) +Unrolled.unapply(unrolled).map(foo) +``` + +In Scala 3, `@unroll`ing a `case class` also needs to generate a `fromProduct` +implementation in the companion object, as shown below: + +```scala +def fromProduct(p: Product): CaseClass = p.productArity match + case 2 => + CaseClass( + p.productElement(0).asInstanceOf[...], + p.productElement(1).asInstanceOf[...], + ) + case 3 => + CaseClass( + p.productElement(0).asInstanceOf[...], + p.productElement(1).asInstanceOf[...], + p.productElement(2).asInstanceOf[...], + ) + ... +``` + +This is not necessary for preserving binary compatibility - the method signature of +`def fromProduct` does not change depending on the number of fields - but it is +necessary to preserve semantic compatibility. `fromProduct` by default does not +take into account field default values, and this change is necessary to make it +use them when the given `p: Product` has a smaller `productArity` than the current +`CaseClass` implementation + + +### Hiding Generated Forwarder Methods + +As the generated forwarder methods are intended only for binary compatibility purposes, +we should generally hide them: IDEs, downstream compilers, ScalaDoc, etc. should behave as +if the generated methods do not exist. + +This is done in two different ways: + +1. In Scala 2, we generate the methods in a post-`pickler` phase. This ensures they do + not appear in the scala signature, and thus are not exposed to downstream tooling + +2. In Scala 3, the generated methods are flagged as `Invisible` + +## Limitations + +### Only the one parameter list of multi-parameter list methods can be `@unroll`ed. + +Unrolling multiple parameter lists would generate a number +of forwarder methods exponential with regard to the number of parameter lists unrolled, +and the generated forwarders may begin to conflict with each other. We can choose to spec +this out and implement it later if necessary, but for 99% of use cases `@unroll`ing one +parameter list should be enough. Typically, only one parameter list in a method has default +arguments, with other parameter lists being `implicit`s or a single callback/blocks, neither +of which usually has default values. + +### Unrolled forwarder methods can collide with manually-defined overrides + +This is similar to any other generated methods. We can raise an error to help users +debug such scenarios, but such name collisions are inevitably possible given how binary +compatibility on the JVM works. + +### `@unroll`ed case classes are only fully binary compatible in Scala 3 + + +They are _almost_ binary compatible in Scala 2. Direct calls to `unapply` are binary +incompatible, but most common pattern matching of `case class`es goes through a different +code path that _is_ binary compatible. There are also the `AbstractFunctionN` traits, from +which the companion object inherits `.curried` and `.tupled` members. Luckily, `unapply` +was made binary compatible in Scala 3, and `AbstractFunctionN`, `.curried`, and `.tupled` +were removed + +### While `@unroll`ed `case class`es are *not* fully _source_ compatible + +This is due to the fact that pattern matching requires all arguments to +be specified. This proposal does not change that. Future improvements related to +[Pattern Matching on Named Fields](https://github.com/scala/improvement-proposals/pull/44) +may bring improvements here. But as we discussed earlier, binary compatibility is generally +more important than source compatibility, and so we do not need to wait for any source +compatibility improvements to land before proceeding with these binary compatibility +improvements. + +### Binary and semantic compatibility for macro-derived derive typeclasses is out of scope + + +This propsosal does not have any opinion on whether or not macro-derivation is be binary/source/semantically +compatible. That is up to the +individual macro implementations to decide. e.g., [uPickle](https://github.com/com-lihaoyi/upickle) +has a very similar rule about adding `case class` fields, except that field ordering +does not matter. Trying to standardize this across all possible macros and all possible +typeclasses is out of scope + +### `@unroll` generates a quadratic amount of generated bytecode as more default parameters are added + +Each forwarder has `O(num-params)` size, and there are `O(num-default-params)` +forwarders. We do not expect this to be a problem in practice, as the small size of the +generated forwarder methods means the constant factor is small, but one could imagine +the `O(n^2)` asymptotic complexity becoming a problem if a method accumulates hundreds of +default parameters over time. In such extreme scenarios, some kind of builder pattern +(such as those listed in [Major Alternatives](#major-alternatives)) may be preferable. + +### `@unroll` only supports `final` methods. + +`object` methods and constructors are naturally +final, but `class` or `trait` methods that are `@unroll`ed need to be explicitly marked `final`. +It has proved difficult to implement the semantics of `@unroll` in the presence of downstream +overrides, `super`, etc. where the downstream overrides can be compiled against by different +versions of the upstream code. If we can come up with some implementation that works, we can +lift this restriction later, but for now I have not managed to do so and so this restriction +stays. + +### Challenges of Non-Final Methods and Overriding + +To elaborate a bit on the issues with non-final methods and overriding, consider the following +case with four classes, `Upstream`, `Downstream`, `Main1` and `Main2`, each of which is compiled +against different versions of each other (hence the varying number of parameters for `foo`): + +```scala +class Upstream{ // V2 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +} +``` + +```scala +class Downstream extends Upstream{ // compiled against Upstream V1 + override def foo(s: String, n: Int = 1) = super.foo(s, n) + s + n +} +``` + +```scala +object Main1 { // compiled against Upstream V2 + def main(args: Array[String]): Unit = { + new Downstream().foo("hello", 123, false, 456L) + } +} +``` + +```scala +object Main2 { // compiled against Upstream V1 + def main(args: Array[String]): Unit = { + new Downstream().foo("hello", 123) + } +} +``` + + +The challenge here is: how do we make sure that `Main1` and `Main2`, who call +`new Downstream().foo`, correctly pick up the version of `def foo` that is +provided by `Downstream`? + +With the current implementation, the `override def foo` inside `Downstream` would only +override one of `Upstream`'s synthetic forwarders, but would not override the actual +primary implementation. As a result, we would see `Main1` calling the implementation +of `foo` from `Upstream`, while `Main2` calls the implementation of `foo` from +`Downstream`. So even though both `Main1` and `Main2` have the same +`Upstream` and `Downstream` code on the classpath, they end up calling different +implementations based on what they were compiled against. + +We cannot perform the method search and dispatch _within_ the `def foo` methods, +because it depends on exactly _how_ `foo` is called: the `InvokeVirtual` call from +`Main1` is meant to resolve to `Downstream#foo`, while the `InvokeSpecial` call +from `Downstream#foo`'s `super.foo` is meant to resolve to `Upstream#foo`. But a +method implementation cannot know how it was called, and thus it is impossible +for `def foo` to forward the call to the right place. + +Like our treatment of [Abstract Methods](#abstract-methods), this scenario can never +happen according to what version combinations are supported by our definition of +[Backwards Compatibility](#backwards-compatibility), but nevertheless is a real +concern due to the requirement that [All Overrides Are Equivalent](#all-overrides-are-equivalent). + +It may be possible to loosen this restriction to also allow abstract methods that +are implemented only once by a final method. See the section about +[Abstract Methods](#abstract-methods) for details. + +## Major Alternatives + +The major alternatives to `@unroll` are listed below: + +1. [data-class](https://index.scala-lang.org/alexarchambault/data-class) +2. [SBT Contrabad](https://www.scala-sbt.org/contraband/) +3. [Structural Data Structures](https://contributors.scala-lang.org/t/pre-sip-structural-data-structures-that-can-evolve-in-a-binary-compatible-way/5684) +4. Avoiding language features like `case class`es or default parameters, as suggested by the + [Binary Compatibility for Library Authors](https://docs.scala-lang.org/overviews/core/binary-compatibility-for-library-authors.html) documentation. + +While those alternate approaches _do work_ - `data-class` and `SBT Datatype` are used heavily +in various open-source projects - I believe they are inferior to the approach that `@unroll` +takes: + +### Case Class v.s. not-a-Case-Class + +The first major difference between `@unroll` and the above alternatives is that these alternatives +all introduce something new: some kind of _not-a-case-class_ `class` that is to be used +when binary compatibility is desired. This _not-a-case-class_ has different syntax from +`case class`es, different semantics, different methods, and so on. + +In contrast, `@unroll` does not introduce any new language-level or library-level constructs. +The `@unroll` annotation is purely a compiler-backend concern for maintaining binary +compatibility. At a language level, `@unroll` allows you to keep using normal method `def`s, +`class`es and `case class`es with exactly the same syntax and semantics you have been using +all along. + +Having people be constantly choosing between _case-class_ and _not-a-case-class_ when +designing their data types, is inferior to simply using `case class`es all the time + + +### Scala Syntax v.s. Java-esque Syntax + + +The alternatives linked above all build a +Java-esque "[inner platform](https://en.wikipedia.org/wiki/Inner-platform_effect)" +on top of the Scala language, with its own conventions like `.withFoo` methods. + +In contrast, `@unroll` makes use of the existing Scala language's default parameters +to achieve the same effect. + +If we think Scala is nicer to write then Java due to its language +features, then `@unroll`'s approach of leveraging those language features is nicer +to use than the alternative's Java-esque syntax. + +Having implementation-level problems - which is what binary compatibility across version +skew is - bleed into the syntax and semantics of the language is also inferior to having it +be controlled by an annotation. Martin Odersky has said that annotations are intended for +things that do not affect typechecking, and `@unroll` fits the bill perfectly. + + +### Evolving Any Class v.s. Evolving Pre-determined Classes + +The alternatives given require that the developer has to decide _up front_ whether their +data type needs to be evolved while maintaining binary compatibility. + +In contrast, `@unroll` allows you to evolve any existing `class` or `case class`. + +In general, trying to decide which classes _will need to evolve later on_ is a difficult +task that is easy to get wrong. `@unroll` totally removes that requirement, allowing +you to take _any_ `class` or `case class` and evolve it later in a binary compatible way. + + +### Binary Compatibility for Methods and Classes + +Lastly, the above alternatives only solve _half_ the problem: how to evolve `case class`es. +This is _schema evolution_. + +Binary compatility is not just a problem for `case class`es adding new fields: normal +`class` constructors, instance method `def`s, static method `def`s, etc. have default +parameters added all the time as well. + +In contrast, `@unroll` allows the evolution of `def`s and normal `class`es, in addition +to `case class`es, all using the same approach: + +1. `@unroll`ing `case class`es is about _schema evolution_ +2. `@unroll`ing concrete method `def`s is about _API evolution_ +3. `@unroll`ing abstract method `def`s is about _protocol evolution_ + +All three cases above have analogous best practices in the broader software engineering +world: whether you are adding an optional column to a database table, adding an +optional flag to a command-line tool, are extending an existing protocol with optional +fields that may need handling by both clients and servers implementing that protocol. + +`@unroll` solves all three problems at once - schema evolution, API evolution, and protocol +evolution. It does so with the same Scala-level syntax and semantics, with the same requirements +and limitations that common schema/API/protocol-evolution best-practices have in the broader +software engineering community. + +### Abstract Methods + +Apart from `final` methods, `@unroll` also supports purely abstract methods. Consider +the following example with a trait `Unrolled` and an implementation `UnrolledObj`: + +```scala +trait Unrolled{ // version 3 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String +} +``` +```scala +object UnrolledObj extends Unrolled{ // version 3 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b +} +``` + +This unrolls to: +```scala +trait Unrolled{ // version 3 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b) + def foo(s: String, n: Int, b: Boolean): String = foo(s, n) + def foo(s: String, n: Int): String +} +``` +```scala +object UnrolledObj extends Unrolled{ // version 3 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l + def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) + def foo(s: String, n: Int) = foo(s, n, true) +} +``` + +Note that both the abstract methods from `trait Unrolled` and the concrete methods +from `object UnrolledObj` generate forwarders when `@unroll`ed, but the forwarders +are generated _in opposite directions_! Unrolled concrete methods forward from longer +parameter lists to shorter parameter lists, while unrolled abstract methods forward +from shorter parameter lists to longer parameter lists. For example, we may have a +version of `object UnrolledObj` that was compiled against an earlier version of `trait Unrolled`: + + +```scala +object UnrolledObj extends Unrolled{ // version 2 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b + def foo(s: String, n: Int) = foo(s, n, true) +} +``` + +But further downstream code calling `.foo` on `UnrolledObj` may expect any of the following signatures, +depending on what version of `Unrolled` and `UnrolledObj` it was compiled against: + +```scala +UnrolledObj.foo(String, Int) +UnrolledObj.foo(String, Int, Boolean) +UnrolledObj.foo(String, Int, Boolean, Long) +``` + +Because such downstream code cannot know which version of `Unrolled` that `UnrolledObj` +was compiled against, we need to ensure all such calls find their way to the correct +implementation of `def foo`, which may be at any of the above signatures. This "double +forwarding" strategy ensures that regardless of _which_ version of `.foo` gets called, +it ends up eventually forwarding to the actual implementation of `foo`, with +the correct combination of passed arguments and default arguments + +```scala +UnrolledObj.foo(String, Int) // forwards to UnrolledObj.foo(String, Int, Boolean) +UnrolledObj.foo(String, Int, Boolean) // actual implementation +UnrolledObj.foo(String, Int, Boolean, Long) // forwards to UnrolledObj.foo(String, Int, Boolean) +``` + +As is the case for `@unroll`ed methods on `trait`s and `class`es, `@unroll`ed +implementations of an abtract method must be final. + +#### Are Reverse Forwarders Really Necessary? + +This "double forwarding" strategy is not strictly necessary to support +[Backwards Compatibility](#backwards-compatibility): the "reverse" forwarders +generated for abstract methods are only necessary when a downstream callsite +of `UnrolledObj.foo` is compiled against a newer version of the original +`trait Unrolled` than the `object UnrolledObj` was, as shown below: + +```scala +trait Unrolled{ // version 3 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0): String = foo(s, n, b) + // generated + def foo(s: String, n: Int, b: Boolean): String = foo(s, n) + def foo(s: String, n: Int): String +} +``` +```scala +object UnrolledObj extends Unrolled{ // version 2 + def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b + // generated + def foo(s: String, n: Int) = foo(s, n, true) +} +``` +```scala +// version 3 +UnrolledObj.foo("hello", 123, true, 456L) +``` + +If we did not have the reverse forwarder from `foo(String, Int, Boolean, Long)` to +`foo(String, Int, Boolean)`, this call would fail at runtime with an `AbstractMethodError`. +It also will get caught by MiMa as a `ReversedMissingMethodProblem`. + +This configuration of version is not allowed given our definition of backwards compatibility: +that definition assumes that `Unrolled` must be of a greater or equal version than `UnrolledObj`, +which itself must be of a greater or equal version than the final call to `UnrolledObj.foo`. However, +the reverse forwarders are needed to fulfill our requirement +[All Overrides Are Equivalent](#all-overrides-are-equivalent): +looking at `trait Unrolled // version 3` and `object UnrolledObj // version 2` in isolation, +we find that without the reverse forwarders the signature `foo(String, Int, Boolean, Long)` +is defined but not implemented. Such an un-implemented abstract method is something +we want to avoid, even if our artifact version constraints mean it should technically +never get called. + +## Minor Alternatives: + + +### `@unrollAll` + +Currently, `@unroll` generates a forwarder only for the annotated default parameter; +if you want to generate multiple forwarders, you need to `@unroll` each one. In the +vast majority of scenarios, we want to unroll every default parameters we add, and in +many cases default parameters are added one at a time. In this case, an `@unrollAll` +annotation may be useful, a shorthand for applying `@unroll` to the annotated default +parameter and every parameter to the right of it: + +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, @unrollAll b: Boolean = true, l: Long = 0) = s + n + b + l +} +``` +```scala +object Unrolled{ + def foo(s: String, n: Int = 1, b: Boolean = true, l: Long = 0) = s + n + b + l + + def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) + def foo(s: String, n: Int) = foo(s, n, true, 0) +} +``` + + + +### Generating Forwarders For Parameter Type Widening or Result Type Narrowing + +While this proposal focuses on generating forwarders for addition of default parameters, +you can also imagine similar forwarders being generated if method parameter types +are widened or if result types are narrowed: + +```scala +// Before +def foo(s: String, n: Int = 1, b: Boolean = true) = s + n + b + l + +// After +def foo(@unrollType[String] s: Object, n: Int = 1, b: Boolean = true) = s.toString + n + b + l + +// Generated +def foo(s: Object, n: Int = 1, b: Boolean = true) = s.toString + n + b + l +def foo(s: String, n: Int = 1, b: Boolean = true) = foo(s, n, b) +``` + +This would follow the precedence of how Java's and Scala's covariant method return +type overrides are implemented: when a class overrides a method with a new +implementation with a narrower return type, a forwarder method is generated to +allow anyone calling the original signature \to be forwarded to the narrower signature. + +This is not currently implemented in `@unroll`, but would be a straightforward addition. + +### Incremental Forwarders or Direct Forwarders + +Given this: + +```scala +def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +``` + +There are two ways to do the forwarders. First option, which I used in above, is +to have each forwarder directly call the primary method: + +```scala +def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) +def foo(s: String, n: Int) = foo(s, n, true, 0) +``` + +Second option is to have each forwarder incrementally call the next forwarder, which +will eventually end up calling the primary method: + +```scala +def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) +def foo(s: String, n: Int) = foo(s, n, true) +``` + +The first option results in shorter stack traces, while the second option results in +roughly half as much generated bytecode in the method bodies (though it's still `O(n^2)`). + +In order to allow `@unroll`ing of [Abstract Methods](#abstract-methods), we had to go with +the second option. This is because when an abstract method is overriden, it is not necessarily +true that the longest override that contains the implementation. Thus we need to forward +between the different `def foo` overrides one at a time until the override containing the +implementation is found. + + + +## Implementation & Testing + +This SIP has a full implementation for Scala {2.12, 2.13, 3} X {JVM, JS, Native} +in the following repository, as a compiler plugin: + +- https://github.com/com-lihaoyi/unroll + +As the `@unroll` annotation is purely a compile-time construct and does not need to exist +at runtime, `@unroll` can be added to Scala 2.13.x without breaking forwards compatibility. + +The linked repo also contains an extensive test suite that uses both MIMA as well +as classpath-mangling to validate that it provides both the binary and semantic +compatibility benefits claimed in this document. In fact, it has even discovered +bugs in the upstream Scala implementation related to binary compatibility, e.g. +[scala-native/scala-native#3747](https://github.com/scala-native/scala-native/issues/3747) + +I have also opened pull requests to a number of popular OSS Scala libraries, +using `@unroll` as a replacement for manually writing binary compatibility stubs, +and the 100s of lines of boilerplate reduction can be seen in the links below: + +- https://github.com/com-lihaoyi/mainargs/pull/113/files +- https://github.com/com-lihaoyi/mill/pull/3008/files +- https://github.com/com-lihaoyi/upickle/pull/555/files +- https://github.com/com-lihaoyi/os-lib/pull/254 + +These pull requests all pass both the test suite as well as the MIMA +`check-binary-compatibility` job, demonstrating that this approach does work +in real-world codebases. At time of writing, these are published under the following +artifacts and can be used in your own projects already: + +- Compiler Plugin: `ivy"com.lihaoyi::unroll-plugin:0.1.12"` +- Annotation: `ivy"com.lihaoyi::unroll-annotation:0.1.12"` diff --git a/_sips/sips/unsigned-integers.md b/_sips/sips/unsigned-integers.md new file mode 100644 index 0000000000..4cfa9995db --- /dev/null +++ b/_sips/sips/unsigned-integers.md @@ -0,0 +1,6 @@ +--- +title: SIP-26 - Unsigned Integers +status: rejected +pull-request-number: 27 + +--- diff --git a/_sips/sips/2012-01-30-value-classes.md b/_sips/sips/value-classes.md similarity index 97% rename from _sips/sips/2012-01-30-value-classes.md rename to _sips/sips/value-classes.md index 35ad090bc1..48ad643281 100644 --- a/_sips/sips/2012-01-30-value-classes.md +++ b/_sips/sips/value-classes.md @@ -1,15 +1,16 @@ --- layout: sip title: SIP-15 - Value Classes - -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. +stage: completed +status: shipped permalink: /sips/:title.html redirect_from: /sips/pending/value-classes.html --- **By: Martin Odersky and Jeff Olson and Paul Phillips and Joshua Suereth** +> Note from the SIP Committee: 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. + ## History | Date | Version | diff --git a/_sips/sips/wildcard-context-bounds.md b/_sips/sips/wildcard-context-bounds.md new file mode 100644 index 0000000000..7ef71f1f61 --- /dev/null +++ b/_sips/sips/wildcard-context-bounds.md @@ -0,0 +1,6 @@ +--- +title: SIP-52 - Wildcard context bounds +status: withdrawn +pull-request-number: 55 + +--- diff --git a/_sips/sips/xistential-containers.md b/_sips/sips/xistential-containers.md new file mode 100644 index 0000000000..a3f4764c3e --- /dev/null +++ b/_sips/sips/xistential-containers.md @@ -0,0 +1,7 @@ +--- +title: 'SIP-69: Existential containers' +status: under-review +pull-request-number: 101 +stage: design + +--- diff --git a/_style/files.md b/_style/files.md index 54ddf4e2db..704948ab32 100644 --- a/_style/files.md +++ b/_style/files.md @@ -11,33 +11,32 @@ previous-page: method-invocation next-page: scaladoc --- -As a rule, files should contain a *single* logical compilation unit. By -"logical" I mean a class, trait or object. One exception to this -guideline is for classes or traits which have companion objects. -Companion objects should be grouped with their corresponding class or -trait in the same file. These files should be named according to the -class, trait or object they contain: +The unit of work for the compiler is a "compilation unit", +which is usually just an ordinary file. +The Scala language places few restrictions on how code is organized across files. +The definition of a class, or equivalently a trait or object, can't be split over multiple files, +so it must be contained within a single file. +A class and its companion object must be defined together in the same file. +A sealed class can be extended only in the same file, so all its subclasses must be defined there. - package com.novell.coolness +Similarly, there are no restrictions on the name of a source file or where it is located in the file system, +although certain conventions are broadly honored in practice. +Generally, the file is named after the class it contains, +or if it has more than one class, the parent class. + +For example, a file, `Inbox.scala`, is expected to contain `Inbox` and its companion: + + package org.coolness class Inbox { ... } // companion object object Inbox { ... } -These compilation units should be placed within a file named -`Inbox.scala` within the `com/novell/coolness` directory. In short, the -Java file naming and positioning conventions should be preferred, -despite the fact that Scala allows for greater flexibility in this -regard. - -## Multi-Unit Files +The file may be located in a directory, `org/coolness`, following Java tooling conventions, +but this is at the discretion of the developer and for their convenience. -Despite what was said above, there are some important situations which -warrant the inclusion of multiple compilation units within a single -file. One common example is that of a sealed trait and several -sub-classes (often emulating the ADT language feature available in -functional languages): +It is natural to put the following `Option` ADT in a file, `Option.scala`: sealed trait Option[+A] @@ -45,26 +44,11 @@ functional languages): case object None extends Option[Nothing] -Because of the nature of sealed superclasses (and traits), all subtypes -*must* be included in the same file. Thus, such a situation definitely -qualifies as an instance where the preference for single-unit files -should be ignored. - -Another case is when multiple classes logically form a single, cohesive -group, sharing concepts to the point where maintenance is greatly served -by containing them within a single file. These situations are harder to -predict than the aforementioned sealed supertype exception. Generally -speaking, if it is *easier* to perform long-term maintenance and -development on several units in a single file rather than spread across -multiple, then such an organizational strategy should be preferred for -these classes. However, keep in mind that when multiple units are -contained within a single file, it is often more difficult to find -specific units when it comes time to make changes. - -**All multi-unit files should be given camelCase names with a lower-case -first letter.** This is a very important convention. It differentiates -multi- from single-unit files, greatly easing the process of finding -declarations. These filenames may be based upon a significant type which -they contain (e.g. `option.scala` for the example above), or may be -descriptive of the logical property shared by all units within (e.g. -`ast.scala`). +The related elements, `Some` and `None`, are easily discoverable, even in the absence of tooling. + +When unrelated classes are grouped together, perhaps because they implement a feature or a model a domain, +the source file receives a descriptive `camelCase` name. +Some prefer this naming scheme for top-level terms. For example, `object project` would be found in `project.scala`. +Similarly, a package object defined as `package object model` is located in `package.scala` in the `model` source directory. + +Files created just for quick testing can have arbitrary names, such as `demo-bug.scala`. diff --git a/_style/index.md b/_style/index.md index 7663a32054..9c126423ca 100644 --- a/_style/index.md +++ b/_style/index.md @@ -59,7 +59,6 @@ This document is intended to outline some basic Scala stylistic guidelines which - [Symbolic Methods/Operators](method-invocation.html#symbolic-methodsoperators) - [Higher-Order Functions](method-invocation.html#higher-order-functions) - [Files](files.html) - - [Multi-Unit Files](files.html#multi-unit-files) - [Scaladoc](scaladoc.html) - [General Style](scaladoc.html#general-style) - [Packages](scaladoc.html#packages) diff --git a/_style/naming-conventions.md b/_style/naming-conventions.md index 1e796b4145..9ea2bd8084 100644 --- a/_style/naming-conventions.md +++ b/_style/naming-conventions.md @@ -195,44 +195,36 @@ getter and setter. For example: ### Parentheses -Unlike Ruby, Scala attaches significance to whether or not a method is -*declared* with parentheses (only applicable to methods of -[arity](https://en.wikipedia.org/wiki/Arity)-0). For example: +Scala allows a parameterless, zero-[arity](https://en.wikipedia.org/wiki/Arity) +method to be declared with an empty parameter list: def foo1() = ... +or with no parameter lists at all: + def foo2 = ... -These are different methods at compile-time. While `foo1` can be called -with or without the parentheses, `foo2` *may not* be called *with* -parentheses. - -Thus, it is actually quite important that proper guidelines be observed -regarding when it is appropriate to declare a method without parentheses -and when it is not. - -Methods which act as accessors of any sort (either encapsulating a field -or a logical property) should be declared *without* parentheses except -if they have side effects. While Ruby and Lift use a `!` to indicate -this, the usage of parens is preferred (please note that fluid APIs and -internal domain-specific languages have a tendency to break the -guidelines given below for the sake of syntax. Such exceptions should -not be considered a violation so much as a time when these rules do not -apply. In a DSL, syntax should be paramount over convention). - -Further, the callsite should follow the declaration; if declared with -parentheses, call with parentheses. While there is temptation to save a -few characters, if you follow this guideline, your code will be *much* -more readable and maintainable. - - // doesn't change state, call as birthdate - def birthdate = firstName - - // updates our internal state, call as age() - def age() = { - _age = updateAge(birthdate) - _age - } +By convention, parentheses are used to indicate that a method has +side effects, such as altering the receiver. + +On the other hand, the absence of parentheses indicates that a +method is like an accessor: it returns a value without altering the +receiver, and on the same receiver in the same state, it always +returns the same answer. + +The callsite should follow the declaration; if declared with +parentheses, call with parentheses. + +These conventions are followed in the Scala standard library and +you should follow them in your own code as well. + +Additional notes: + +* Scala 3 errors if you leave out the parentheses at the call site. Scala 2 merely warns. +* Scala 3 and 2 both error if the call site has parentheses where the definition doesn't. +* Java-defined methods are exempt from this distinction and may be called either way. +* If a method _does_ take parameters, there isn't any convention for indicating whether it also has side effects. +* Creating an object isn't considered a side effect. So for example, Scala collections have an `iterator` method with no parens. Yes, you get a new iterator each time. And yes, iterators are mutable. But every fresh iterator is the same until it has been altered by calling a side-effecting method such as `Iterator#next()`, which _is_ declared with parentheses. See this [2018 design discussion](https://github.com/scala/collection-strawman/issues/520). ### Symbolic Method Names @@ -364,8 +356,7 @@ for local names to be very short: def add(a: Int, b: Int) = a + b -This would be bad practice in languages like Java, but it is *good* -practice in Scala. This convention works because properly-written Scala +This convention works because properly-written Scala methods are quite short, only spanning a single expression and rarely going beyond a few lines. Few local names are used (including parameters), and so there is no need to contrive long, descriptive diff --git a/_style/scaladoc.md b/_style/scaladoc.md index 7a78bd713f..12dc9528a5 100644 --- a/_style/scaladoc.md +++ b/_style/scaladoc.md @@ -208,7 +208,7 @@ sure to indicate the actual method names: * @return a new Person instance with the age determined by the * birthdate and current date. */ - def apply(name: String, birthDate: java.util.Date) = {} + def apply(name: String, birthDate: java.time.LocalDate) = {} } If your object holds implicit conversions, provide an example in the diff --git a/_th/cheatsheets/index.md b/_th/cheatsheets/index.md index 49861d3f0c..b59cf817a3 100644 --- a/_th/cheatsheets/index.md +++ b/_th/cheatsheets/index.md @@ -66,9 +66,9 @@ language: "th" | `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 ใน arg ของฟังก์ชันสำหรับ จับคู่รูปแบบ (pattern maching) | -| 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" กับ backticks ถูกตีความว่าเป็น v42 val ที่มีอยู่และ
พิมพ์ "Not 42" | -| Good
`val UppercaseVal = 42`
`Some(3) match {`
` case Some(UppercaseVal) => println("42")`
` case _ => println("Not 42")`
`}` | UppercaseVal ถือว่าเป็น val ที่มีอยู่ไม่ใช่ตัวแปรรูปแบบใหม่
เพราะมันเริ่มต้นด้วยตัวอักษรตัวพิมพ์ใหญ่ ดังนั้นค่าที่มีอยู่ใน
UppercaseVal จะถูกตรวจสอบเทียบกับ 3 และพิมพ์ "Not 42" | +| 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" กับ backticks ถูกตีความว่าเป็น v42 val ที่มีอยู่และ
แสดงค่า "Not 42" | +| Good
`val UppercaseVal = 42`
`Some(3) match {`
` case Some(UppercaseVal) => println("42")`
` case _ => println("Not 42")`
`}` | UppercaseVal ถือว่าเป็น val ที่มีอยู่ไม่ใช่ตัวแปรรูปแบบใหม่
เพราะมันเริ่มต้นด้วยตัวอักษรตัวพิมพ์ใหญ่ ดังนั้นค่าที่มีอยู่ใน
UppercaseVal จะถูกตรวจสอบเทียบกับ 3 และแสดงค่า "Not 42" | | การใช้งาน object | | | `class C(x: R)` _เหมือนกับ_
`class C(private val x: R)`
`var c = new C(4)` | ตัวสร้างพารามิเตอร์ - x มีเฉพาะในคลาส body เท่านั้น | | `class C(val x: R)`
`var c = new C(4)`
`c.x` | ตัวสร้างพารามิเตอร์ - กำหนดสมาชิกสาธารณะโดยอัตโนมัติ | diff --git a/_th/tour/automatic-closures.md b/_th/tour/automatic-closures.md deleted file mode 100644 index 1c30b693a9..0000000000 --- a/_th/tour/automatic-closures.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: tour -title: Automatic Closures -partof: scala-tour - -language: th ---- diff --git a/_th/tour/basics.md b/_th/tour/basics.md index 48c285511e..273161fd6a 100644 --- a/_th/tour/basics.md +++ b/_th/tour/basics.md @@ -15,16 +15,14 @@ In this page, we will cover basics of Scala. ## ทดลอง Scala ในเว็บบราวเซอร์ -เราสามารถรัน Scala ในเว็บเบราว์เซอร์ด้วย ScalaFiddle +เราสามารถรัน Scala ในเว็บเบราว์เซอร์ด้วย Scastie -1. ไปที่ [https://scalafiddle.io](https://scalafiddle.io). +1. ไปที่ [Scastie](https://scastie.scala-lang.org/). 2. วาง `println("Hello, world!")` ในด้านซ้าย. 3. กดที่ปุ่ม "Run" . output จะแสดงในด้านขวา ในขั้นตอนนี้ง่ายมาก ที่จะได้ประสบการณ์ของเรากับ Scala -หลายๆ โค้ดตัวอย่างในเอกสารนี้จะฝังใน ScalaFiddle ซึ่งคุณสามารถกดที่ปุ่ม Run เพื่อดูว่าโด้นนั้นๆ จะได้ผลลัพธ์อย่างไร - ## Expressions Expression หรือ นิพจน์ เป็นโค้ดที่ทำการคำนวนได้ @@ -33,14 +31,12 @@ Expression หรือ นิพจน์ เป็นโค้ดที่ท ``` เราสามารถแสดงผลลัพธ์ของ Expression ด้วยการใช้ `println` -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ### Values @@ -111,21 +107,17 @@ function เป็น expression ที่รับ parameter ได้ เราสามารถตั้งชื่อของ function ได้ดังนี้ -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} function สามารถรับ parameter ได้หลายตัว -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} หรือ เราจะไม่รับ parameter เลยก็ได้ @@ -140,23 +132,19 @@ Method มีลักษณะเหมือนกับ function มาก Method จะประกาศได้ด้วย keyword `def` ตามด้วยชื่อของ function, รายการ parameter, return type และ body ของ function -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} สังเกตว่า การ return type จะประกาศ _หลังจาก_ รายการ parameter และ colon `: Int` Method ยังสามารถรับรายการ parameter ได้หลายรายการ -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} หรือ ไม่มีรายการ parameter เลยก็ได้ อย่างเช่น @@ -169,7 +157,6 @@ println("Hello, " + name + "!") Method สามารถมี expression ได้หลายบรรทัด -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -177,7 +164,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} expression สุดท้ายใน body เป็น expression ที่ return value ของ method (Scala ก็มี keyword `return` แต่ว่าไม่ค่อยได้ใช้) @@ -222,15 +208,15 @@ val yetAnotherPoint = Point(2, 2) ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$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.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) and Point(2,2) are different. ``` @@ -277,7 +263,6 @@ trait Greeter { Trait สามารถมี default implementation ได้ -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -302,7 +287,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} จากตัวอย่างนี้ `defaultGreeter` ขยายเพียง trait เดียว แต่มันสามารถขยายหลาย trait @@ -310,7 +294,7 @@ customGreeter.greet("Scala developer") // How are you, Scala developer? ## Main Method -main method เป็น entry point หรือจุดเริ่มต้นของโปรแกรม ใน ​Java Virtual Machine +main method เป็น entry point หรือจุดเริ่มต้นของโปรแกรม ใน ​Java Virtual Machine ต้องการ main method ชื่อว่า `main` และสามารถรับ argument ที่เป็น array ของ string ใช้ object เราสามารถประกาศ main method ได้ดังนี้: diff --git a/_th/tour/classes.md b/_th/tour/classes.md index 7800a5f492..3054dbcc8d 100644 --- a/_th/tour/classes.md +++ b/_th/tour/classes.md @@ -11,7 +11,7 @@ next-page: traits previous-page: unified-types --- -คลาสใน Scala เป็นพิมพ์เขียวสำหรับสร้าง object ในคลาสสามารถมี method, value, ตัวแปร, type, object, +คลาสใน Scala เป็นพิมพ์เขียวสำหรับสร้าง object ในคลาสสามารถมี method, value, ตัวแปร, type, object, trait และคลาส ซึ่งเรียกรวมๆ กันว่า _members_ หรือ _สมาชิก_ ของคลาส type, object และ trait จะกล่าวถึงภายหลัง ## การกำหนดคลาส @@ -22,7 +22,7 @@ class User val user1 = new User ``` -keyword `new` ใช้เพื่อสร้าง instance ของคลาส `User` มี constructor เริ่มต้นซึ่งไม่รับค่า argument เพราะว่าไม่ได้กำหนด constructor ไว้ตอนประกาสคลาส +keyword `new` ใช้เพื่อสร้าง instance ของคลาส `User` มี constructor เริ่มต้นซึ่งไม่รับค่า argument เพราะว่าไม่ได้กำหนด constructor ไว้ตอนประกาสคลาส อย่างไรก็ตาม, เราอาจจะมี constructor และ body ของคลาส ตัวอย่างดังนี้ เป็นการประกาศคลาสสำหรับจุด (point): @@ -41,11 +41,11 @@ class Point(var x: Int, var y: Int) { val point1 = new Point(2, 3) point1.x // 2 -println(point1) // พิมพ์ (2, 3) +println(point1) // แสดงค่า (2, 3) ``` คลาส `Point` นี้มีสมาชิก 4 ตัว คือ ตัวแปร `x` และ `y` และ method `move` และ `toString` -ไม่เหมือนภาษาอื่นๆ, ซึ่ง constructor หลักจะอยู่ใน class signature `(var x: Int, var y: Int)` +ไม่เหมือนภาษาอื่นๆ, ซึ่ง constructor หลักจะอยู่ใน class signature `(var x: Int, var y: Int)` method `move` รับ argument ชนิดตัวเลข 2 ตัว และ return เป็นค่า Unit `()` ซึ่งไม่มีค่า จะมีผลลัพธ์คลายกับ `void` ในภาษาที่เหมือน Java, `toString` ในทางกลับกัน ไม่รับ argument ใดๆ แต่ return เป็นค่า `String` ซึ่งแทนที่ method `toString` จาก [`AnyRef`](unified-types.html) โดยจะมี keyword `override` @@ -58,7 +58,7 @@ 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) // พิมพ์ 1 +println(point1.x) // แสดงค่า 1 ``` @@ -68,13 +68,13 @@ println(point1.x) // พิมพ์ 1 ```scala mdoc:nest class Point(var x: Int = 0, var y: Int = 0) val point2 = new Point(y=2) -println(point2.y) // พิมพ์ 2 +println(point2.y) // แสดงค่า 2 ``` นี้เป็นวิธีปฏิบัติที่ดีเพื่อจะทำให้โค้ดชัดเจนมากขึ้น ## Private Members และ Getter/Setter -สมาชิกของคลาสจะเป็น public โดยค่าเริ่มต้น ใช้ access modifier `private` +สมาชิกของคลาสจะเป็น public โดยค่าเริ่มต้น ใช้ access modifier `private` เพื่อซ่อนสมาชิกนั้นจากภายนอกของคลาส ```scala mdoc:reset class Point { @@ -97,10 +97,10 @@ class Point { val point1 = new Point point1.x = 99 -point1.y = 101 // พิมพ์คำเตือน warning +point1.y = 101 // แสดงค่า "WARNING: Out of bounds" ``` -คลาส `Point` เวอร์ชันนี้ ข้อมูลจะถูกเก็บไว้ในตัวแปรชนิด private ที่ชื่อว่า `_x` และ `_y` และมี method ที่ชื่อว่า `def x` และ `def y` ทีจะใช้ในการเข้าถึงข้อมูล private เป็น getter, `def x_=` และ `def y=` -เป็น method สำหรับตรวจสอบข้อมูลและ setting ค่าของตัวแปร `_x` และ `_y` +คลาส `Point` เวอร์ชันนี้ ข้อมูลจะถูกเก็บไว้ในตัวแปรชนิด private ที่ชื่อว่า `_x` และ `_y` และมี method ที่ชื่อว่า `def x` และ `def y` ทีจะใช้ในการเข้าถึงข้อมูล private เป็น getter, `def x_=` และ `def y=` +เป็น method สำหรับตรวจสอบข้อมูลและ setting ค่าของตัวแปร `_x` และ `_y` สังเกตว่า syntax พิเศษนี้สำหรับ setter: คือ method ที่ตามด้วย `_=` ไปยังตัวระบุของ setter และ parameter ตามหลังมา constructor หลักกำหนด parameter ด้วย `val` และ `var` เป็น public อย่างไรก็ตามเพราะว่า `val` เป็นตัวแปรที่เปลี่ยนแปลงไม่ได้ (immutable) เราไม่สามารถเขียบแบบนี้ได้ diff --git a/_th/tour/named-arguments.md b/_th/tour/named-arguments.md index 161b53b50e..0257732c5d 100644 --- a/_th/tour/named-arguments.md +++ b/_th/tour/named-arguments.md @@ -10,3 +10,56 @@ language: th next-page: packages-and-imports previous-page: default-parameter-values --- + +เมื่อเราเรียกใช้ method แล้วเราสามารถระบุชื่อ argument (label the argument) สำหรับ parameter ใดๆ ได้ดังนี้: + +{% tabs named-arguments-when-good %} + +{% tab 'Scala 2 and 3' for=named-arguments-when-good %} + +```scala mdoc +def printName(first: String, last: String): Unit = + println(s"$first $last") + +printName("John", "Public") // แสดงค่า "John Public" +printName(first = "John", last = "Public") // แสดงค่า "John Public" +printName(last = "Public", first = "John") // แสดงค่า "John Public" +printName("Elton", last = "John") // แสดงค่า "Elton John" +``` + +{% endtab %} + +{% endtabs %} + +named argument นั้นมีประโยชน์เมื่อ parameter 2 ตัวมี type เดียวกัน\ +ทำให้ argument ที่เราส่งไปให้ function อาจถูกสลับกันโดยไม่ได้ตั้งใจ + +สังเกตว่าเราจะเขียน argument ที่ระบุชื่อในลำดับใดก็ได้\ +แต่ถ้า argument ไม่ได้อยู่ในลำดับของ parameter ใน function จากซ้ายไปขวา แล้ว argument ที่เหลือจะต้องระบุชื่อทั้งหมด + +ในตัวอย่างข้างล่างนี้ named argument ทำให้เราสามารถเว้น parameter `middle` ได้\ +แต่ในกรณีที่เกิด `error: positional after named argument`\ +เนื่องจาก argument ตัวแรกไม่ได้เรียงตามลำดับของ parameter (ตัวแรกไม่ใช่ parameter `first` และ argument ตัวที่ 2 เป็นต้นไปก็ไม่ได้ระบุชื่อด้วย)\ +ดังนั้น เราจะต้องระบุชื่อ argument ตั้งแต่ตัวที่ 2 เป็นต้นไป + +{% tabs named-arguments-when-error %} + +{% tab 'Scala 2 and 3' for=named-arguments-when-error %} + +```scala mdoc:fail +def printFullName(first: String, middle: String = "Q.", last: String): Unit = + println(s"$first $middle $last") + +printFullName(first = "John", last = "Public") // แสดงค่า "John Q. Public" +printFullName("John", last = "Public") // แสดงค่า "John Q. Public" +printFullName("John", middle = "Quincy", "Public") // แสดงค่า "John Quincy Public" +printFullName(last = "Public", first = "John") // แสดงค่า "John Q. Public" +printFullName(last = "Public", "John") // error: positional after named argument +``` + +{% endtab %} + +{% endtabs %} + +เราสามารถใช้ Named Argument กับการเรียกใช้ method ของ Java ได้\ +แต่ทำได้เฉพาะในกรณีที่ Java library นั้นถูกคอมไพล์ด้วยออพชั่น `-parameters` เท่านั้น diff --git a/_th/tour/tour-of-scala.md b/_th/tour/tour-of-scala.md index f10a88d77d..ab75a7d541 100644 --- a/_th/tour/tour-of-scala.md +++ b/_th/tour/tour-of-scala.md @@ -13,7 +13,7 @@ next-page: basics ## ยินดีต้อนรับสู่การเดินทาง การเดินทางครั้งนี้ประกอบด้วยการแนะนำแบบสั้นๆ สำหรับแต่ล่ะคุณสมบัติของ Scala ที่ใช้บ่อยที่สุด และมีมีความตั้งใจเพื่อสำหรับผู้ที่เรียนรู้ภาษา Scala ใหม่ -และนี่ก็เป็นเพียงบทความสั้นๆ ที่ไม่ใช้การสอนเต็มรูปแบบของภาษา Scala หากต้องการเรียนรู้มากขึ้นให้พิจารณา[หนังสือ](/books.html) หรือขอคำปรึกษาจาก[แหล่งอื่นๆ](/learn.html) +และนี่ก็เป็นเพียงบทความสั้นๆ ที่ไม่ใช้การสอนเต็มรูปแบบของภาษา Scala หากต้องการเรียนรู้มากขึ้นให้พิจารณา[หนังสือ](/books.html) หรือขอคำปรึกษาจาก[แหล่งอื่นๆ](/online-courses.html) ## อะไรคือ Scala? Scala เป็นภาษาเขียนโปรแกรมแบบหลากหลายกระบวนทัศน์ที่ทันสมัย ที่ออกแบบมาเพื่อแสดงรูปแบบการเขียนโปรแกรมโดยทั่วไปที่กระชับ สง่างาม และมีชนิดข้อมูลที่ปลอดภัย (type-safe) ซึ่งผสานคุณสมบัติของภาษาเชิงวัตถุและภาษาเชิงฟังก์ชัน diff --git a/_th/tour/traits.md b/_th/tour/traits.md index dfc3b31a74..92dc07597c 100644 --- a/_th/tour/traits.md +++ b/_th/tour/traits.md @@ -75,7 +75,7 @@ val cat = new Cat("Sally") val animals = ArrayBuffer.empty[Pet] animals.append(dog) animals.append(cat) -animals.foreach(pet => println(pet.name)) // พิมพ์ Harry Sally +animals.foreach(pet => println(pet.name)) // แสดงค่า Harry Sally ``` -`trait Pet` มี abstract field `name` ซึ่ง implement โดย Cat และ Dog ใน constructor ของมัน +`trait Pet` มี abstract field `name` ซึ่ง implement โดย Cat และ Dog ใน constructor ของมัน ในบรรทัดสุดท้าย เราเรียก `pet.name` ซึ่งจะต้องถูก implement แล้วใน subtype ใดๆ ของ trait `Pet` diff --git a/_th/tour/unified-types.md b/_th/tour/unified-types.md index 7e92c8a39f..4c2b24c48a 100644 --- a/_th/tour/unified-types.md +++ b/_th/tour/unified-types.md @@ -57,7 +57,7 @@ Value type สามารถแปลได้ด้วยวิธีดัง ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (หมายเหตุว่าค่าความละเอียดจะสูญหายไปในกรณีนี้) +val y: Float = x.toFloat // 9.8765434E8 (หมายเหตุว่าค่าความละเอียดจะสูญหายไปในกรณีนี้) val face: Char = '☺' val number: Int = face // 9786 @@ -67,7 +67,7 @@ val number: Int = face // 9786 ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // ไม่เป็นไปตามที่ต้องการ ``` diff --git a/_tour/abstract-type-members.md b/_tour/abstract-type-members.md index 77dedcc386..aef46a52b2 100644 --- a/_tour/abstract-type-members.md +++ b/_tour/abstract-type-members.md @@ -8,7 +8,7 @@ previous-page: inner-classes topics: abstract type members prerequisite-knowledge: variance, upper-type-bound -redirect_from: +redirect_from: - "/tutorials/tour/abstract-types.html" - "/tour/abstract-types.html" --- @@ -17,14 +17,28 @@ Abstract types, such as traits and abstract classes, can in turn have abstract t This means that the concrete implementations define the actual types. Here's an example: +{% tabs abstract-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_1 %} ```scala mdoc trait Buffer { type T val element: T } ``` +{% endtab %} +{% tab 'Scala 3' for=abstract-types_1 %} +```scala +trait Buffer: + type T + val element: T +``` +{% endtab %} +{% endtabs %} + 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. +{% tabs abstract-types_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_2 %} ```scala mdoc abstract class SeqBuffer extends Buffer { type U @@ -32,29 +46,61 @@ abstract class SeqBuffer extends Buffer { def length = element.length } ``` +{% endtab %} +{% tab 'Scala 3' for=abstract-types_2 %} +```scala +abstract class SeqBuffer extends Buffer: + type U + type T <: Seq[U] + def length = element.length +``` +{% endtab %} +{% endtabs %} + Notice how we can use yet another abstract type `U` in the specification of an upper-type-bound for `T`. 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: +{% tabs abstract-types_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_3 %} ```scala mdoc 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) - } + type T = List[U] + val element = List(elem1, elem2) + } val buf = newIntSeqBuf(7, 8) println("length = " + buf.length) println("content = " + buf.element) ``` +{% endtab %} +{% tab 'Scala 3' for=abstract-types_3 %} +```scala +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) +``` +{% endtab %} +{% endtabs %} + Here the factory `newIntSeqBuf` uses an anonymous class implementation of `IntSeqBuffer` (i.e. `new IntSeqBuffer`) to set the abstract type `T` to the concrete type `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: +{% tabs abstract-types_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=abstract-types_4 %} ```scala mdoc:nest abstract class Buffer[+T] { val element: T @@ -72,5 +118,24 @@ val buf = newIntSeqBuf(7, 8) println("length = " + buf.length) println("content = " + buf.element) ``` +{% endtab %} +{% tab 'Scala 3' for=abstract-types_4 %} +```scala +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) +``` +{% endtab %} +{% endtabs %} 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 type members with type parameters. diff --git a/_tour/annotations.md b/_tour/annotations.md index bdd39a09b7..a9876fab42 100644 --- a/_tour/annotations.md +++ b/_tour/annotations.md @@ -11,14 +11,29 @@ redirect_from: "/tutorials/tour/annotations.html" --- 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. -``` + +{% tabs annotations_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_1 %} +```scala mdoc:fail object DeprecationDemo extends App { @deprecated("deprecation message", "release # which deprecates method") def hello = "hola" - hello + hello } ``` +{% endtab %} +{% tab 'Scala 3' for=annotations_1 %} +```scala +object DeprecationDemo extends App: + @deprecated("deprecation message", "release # which deprecates method") + def hello = "hola" + + hello +``` +{% endtab %} +{% endtabs %} + 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. @@ -26,6 +41,9 @@ An annotation clause applies to the first definition or declaration following it ## 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: + +{% tabs annotations_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_2 %} ```scala mdoc import scala.annotation.tailrec @@ -38,8 +56,26 @@ def factorial(x: Int): Int = { 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: +{% endtab %} +{% tab 'Scala 3' for=annotations_2 %} +```scala +import scala.annotation.tailrec + +def factorial(x: Int): Int = + + @tailrec + def factorialHelper(x: Int, accumulator: Int): Int = + if x == 1 then accumulator else factorialHelper(x - 1, accumulator * x) + factorialHelper(x, 1) ``` +{% endtab %} +{% endtabs %} + +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: + +{% tabs annotations_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_3 %} +```scala mdoc:fail import scala.annotation.tailrec def factorial(x: Int): Int = { @@ -50,76 +86,134 @@ def factorial(x: Int): Int = { factorialHelper(x) } ``` -We would get the message "Recursive call not in tail position". +{% endtab %} +{% tab 'Scala 3' for=annotations_3 %} +```scala +import scala.annotation.tailrec +def factorial(x: Int): Int = + @tailrec + def factorialHelper(x: Int): Int = + if x == 1 then 1 else x * factorialHelper(x - 1) + factorialHelper(x) +``` +{% endtab %} +{% endtabs %} + +We would get the message "Recursive call not in tail position". ## Annotations affecting code generation + +{% tabs annotations_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_4 %} + 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. +{% endtab %} +{% tab 'Scala 3' for=annotations_4 %} + +Some annotations like `@main` affect the generated code (i.e. your jar file might have different bytes than if you hadn't used the annotation). A `@main` annotation on a method generates an executable program that calls the method as an entry point. + +{% endtab %} +{% endtabs %} + ### 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 -``` +{% tabs annotations_5 %} +{% tab 'Java' for=annotations_5 %} +```java @interface Source { - public String URL(); + public String url(); public String mail(); } ``` +{% endtab %} +{% endtabs %} And then apply it as follows -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_6 %} +{% tab 'Java' for=annotations_6 %} +```java +@Source(url = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends TheirClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} -An annotation application in Scala looks like a constructor invocation, for instantiating a Java annotation one has to use named arguments: +An annotation application in Scala looks like a constructor invocation, but to instantiate a Java annotation one has to use named arguments: -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_7 %} +{% tab 'Scala 2 and 3' for=annotations_7 %} +```scala +@Source(url = "https://coders.com/", mail = "support@coders.com") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} 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: -``` +{% tabs annotations_8 %} +{% tab 'Java' for=annotations_8 %} +```java @interface SourceURL { public String value(); public String mail() default ""; } ``` +{% endtab %} +{% endtabs %} -And then apply it as follows +And then apply it as follows: -``` +{% tabs annotations_9 %} +{% tab 'Java' for=annotations_9 %} +```java @SourceURL("https://coders.com/") -public class MyClass extends TheirClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} -In this case, Scala provides the same possibility +In this case, Scala provides the same possibility: -``` +{% tabs annotations_10 %} +{% tab 'Scala 2 and 3' for=annotations_10 %} +```scala @SourceURL("https://coders.com/") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} -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: +The `mail` element was specified with a default value so we need not explicitly provide a value for it. +However, if we need to provide one then in Java we must also explicitly name the `value` parameter: -``` +{% tabs annotations_11 %} +{% tab 'Java' for=annotations_11 %} +```java @SourceURL(value = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends TheirClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} -Scala provides more flexibility in this respect +Scala provides more flexibility in this respect, so we can choose to only name the `mail` parameter: -``` +{% tabs annotations_12 %} +{% tab 'Scala 2 and 3' for=annotations_12 %} +```scala @SourceURL("https://coders.com/", mail = "support@coders.com") - class MyScalaClass ... +class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} diff --git a/_tour/automatic-closures.md b/_tour/automatic-closures.md deleted file mode 100644 index 7df0b975ab..0000000000 --- a/_tour/automatic-closures.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: tour -title: Automatic Type-Dependent Closure Construction -partof: scala-tour - -redirect_from: "/tutorials/tour/automatic-closures.html" ---- - -Scala allows parameterless function names as parameters of methods. When such a method is called, the actual parameters for parameterless function names are not evaluated and a nullary function is passed instead which encapsulates the computation of the corresponding parameter (so-called *call-by-name* evaluation). - -The following code demonstrates this mechanism: - - object TargetTest1 extends Application { - def whileLoop(cond: => Boolean)(body: => Unit): Unit = - if (cond) { - body - whileLoop(cond)(body) - } - var i = 10 - whileLoop (i > 0) { - println(i) - i -= 1 - } - } - -The function whileLoop takes two parameters `cond` and `body`. When the function is applied, the actual parameters do not get evaluated. But whenever the formal parameters are used in the body of `whileLoop`, the implicitly created nullary functions will be evaluated instead. Thus, our method `whileLoop` implements a Java-like while-loop with a recursive implementation scheme. - -We can combine the use of [infix/postfix operators](operators.html) with this mechanism to create more complex statements (with a nice syntax). - -Here is the implementation of a loop-unless statement: - - object TargetTest2 extends Application { - 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) - } -The `loop` function just accepts a body of a loop and returns an instance of class `LoopUnlessCond` (which encapsulates this body object). Note that the body didn't get evaluated yet. Class `LoopUnlessCond` has a method `unless` which we can use as a *infix operator*. This way, we achieve a quite natural syntax for our new loop: `loop { < stats > } unless ( < cond > )`. - -Here's the output when `TargetTest2` gets executed: - - i = 10 - i = 9 - i = 8 - i = 7 - i = 6 - i = 5 - i = 4 - i = 3 - i = 2 - i = 1 diff --git a/_tour/basics.md b/_tour/basics.md index 2ba7e70655..ccfb324feb 100644 --- a/_tour/basics.md +++ b/_tour/basics.md @@ -14,72 +14,98 @@ In this page, we will cover the basics of Scala. ## Trying Scala in the Browser -You can run Scala in your browser with _ScalaFiddle_. This is an easy, zero-setup way to experiment with pieces of Scala code: +You can run Scala in your browser with _Scastie_. This is an easy, zero-setup way to experiment with pieces of Scala code: -1. Go to [https://scalafiddle.io](https://scalafiddle.io). +1. Go to [Scastie](https://scastie.scala-lang.org/). 2. Paste `println("Hello, world!")` in the left pane. 3. Click __Run__. The output appears in the right pane. -_ScalaFiddle_ is integrated with some of the code examples in this documentation; if you see a __Run__ button in a code example below, click it to directly experiment with the code. - ## Expressions Expressions are computable statements: + +{% tabs expression %} +{% tab 'Scala 2 and 3' for=expression %} ```scala mdoc 1 + 1 ``` +{% endtab %} +{% endtabs %} + You can output the results of expressions using `println`: -{% scalafiddle %} +{% tabs println %} +{% tab 'Scala 2 and 3' for=println %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} +{% endtab %} +{% endtabs %} ### Values You can name the results of expressions using the `val` keyword: +{% tabs val %} +{% tab 'Scala 2 and 3' for=val %} ```scala mdoc val x = 1 + 1 println(x) // 2 ``` +{% endtab %} +{% endtabs %} Named results, such as `x` here, are called values. Referencing a value does not re-compute it. Values cannot be re-assigned: +{% tabs val-error %} +{% tab 'Scala 2 and 3' for=val-error %} ```scala mdoc:fail x = 3 // This does not compile. ``` +{% endtab %} +{% endtabs %} The type of a value can be omitted and [inferred](https://docs.scala-lang.org/tour/type-inference.html), or it can be explicitly stated: +{% tabs type-inference %} +{% tab 'Scala 2 and 3' for=type-inference %} ```scala mdoc:nest val x: Int = 1 + 1 ``` +{% endtab %} +{% endtabs %} -Notice how the type declaration `Int` comes after the identifier `x`. You also need a `:`. +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. +{% tabs var %} +{% tab 'Scala 2 and 3' for=var %} ```scala mdoc:nest var x = 1 + 1 x = 3 // This compiles because "x" is declared with the "var" keyword. println(x * x) // 9 ``` +{% endtab %} +{% endtabs %} As with values, the type of a variable can be omitted and [inferred](https://docs.scala-lang.org/tour/type-inference.html), or it can be explicitly stated: +{% tabs type-inference-2 %} +{% tab 'Scala 2 and 3' for=type-inference-2 %} ```scala mdoc:nest var x: Int = 1 + 1 ``` +{% endtab %} +{% endtabs %} ## Blocks @@ -88,12 +114,16 @@ 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: +{% tabs blocks %} +{% tab 'Scala 2 and 3' for=blocks %} ```scala mdoc println({ val x = 1 + 1 x + 1 }) // 3 ``` +{% endtab %} +{% endtabs %} ## Functions @@ -101,36 +131,48 @@ Functions are expressions that have parameters, and take arguments. You can define an anonymous function (i.e., a function that has no name) that returns a given integer plus one: +{% tabs anonymous-function %} +{% tab 'Scala 2 and 3' for=anonymous-function %} ```scala mdoc (x: Int) => x + 1 ``` +{% endtab %} +{% endtabs %} On the left of `=>` is a list of parameters. On the right is an expression involving the parameters. You can also name functions: -{% scalafiddle %} +{% tabs named-function %} +{% tab 'Scala 2 and 3' for=named-function %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} +{% endtab %} +{% endtabs %} A function can have multiple parameters: -{% scalafiddle %} +{% tabs multiple-parameters %} +{% tab 'Scala 2 and 3' for=multiple-parameters %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} +{% endtab %} +{% endtabs %} Or it can have no parameters at all: +{% tabs no-parameters %} +{% tab 'Scala 2 and 3' for=no-parameters %} ```scala mdoc val getTheAnswer = () => 42 println(getTheAnswer()) // 42 ``` +{% endtab %} +{% endtabs %} ## Methods @@ -138,36 +180,46 @@ Methods look and behave very similar to functions, but there are a few key diffe Methods are defined with the `def` keyword. `def` is followed by a name, parameter list(s), a return type, and a body: -{% scalafiddle %} +{% tabs method %} +{% tab 'Scala 2 and 3' for=method %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} +{% endtab %} +{% endtabs %} Notice how the return type `Int` is declared _after_ the parameter list and a `:`. A method can take multiple parameter lists: -{% scalafiddle %} +{% tabs multiple-parameter-lists %} +{% tab 'Scala 2 and 3' for=multiple-parameter-lists %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} +{% endtab %} +{% endtabs %} Or no parameter lists at all: +{% tabs no-parameter-lists %} +{% tab 'Scala 2 and 3' for=no-parameter-lists %} ```scala mdoc def name: String = System.getProperty("user.name") println("Hello, " + name + "!") ``` +{% endtab %} +{% endtabs %} There are some other differences, but for now, you can think of methods as something similar to functions. Methods can have multi-line expressions as well: -{% scalafiddle %} +{% tabs get-square-string class=tabs-scala-version %} + +{% tab 'Scala 2' for=get-square-string %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -175,7 +227,19 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} +{% endtab %} + +{% tab 'Scala 3' for=get-square-string %} +```scala +def getSquareString(input: Double): String = + val square = input * input + square.toString + +println(getSquareString(2.5)) // 6.25 +``` +{% endtab %} + +{% endtabs %} The last expression in the body is the method's return value. (Scala does have a `return` keyword, but it is rarely used.) @@ -183,20 +247,48 @@ The last expression in the body is the method's return value. (Scala does have a You can define classes with the `class` keyword, followed by its name and constructor parameters: +{% tabs greeter-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-definition %} ```scala mdoc class Greeter(prefix: String, suffix: String) { def greet(name: String): Unit = println(prefix + name + suffix) } ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-definition %} +```scala +class Greeter(prefix: String, suffix: String): + def greet(name: String): Unit = + println(prefix + name + suffix) +``` +{% endtab %} + +{% endtabs %} + The return type of the method `greet` is `Unit`, which signifies that there is nothing meaningful to return. It is 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: +In Scala 2 you can make an instance of a class with the `new` keyword. In Scala 3, however, the `new` keyword is not needed thanks to [universal apply methods](https://docs.scala-lang.org/scala3/reference/other-new-features/creator-applications.html): + +{% tabs greeter-usage class=tabs-scala-version %} +{% tab 'Scala 2' for=greeter-usage %} ```scala mdoc:nest val greeter = new Greeter("Hello, ", "!") greeter.greet("Scala developer") // Hello, Scala developer! ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-usage %} +```scala +val greeter = Greeter("Hello, ", "!") +greeter.greet("Scala developer") // Hello, Scala developer! +``` +{% endtab %} + +{% endtabs %} We will cover classes in depth [later](classes.html). @@ -206,20 +298,31 @@ Scala has a special type of class called a "case" class. By default, instances o You can define case classes with the `case class` keywords: +{% tabs case-class-definition %} +{% tab 'Scala 2 and 3' for=case-class-definition %} ```scala mdoc case class Point(x: Int, y: Int) ``` +{% endtab %} +{% endtabs %} You can instantiate case classes without the `new` keyword: +{% tabs case-class-creation %} +{% tab 'Scala 2 and 3' for=case-class-creation %} ```scala mdoc val point = Point(1, 2) val anotherPoint = Point(1, 2) val yetAnotherPoint = Point(2, 2) ``` +{% endtab %} +{% endtabs %} Instances of case classes are compared by value, not by reference: +{% tabs compare-case-class-equality class=tabs-scala-version %} + +{% tab 'Scala 2' for=compare-case-class-equality %} ```scala mdoc if (point == anotherPoint) { println(s"$point and $anotherPoint are the same.") @@ -233,6 +336,25 @@ if (point == yetAnotherPoint) { println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) and Point(2,2) are different. ``` +{% endtab %} + +{% tab 'Scala 3' for=compare-case-class-equality %} +```scala +if point == anotherPoint then + println(s"$point and $anotherPoint are the same.") +else + println(s"$point and $anotherPoint are different.") +// ==> Point(1,2) and Point(1,2) are the same. + +if point == yetAnotherPoint then + println(s"$point and $yetAnotherPoint are the same.") +else + println(s"$point and $yetAnotherPoint are different.") +// ==> Point(1,2) and Point(2,2) are different. +``` +{% endtab %} + +{% endtabs %} There is a lot more to case classes that we would like to introduce, and we are convinced you will fall in love with them! We will cover them in depth [later](case-classes.html). @@ -242,6 +364,9 @@ Objects are single instances of their own definitions. You can think of them as You can define objects with the `object` keyword: +{% tabs id-factory-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=id-factory-definition %} ```scala mdoc object IdFactory { private var counter = 0 @@ -251,15 +376,32 @@ object IdFactory { } } ``` +{% endtab %} + +{% tab 'Scala 3' for=id-factory-definition %} +```scala +object IdFactory: + private var counter = 0 + def create(): Int = + counter += 1 + counter +``` +{% endtab %} + +{% endtabs %} You can access an object by referring to its name: +{% tabs id-factory-usage %} +{% tab 'Scala 2 and 3' for=id-factory-usage %} ```scala mdoc val newId: Int = IdFactory.create() println(newId) // 1 val newerId: Int = IdFactory.create() println(newerId) // 2 ``` +{% endtab %} +{% endtabs %} We will cover objects in depth [later](singleton-objects.html). @@ -269,24 +411,53 @@ Traits are abstract data types containing certain fields and methods. In Scala i You can define traits with the `trait` keyword: +{% tabs greeter-trait-def class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-trait-def %} ```scala mdoc:nest trait Greeter { def greet(name: String): Unit } ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-trait-def %} +```scala +trait Greeter: + def greet(name: String): Unit +``` +{% endtab %} + +{% endtabs %} Traits can also have default implementations: -{% scalafiddle %} +{% tabs greeter-trait-def-impl class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-trait-def-impl %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = println("Hello, " + name + "!") } ``` +{% endtab %} + +{% tab 'Scala 3' for=greeter-trait-def-impl %} +```scala +trait Greeter: + def greet(name: String): Unit = + println("Hello, " + name + "!") +``` +{% endtab %} + +{% endtabs %} You can extend traits with the `extends` keyword and override an implementation with the `override` keyword: +{% tabs greeter-implementations class=tabs-scala-version %} + +{% tab 'Scala 2' for=greeter-implementations %} ```scala mdoc class DefaultGreeter extends Greeter @@ -302,19 +473,41 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} +{% endtab %} + +{% tab 'Scala 3' for=greeter-implementations %} +```scala +class DefaultGreeter extends Greeter + +class CustomizableGreeter(prefix: String, postfix: String) extends Greeter: + override def greet(name: String): Unit = + println(prefix + name + postfix) + +val greeter = DefaultGreeter() +greeter.greet("Scala developer") // Hello, Scala developer! + +val customGreeter = CustomizableGreeter("How are you, ", "?") +customGreeter.greet("Scala developer") // How are you, Scala developer? +``` +{% endtab %} + +{% endtabs %} Here, `DefaultGreeter` extends only one single trait, but it could extend multiple traits. We will cover traits in depth [later](traits.html). -## Main Method +## Program Entry Point The main method is the entry point of a Scala program. The Java Virtual Machine requires a main method, named `main`, that takes one argument: an array of strings. -Using an object, you can define the main method as follows: +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-demo %} + +In Scala 2 you must define a main method manually. Using an object, you can define the main method as follows: ```scala mdoc object Main { @@ -322,7 +515,19 @@ object Main { println("Hello, Scala developer!") } ``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} + +In Scala 3, with the `@main` annotation, a main method is automatically generated from a method as follows: + +```scala +@main def hello() = println("Hello, Scala developer!") +``` +{% endtab %} + +{% endtabs %} ## More resources -* [Scala book](/overviews/scala-book/prelude-taste-of-scala.html) overview +* [Scala book](/scala3/book/taste-intro.html) overview diff --git a/_tour/by-name-parameters.md b/_tour/by-name-parameters.md index 6d0ff8b610..441aefa597 100644 --- a/_tour/by-name-parameters.md +++ b/_tour/by-name-parameters.md @@ -8,16 +8,25 @@ next-page: annotations previous-page: operators redirect_from: "/tutorials/tour/by-name-parameters.html" +redirect_from: "/tutorials/tour/automatic-closures.html" --- _By-name parameters_ are evaluated every time they are used. They won't be evaluated at all if they are unused. This is similar to replacing the by-name parameters with the passed expressions. They are in contrast to _by-value parameters_. To make a parameter called by-name, simply prepend `=>` to its type. + +{% tabs by-name-parameters_1 %} +{% tab 'Scala 2 and 3' for=by-name-parameters_1 %} ```scala mdoc def calculate(input: => Int) = input * 37 ``` +{% endtab %} +{% endtabs %} + By-name parameters have 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: +{% tabs by-name-parameters_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=by-name-parameters_2 %} ```scala mdoc def whileLoop(condition: => Boolean)(body: => Unit): Unit = if (condition) { @@ -32,6 +41,24 @@ whileLoop (i > 0) { i -= 1 } // prints 2 1 ``` +{% endtab %} +{% tab 'Scala 3' for=by-name-parameters_2 %} +```scala +def whileLoop(condition: => Boolean)(body: => Unit): Unit = + if condition then + body + whileLoop(condition)(body) + +var i = 2 + +whileLoop (i > 0) { + println(i) + i -= 1 +} // prints 2 1 +``` +{% endtab %} +{% endtabs %} + 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. diff --git a/_tour/case-classes.md b/_tour/case-classes.md index b05756f81b..889f8e98bc 100644 --- a/_tour/case-classes.md +++ b/_tour/case-classes.md @@ -15,14 +15,26 @@ Case classes are like regular classes with a few key differences which we will g ## Defining a case class A minimal case class requires the keywords `case class`, an identifier, and a parameter list (which may be empty): + +{% tabs case-classe_Book %} + +{% tab 'Scala 2 and 3' for=case-classe_Book %} ```scala mdoc case class Book(isbn: String) val frankenstein = Book("978-0486282114") ``` -Notice how the keyword `new` was not used to instantiate the `Book` case class. This is because case classes have an `apply` method by default which takes care of object construction. +{% endtab %} + +{% endtabs %} + +Although that is usually left out, it is possible to explicitly use the `new` keyword, as `new Book()`. 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. + +{% tabs case-classe_Message_define %} + +{% tab 'Scala 2 and 3' for=case-classe_Message_define %} ``` case class Message(sender: String, recipient: String, body: String) val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?") @@ -30,10 +42,18 @@ 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 ``` +{% endtab %} + +{% endtabs %} + 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 Instances of case classes are compared by structure and not by reference: + +{% tabs case-classe_Message_compare %} + +{% tab 'Scala 2 and 3' for=case-classe_Message_compare %} ```scala mdoc case class Message(sender: String, recipient: String, body: String) @@ -41,10 +61,18 @@ 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 ``` +{% endtab %} + +{% endtabs %} + 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. + +{% tabs case-classe_Message_copy %} + +{% tab 'Scala 2 and 3' for=case-classe_Message_copy %} ```scala mdoc:nest 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") @@ -53,8 +81,12 @@ message5.sender // travis@washington.us message5.recipient // claire@bourgogne.fr message5.body // "Me zo o komz gant ma amezeg" ``` +{% endtab %} + +{% endtabs %} + The recipient of `message4` is used as the sender of `message5` but the `body` of `message4` was copied directly. ## More resources -* Learn more about case classes in the [Scala Book](/overviews/scala-book/case-classes.html) +* Learn more about case classes in the [Scala Book](/scala3/book/domain-modeling-tools.html#case-classes) diff --git a/_tour/classes.md b/_tour/classes.md index 0bda77c78e..683ae049cf 100644 --- a/_tour/classes.md +++ b/_tour/classes.md @@ -18,13 +18,37 @@ values, variables, types, objects, traits, and classes which are collectively ca ## Defining a class A minimal class definition is simply the keyword `class` and an identifier. Class names should be capitalized. + +{% tabs class-minimal-user class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-minimal-user %} ```scala mdoc 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: +The keyword `new` is used to create an instance of the class. +{% endtab %} + +{% tab 'Scala 3' for=class-minimal-user %} +```scala +class User + +val user1 = User() +``` + +We call the class like a function, as `User()`, to create an instance of the class. +It is also possible to explicitly use the `new` keyword, as `new User()`, although that is usually left out. +{% endtab %} + +{% endtabs %} + +`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: + +{% tabs class-point-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-example %} ```scala mdoc class Point(var x: Int, var y: Int) { @@ -38,38 +62,93 @@ class Point(var x: Int, var y: Int) { } val point1 = new Point(2, 3) -println(point1.x) // 2 -println(point1) // prints (2, 3) +println(point1.x) // prints 2 +println(point1) // prints (2, 3) +``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-example %} +```scala +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)" +end Point + +val point1 = Point(2, 3) +println(point1.x) // prints 2 +println(point1) // prints (2, 3) ``` +{% endtab %} + +{% endtabs %} 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. +`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 to `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: +{% tabs class-point-with-default-values class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-with-default-values %} ```scala mdoc:nest 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 +val origin = new Point // x and y are both set to 0 +val point1 = new Point(1) // x is set to 1 and y is set to 0 +println(point1) // prints (1, 0) +``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-with-default-values %} +```scala +class Point(var x: Int = 0, var y: Int = 0) +val origin = Point() // x and y are both set to 0 +val point1 = Point(1) // x is set to 1 and y is set to 0 +println(point1) // prints (1, 0) ``` +{% endtab %} + +{% endtabs %} 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. + +{% tabs class-point-named-argument class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-named-argument %} ```scala mdoc:nest class Point(var x: Int = 0, var y: Int = 0) val point2 = new Point(y = 2) -println(point2.y) // prints 2 +println(point2) // prints (0, 2) ``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-named-argument %} +```scala +class Point(var x: Int = 0, var y: Int = 0) +val point2 = Point(y = 2) +println(point2) // prints (0, 2) +``` +{% endtab %} + +{% endtabs %} 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. + +{% tabs class-point-private-getter-setter class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-private-getter-setter %} ```scala mdoc:reset class Point { private var _x = 0 @@ -77,39 +156,111 @@ class Point { private val bound = 100 def x: Int = _x - def x_= (newValue: Int): Unit = { - if (newValue < bound) _x = newValue else printWarning() + def x_=(newValue: Int): Unit = { + if (newValue < bound) + _x = newValue + else + printWarning() } def y: Int = _y - def y_= (newValue: Int): Unit = { - if (newValue < bound) _y = newValue else printWarning() + def y_=(newValue: Int): Unit = { + if (newValue < bound) + _y = newValue + else + printWarning() } - private def printWarning() = println("WARNING: Out of bounds") + private def printWarning(): Unit = + println("WARNING: Out of bounds") } val point1 = new Point point1.x = 99 point1.y = 101 // prints the warning ``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-private-getter-setter %} +```scala +class Point: + private var _x = 0 + private var _y = 0 + private val bound = 100 + + def x: Int = _x + def x_=(newValue: Int): Unit = + if newValue < bound then + _x = newValue + else + printWarning() + + def y: Int = _y + def y_=(newValue: Int): Unit = + if newValue < bound then + _y = newValue + else + printWarning() + + private def printWarning(): Unit = + println("WARNING: Out of bounds") +end Point + +val point1 = Point() +point1.x = 99 +point1.y = 101 // prints the warning +``` +{% endtab %} + +{% endtabs %} + 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. + +{% tabs class-point-cannot-set-val class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-cannot-set-val %} ```scala mdoc:fail class Point(val x: Int, val y: Int) val point = new Point(1, 2) point.x = 3 // <-- does not compile ``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-cannot-set-val %} +```scala +class Point(val x: Int, val y: Int) +val point = Point(1, 2) +point.x = 3 // <-- does not compile +``` +{% endtab %} + +{% endtabs %} Parameters without `val` or `var` are private values, visible only within the class. + +{% tabs class-point-non-val-ctor-param class=tabs-scala-version %} + +{% tab 'Scala 2' for=class-point-non-val-ctor-param %} ```scala mdoc:fail class Point(x: Int, y: Int) val point = new Point(1, 2) point.x // <-- does not compile ``` +{% endtab %} + +{% tab 'Scala 3' for=class-point-non-val-ctor-param %} +```scala +class Point(x: Int, y: Int) +val point = Point(1, 2) +point.x // <-- does not compile +``` +{% endtab %} + +{% endtabs %} ## More resources -* Learn more about Classes in the [Scala Book](/overviews/scala-book/classes.html) -* How to use [Auxiliary Class Constructors](/overviews/scala-book/classes-aux-constructors.html) +* Learn more about Classes in the [Scala Book](/scala3/book/domain-modeling-tools.html#classes) +* How to use [Auxiliary Class Constructors](/scala3/book/domain-modeling-tools.html#auxiliary-constructors) diff --git a/_tour/compound-types.md b/_tour/compound-types.md index 4cd53674b5..2c12773600 100644 --- a/_tour/compound-types.md +++ b/_tour/compound-types.md @@ -1,6 +1,6 @@ --- layout: tour -title: Compound Types +title: Intersection Types, aka Compound Types partof: scala-tour num: 26 @@ -10,13 +10,18 @@ previous-page: abstract-type-members redirect_from: "/tutorials/tour/compound-types.html" --- -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. +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 *intersection types*, (or *compound types* in +Scala 2) which are types that behave like any part of the intersection. Suppose we have two traits `Cloneable` and `Resetable`: +{% tabs compound-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_1 %} ```scala mdoc trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { + override def clone(): Cloneable = { // makes clone public super.clone().asInstanceOf[Cloneable] } } @@ -24,28 +29,67 @@ trait Resetable { def reset: Unit } ``` +{% endtab %} +{% tab 'Scala 3' for=compound-types_1 %} +```scala +trait Cloneable extends java.lang.Cloneable: + override def clone(): Cloneable = // makes clone public + super.clone().asInstanceOf[Cloneable] +trait Resetable: + def reset: Unit +``` +{% endtab %} +{% endtabs %} Now suppose we want to write a function `cloneAndReset` which takes an object, clones it and resets the original object: -``` +{% tabs compound-types_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_2 %} +```scala mdoc:fail def cloneAndReset(obj: ?): Cloneable = { val cloned = obj.clone() obj.reset cloned } ``` +{% endtab %} +{% tab 'Scala 3' for=compound-types_2 %} +```scala +def cloneAndReset(obj: ?): Cloneable = + val cloned = obj.clone() + obj.reset + cloned +``` +{% endtab %} +{% endtabs %} -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`. +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`. +{% tabs compound-types_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_3 %} +This compound type is written in Scala as `Cloneable with Resetable`. Here's the updated function: - -``` +```scala mdoc:fail def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //... } ``` +Note that you can have more than two types: `A with B with C with ...`. +This means the same as thing as `(...(A with B) with C) with ... )` +{% endtab %} +{% tab 'Scala 3' for=compound-types_3 %} +This intersection type is written in Scala as `Cloneable & Resetable`. -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 }` +Here's the updated function: +```scala +def cloneAndReset(obj: Cloneable & Resetable): Cloneable = { + //... +} +``` + +Note that you can have more than two types: `A & B & C & ...`. +And `&` is associative, so parentheses can be added around any part without changing the meaning. +{% endtab %} +{% endtabs %} -An example for the use of refinements is given on the page about [class composition with mixins](mixin-class-composition.html). + diff --git a/_tour/default-parameter-values.md b/_tour/default-parameter-values.md index 60776fd228..96b28f278a 100644 --- a/_tour/default-parameter-values.md +++ b/_tour/default-parameter-values.md @@ -13,29 +13,45 @@ redirect_from: "/tutorials/tour/default-parameter-values.html" Scala provides the ability to give parameters default values that can be used to allow a caller to omit those parameters. +{% tabs default-parameter-values-1 %} +{% tab 'Scala 2 and 3' for=default-parameter-values-1 %} ```scala mdoc 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 ``` +{% endtab %} +{% endtabs %} + 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. +{% tabs default-parameter-values-2 %} +{% tab 'Scala 2 and 3' for=default-parameter-values-2 %} ```scala mdoc class Point(val x: Double = 0, val y: Double = 0) val point1 = new Point(y = 1) ``` +{% endtab %} +{% endtabs %} + Here we have to say `y = 1`. Note that default parameters in Scala are not optional when called from Java code: +{% tabs default-parameter-values-3 %} +{% tab 'Scala 2 and 3' for=default-parameter-values-3 %} ```scala mdoc:reset // Point.scala class Point(val x: Double = 0, val y: Double = 0) ``` +{% endtab %} +{% endtabs %} +{% tabs default-parameter-values-4 %} +{% tab 'Java' for=default-parameter-values-4 %} ```java // Main.java public class Main { @@ -44,3 +60,30 @@ public class Main { } } ``` +{% endtab %} +{% endtabs %} + +### Default Parameters for Overloaded Methods + +Scala doesn't allow having two methods with default parameters and with the same name (overloaded). +An important reason why is to avoid the ambiguity that can be caused due to the existence of default parameters. To illustrate the problem, let's consider the method declarations provided below: + +{% tabs default-parameter-values-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala mdoc:fail +object A { + def func(x: Int = 34): Unit + def func(y: String = "abc"): Unit +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object A: + def func(x: Int = 34): Unit + def func(y: String = "abc"): Unit +``` +{% endtab %} +{% endtabs %} + +If we call `A.func()`, compiler cannot know whether the programmer intended to call `func(x: Int = 34)` or `func(y: String = "abc")`. diff --git a/_tour/extractor-objects.md b/_tour/extractor-objects.md index b7e08ed708..a859d6cdda 100644 --- a/_tour/extractor-objects.md +++ b/_tour/extractor-objects.md @@ -12,12 +12,15 @@ redirect_from: "/tutorials/tour/extractor-objects.html" 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. +{% tabs extractor-objects_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=extractor-objects_definition %} ```scala mdoc import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") @@ -31,28 +34,68 @@ customer1ID match { case _ => println("Could not extract a CustomerID") } ``` +{% endtab %} + +{% tab 'Scala 3' for=extractor-objects_definition %} +```scala +import scala.util.Random + +object CustomerID: + + def apply(name: String) = s"$name--${Random.nextLong()}" + + def unapply(customerID: String): Option[String] = + val stringArray: Array[String] = customerID.split("--") + if stringArray.tail.nonEmpty then Some(stringArray.head) else None + +val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 +customer1ID match + case CustomerID(name) => println(name) // prints Sukyoung + case _ => println("Could not extract a CustomerID") +``` +{% endtab %} + +{% endtabs %} 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) => println(name)`, we're calling the unapply method with `CustomerID.unapply(customer1ID)`. Since a value definition can use a pattern to introduce a new variable, an extractor can be used to initialize the variable, where the unapply method supplies the value. +{% tabs extractor-objects_use-case-1 %} + +{% tab 'Scala 2 and 3' for=extractor-objects_use-case-1 %} ```scala mdoc val customer2ID = CustomerID("Nico") val CustomerID(name) = customer2ID println(name) // prints Nico ``` +{% endtab %} + +{% endtabs %} This is equivalent to `val name = CustomerID.unapply(customer2ID).get`. +{% tabs extractor-objects_use-case-2 %} + +{% tab 'Scala 2 and 3' for=extractor-objects_use-case-2 %} ```scala mdoc val CustomerID(name2) = "--asdfasdfasdf" ``` +{% endtab %} + +{% endtabs %} If there is no match, a `scala.MatchError` is thrown: -```scala +{% tabs extractor-objects_use-case-3 %} + +{% tab 'Scala 2 and 3' for=extractor-objects_use-case-3 %} +```scala mdoc:crash val CustomerID(name3) = "-asdfasdfasdf" ``` +{% endtab %} + +{% endtabs %} The return type of an `unapply` should be chosen as follows: diff --git a/_tour/for-comprehensions.md b/_tour/for-comprehensions.md index ce9a8849c5..a97758d8b3 100644 --- a/_tour/for-comprehensions.md +++ b/_tour/for-comprehensions.md @@ -7,15 +7,18 @@ num: 19 next-page: generic-classes previous-page: extractor-objects -redirect_from: +redirect_from: - "/tutorials/tour/for-comprehensions.html" - "/tutorials/tour/sequence-comprehensions.html" --- -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. +Scala offers a lightweight notation for expressing _sequence comprehensions_. Comprehensions have the form `for (enumerators) yield e`, where `enumerators` refers to a list of enumerators. An _enumerator_ is either a generator, or it is a guard (see: [Control Structures](/scala3/book/control-structures.html#for-loops)). A comprehension evaluates the body `e` for each binding generated by the enumerators and returns a sequence of these values. Here's an example: +{% tabs for-comprehensions-01 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-01 %} + ```scala mdoc case class User(name: String, age: Int) @@ -32,23 +35,66 @@ val twentySomethings = twentySomethings.foreach(println) // prints Travis Dennis ``` +{% endtab %} +{% tab 'Scala 3' for=for-comprehensions-01 %} + +```scala +case class User(name: String, age: Int) + +val userBase = List( + User("Travis", 28), + User("Kelly", 33), + User("Jennifer", 44), + 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(println) // prints Travis Dennis +``` + +{% endtab %} +{% endtabs %} + A `for` loop with a `yield` statement returns a result, the container type of which is determined by the first generator. `user <- userBase` is a `List`, and because we said `yield user.name` where `user.name` is a `String`, the overall result is a `List[String]`. And `if user.age >=20 && user.age < 30` is a guard that filters out users who are not in their twenties. 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`: +{% tabs for-comprehensions-02 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-02 %} + ```scala mdoc def foo(n: Int, v: Int) = for (i <- 0 until n; j <- 0 until n if i + j == v) yield (i, j) -foo(10, 10) foreach { +foo(10, 10).foreach { case (i, j) => println(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1) } +``` +{% endtab %} +{% tab 'Scala 3' for=for-comprehensions-02 %} + +```scala +def foo(n: Int, v: Int) = + for i <- 0 until n + j <- 0 until n if i + j == v + yield (i, j) + +foo(10, 10).foreach { + (i, j) => println(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1) +} ``` + +{% endtab %} +{% endtabs %} + 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: + ```scala (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 0) ... ``` @@ -57,6 +103,9 @@ Note that comprehensions are not restricted to lists. Every datatype that suppor You can omit `yield` in a comprehension. In that case, comprehension will return `Unit`. This can be useful in case you need to perform side-effects. Here's a program equivalent to the previous one, but without using `yield`: +{% tabs for-comprehensions-03 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-comprehensions-03 %} + ```scala mdoc:nest def foo(n: Int, v: Int) = for (i <- 0 until n; @@ -66,6 +115,22 @@ def foo(n: Int, v: Int) = foo(10, 10) ``` +{% endtab %} + +{% tab 'Scala 3' for=for-comprehensions-03 %} + +```scala +def foo(n: Int, v: Int) = + for i <- 0 until n + j <- 0 until n if i + j == v + do println(s"($i, $j)") + +foo(10, 10) +``` + +{% endtab %} +{% endtabs %} + ## More resources -* Other examples of "For comprehension" in the [Scala Book](/overviews/scala-book/for-expressions.html) +- Other examples of "For comprehension" in the [Scala Book](/scala3/book/control-structures.html#for-expressions) diff --git a/_tour/generic-classes.md b/_tour/generic-classes.md index d883e88115..5dbb8990d8 100644 --- a/_tour/generic-classes.md +++ b/_tour/generic-classes.md @@ -10,10 +10,14 @@ assumed-knowledge: classes unified-types redirect_from: "/tutorials/tour/generic-classes.html" --- + 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. + +{% tabs generic-classes-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-1 %} ```scala mdoc class Stack[A] { private var elements: List[A] = Nil @@ -27,6 +31,22 @@ class Stack[A] { } } ``` +{% endtab %} +{% tab 'Scala 3' for=generic-classes-1 %} +```scala +class Stack[A]: + private var elements: List[A] = Nil + def push(x: A): Unit = + elements = x :: elements + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` +{% endtab %} +{% endtabs %} + 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`). `Nil` here is an empty `List` and is not to be confused with `null`. @@ -34,15 +54,33 @@ This implementation of a `Stack` class takes any type `A` as a parameter. This m ## Usage To use a generic class, put the type in the square brackets in place of `A`. -``` + +{% tabs generic-classes-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-2 %} +```scala mdoc 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: +{% endtab %} +{% tab 'Scala 3' for=generic-classes-2 %} +```scala +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 ``` +{% endtab %} +{% endtabs %} + +The instance `stack` can only take Ints. However, if the type argument had subtypes, those could be passed in: + +{% tabs generic-classes-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=generic-classes-3 %} +```scala mdoc:nest class Fruit class Apple extends Fruit class Banana extends Fruit @@ -54,6 +92,23 @@ val banana = new Banana stack.push(apple) stack.push(banana) ``` +{% endtab %} +{% tab 'Scala 3' for=generic-classes-3 %} +```scala +class Fruit +class Apple extends Fruit +class Banana extends Fruit + +val stack = Stack[Fruit] +val apple = Apple() +val banana = Banana() + +stack.push(apple) +stack.push(banana) +``` +{% endtab %} +{% endtabs %} + 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 index f60f910b8f..7dc08ea005 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -16,31 +16,54 @@ The terminology can get a bit confusing at this point, and we use the phrase "higher order function" for both methods and functions that take functions as parameters or that return a function. -In a pure Object Oriented world a good practice is to avoid exposing methods parameterized with functions that might leak object's internal state. Leaking internal state might break the invariants of the object itself thus violating encapsulation. +In a pure Object Oriented world, a good practice is to avoid exposing methods parameterised with functions that might leak an object's internal state. Leaking internal state might break the invariants of the object itself, thus violating encapsulation. One of the most common examples is the higher-order function `map` which is available for collections in Scala. -```scala mdoc -val salaries = Seq(20000, 70000, 40000) + +{% tabs map_example_1 %} + +{% tab 'Scala 2 and 3' for=map_example_1 %} +```scala mdoc:nest +val salaries = Seq(20_000, 70_000, 40_000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) ``` +{% endtab %} + +{% endtabs %} + `doubleSalary` is a function which takes a single Int, `x`, and returns `x * 2`. In general, the tuple on the left of the arrow `=>` is a parameter list and the value of the expression on the right is what gets returned. On line 3, the function `doubleSalary` gets applied to each element in the list of salaries. To shrink the code, we could make the function anonymous and pass it directly as an argument to map: -```scala:nest -val salaries = Seq(20000, 70000, 40000) + +{% tabs map_example_2 %} + +{% tab 'Scala 2 and 3' for=map_example_2 %} +```scala mdoc:nest +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) ``` +{% endtab %} + +{% endtabs %} + Notice how `x` is not declared as an Int in the above example. That's because the -compiler can infer the type based on the type of function map expects (see [Currying](/tour/multiple-parameter-lists.html)). An even more idiomatic way to write the same piece of code would be: +compiler can infer the type based on the type of function `map` expects (see [Currying](/tour/multiple-parameter-lists.html)). An even more idiomatic way to write the same piece of code would be: + +{% tabs map_example_3 %} +{% tab 'Scala 2 and 3' for=map_example_3 %} ```scala mdoc:nest -val salaries = Seq(20000, 70000, 40000) +val salaries = Seq(20_000, 70_000, 40_000) val newSalaries = salaries.map(_ * 2) ``` +{% endtab %} + +{% endtabs %} + Since the Scala compiler already knows the type of the parameters (a single Int), you just need to provide the right side of the function. The only caveat is that you need to use `_` in place of a parameter name (it was `x` in @@ -49,6 +72,10 @@ the previous example). ## Coercing methods into functions It is also possible to pass methods as arguments to higher-order functions because the Scala compiler will coerce the method into a function. + +{% tabs Coercing_methods_into_functions class=tabs-scala-version %} + +{% tab 'Scala 2' for=Coercing_methods_into_functions %} ```scala mdoc case class WeeklyWeatherForecast(temperatures: Seq[Double]) { @@ -57,6 +84,20 @@ case class WeeklyWeatherForecast(temperatures: Seq[Double]) { def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF } ``` +{% endtab %} + +{% tab 'Scala 3' for=Coercing_methods_into_functions %} +```scala +case class WeeklyWeatherForecast(temperatures: Seq[Double]): + + private def convertCtoF(temp: Double) = temp * 1.8 + 32 + + def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF +``` +{% endtab %} + +{% endtabs %} + Here the method `convertCtoF` is passed to the higher order function `map`. This is possible because the compiler coerces `convertCtoF` to the function `x => convertCtoF(x)` (note: `x` will be a generated name which is guaranteed to be unique within its scope). @@ -64,6 +105,9 @@ Here the method `convertCtoF` is passed to the higher order function `map`. This One reason to use higher-order functions is to reduce redundant code. Let's say you wanted some methods that could raise someone's salaries by various factors. Without creating a higher-order function, it might look something like this: +{% tabs Functions_that_accept_functions_1 class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_1 %} ```scala mdoc object SalaryRaiser { @@ -77,10 +121,31 @@ object SalaryRaiser { salaries.map(salary => salary * salary) } ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_accept_functions_1 %} +```scala +object SalaryRaiser: + + def smallPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * 1.1) + + def greatPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * salary) +``` +{% endtab %} + +{% endtabs %} Notice how each of the three methods vary only by the multiplication factor. To simplify, you can extract the repeated code into a higher-order function like so: +{% tabs Functions_that_accept_functions_2 class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_accept_functions_2 %} ```scala mdoc:nest object SalaryRaiser { @@ -97,17 +162,41 @@ object SalaryRaiser { promotion(salaries, salary => salary * salary) } ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_accept_functions_2 %} +```scala +object SalaryRaiser: + + private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] = + salaries.map(promotionFunction) + + def smallPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * 1.1) + + def greatPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * salary) +``` +{% endtab %} + +{% endtabs %} The new method, `promotion`, takes the salaries plus a function of type `Double => Double` (i.e. a function that takes a Double and returns a Double) and returns the product. -Methods and functions usually express behaviours or data transformations, therefore having functions that compose based on other functions can help building generic mechanisms. Those generic operations defer to lock down the entire operation behaviour giving clients a way to control or further customize parts of the operation itself. +Methods and functions usually express behaviours or data transformations. Therefore, having functions that compose based on other functions can allow us to build more generic mechanisms. Such generic operations avoid completely locking down their behaviour in order to give clients a way to control or further customize parts of those operations. ## Functions that return functions There are certain cases where you want to generate a function. Here's an example of a method that returns a function. +{% tabs Functions_that_return_functions class=tabs-scala-version %} + +{% tab 'Scala 2' for=Functions_that_return_functions %} ```scala mdoc def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = { val schema = if (ssl) "https://" else "http://" @@ -120,6 +209,23 @@ val endpoint = "users" val query = "id=1" val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String ``` +{% endtab %} + +{% tab 'Scala 3' for=Functions_that_return_functions %} +```scala +def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = + val schema = if ssl then "https://" else "http://" + (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query" + +val domainName = "www.example.com" +def getURL = urlBuilder(ssl=true, domainName) +val endpoint = "users" +val query = "id=1" +val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String +``` +{% endtab %} + +{% endtabs %} Notice the return type of urlBuilder `(String, String) => String`. This means that the returned anonymous function takes two Strings and returns a String. In this case, diff --git a/_tour/implicit-conversions.md b/_tour/implicit-conversions.md index 4915f51daf..7c0b5840e4 100644 --- a/_tour/implicit-conversions.md +++ b/_tour/implicit-conversions.md @@ -10,51 +10,34 @@ previous-page: implicit-parameters redirect_from: "/tutorials/tour/implicit-conversions.html" --- -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 a powerful Scala feature that enable two common use cases: +- allow users to supply an argument of one type, as if it were another type, to avoid boilerplate. +- in Scala 2, to provide additional members to closed classes (replaced by [extension methods][exts] in Scala 3). + +### Detailed Explanation +{% tabs implicit-conversion-defn class=tabs-scala-version %} +{% tab 'Scala 2' %} +In Scala 2, an implicit conversion from type `S` to type `T` is defined by either an [implicit class]({% link _overviews/core/implicit-classes.md %}) `T` that has a single parameter of type `S`, an [implicit value]({% link _tour/implicit-parameters.md %}) which has function type `S => T`, or by an implicit method convertible to a value of that type. +{% endtab %} +{% tab 'Scala 3' %} +In Scala 3, an implicit conversion from type `S` to type `T` is defined by a [given instance]({% link _tour/implicit-parameters.md %}) which has type `scala.Conversion[S, T]`. For compatibility with Scala 2, they can also be defined by an implicit method (read more in the Scala 2 tab). +{% endtab %} +{% endtabs %} 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`. +1. If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`. +2. 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`. +In the first case, a conversion `c` is searched for, which is applicable to `e` and whose result type conforms to `T`. -If an implicit method `List[A] => Ordered[List[A]]` is in scope, as well as an implicit method `Int => Ordered[Int]`, the following operation on the two lists of type `List[Int]` is legal: +An example is to pass a `scala.Int`, e.g. `x`, to a method that expects `scala.Long`. In this case, the implicit conversion `Int.int2long(x)` is inserted. -``` -List(1, 2, 3) <= List(4, 5) -``` -An implicit method `Int => Ordered[Int]` is provided automatically through `scala.Predef.intWrapper`. An example of an implicit method `List[A] => Ordered[List[A]]` is provided below. +In the second case, a conversion `c` is searched for, which is applicable to `e` and whose result contains a member named `m`. -```scala mdoc -import scala.language.implicitConversions +An example is to compare two strings `"foo" < "bar"`. In this case, `String` has no member `<`, so the implicit conversion `Predef.augmentString("foo") < "bar"` is inserted. (`scala.Predef` is automatically imported into all Scala programs.) -implicit def list2ordered[A](x: List[A]) - (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] = - new Ordered[List[A]] { - //replace with a more useful implementation - def compare(that: List[A]): Int = 1 - } -``` +Further reading: [Implicit Conversions (in the Scala book)]({% link _overviews/scala3-book/ca-implicit-conversions.md %}). -The implicitly imported object `scala.Predef` declares several aliases to frequently used types (e.g. `scala.collection.immutable.Map` is aliased to `Map`) 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: - -```scala mdoc -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. +[exts]: {% link _overviews/scala3-book/ca-extension-methods.md %} diff --git a/_tour/implicit-parameters.md b/_tour/implicit-parameters.md index e67d992d79..4d5977f242 100644 --- a/_tour/implicit-parameters.md +++ b/_tour/implicit-parameters.md @@ -1,6 +1,6 @@ --- layout: tour -title: Implicit Parameters +title: Contextual Parameters, aka Implicit Parameters partof: scala-tour num: 28 @@ -10,61 +10,93 @@ previous-page: self-types redirect_from: "/tutorials/tour/implicit-parameters.html" --- -A method can have an _implicit_ parameter list, marked by the _implicit_ keyword at the start of the parameter list. If the parameters in that parameter list are not passed as usual, Scala will look if it can get an implicit value of the correct type, and if it can, pass it automatically. +A method can have *contextual parameters*, also called *implicit parameters*, or more concisely *implicits*. +Parameter lists starting with the keyword `using` (or `implicit` in Scala 2) mark contextual parameters. +Unless the call site explicitly provides arguments for those parameters, Scala will look for implicitly available `given` (or `implicit` in Scala 2) values of the correct type. +If it can find appropriate values, it automatically passes them. -The places Scala will look for these parameters fall into two categories: +This is best shown using a small example first. +We define an interface `Comparator[A]` that can compare elements of type `A`, and provide two implementations, for `Int`s and `String`s. +We then define a method `max[A](x: A, y: A)` that returns the greater of the two arguments. +Since `x` and `y` are generically typed, in general we do not know how to compare them, but we can ask for an appropriate comparator. +As there is typically a canonical comparator for any given type `A`, we can declare them as *given*s, or *implicitly* available. -* Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called. -* Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type. - -A more detailed guide to where Scala looks for implicits can be found in [the FAQ](//docs.scala-lang.org/tutorials/FAQ/finding-implicits.html). - -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 cannot be top-level. +{% tabs implicits-comparator class=tabs-scala-version %} +{% tab 'Scala 2' for=implicits-comparator %} ```scala mdoc -abstract class Monoid[A] { - def add(x: A, y: A): A - def unit: A +trait Comparator[A] { + def compare(x: A, y: A): Int } -object ImplicitTest { - implicit val stringMonoid: Monoid[String] = new Monoid[String] { - def add(x: String, y: String): String = x concat y - def unit: String = "" +object Comparator { + implicit object IntComparator extends Comparator[Int] { + def compare(x: Int, y: Int): Int = Integer.compare(x, y) } - - implicit val intMonoid: Monoid[Int] = new 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)) - - def main(args: Array[String]): Unit = { - println(sum(List(1, 2, 3))) // uses intMonoid implicitly - println(sum(List("a", "b", "c"))) // uses stringMonoid implicitly + + implicit object StringComparator extends Comparator[String] { + def compare(x: String, y: String): Int = x.compareTo(y) } } -``` -`Monoid` defines an operation called `add` here, that combines a pair of `A`s and returns another `A`, together with an operation called `unit` that is able to create some (specific) `A`. +def max[A](x: A, y: A)(implicit comparator: Comparator[A]): A = + if (comparator.compare(x, y) >= 0) x + else y + +println(max(10, 6)) // 10 +println(max("hello", "world")) // world +``` -To show how implicit parameters work, we first define monoids `stringMonoid` and `intMonoid` for strings and integers, respectively. The `implicit` keyword indicates that the corresponding object can be used implicitly. +```scala mdoc:fail +// does not compile: +println(max(false, true)) +// ^ +// error: could not find implicit value for parameter comparator: Comparator[Boolean] +``` -The method `sum` takes a `List[A]` and returns an `A`, which takes the initial `A` from `unit`, and combines each next `A` in the list to that with the `add` method. Making the parameter `m` implicit here means we only have to provide the `xs` parameter when we call the method if Scala can find an implicit `Monoid[A]` to use for the implicit `m` parameter. +The `comparator` parameter is automatically filled in with `Comparator.IntComparator` for `max(10, 6)`, and with `Comparator.StringComparator` for `max("hello", "world")`. +Since no implicit `Comparator[Boolean]` can be found, the call `max(false, true)` fails to compile. +{% endtab %} -In our `main` method we call `sum` twice, and only provide the `xs` parameter. Scala will now look for an implicit in the scope mentioned above. The first call to `sum` passes a `List[Int]` for `xs`, which means that `A` is `Int`. The implicit parameter list with `m` is left out, so Scala will look for an implicit of type `Monoid[Int]`. The first lookup rule reads +{% tab 'Scala 3' for=implicits-comparator %} +```scala +trait Comparator[A]: + def compare(x: A, y: A): Int -> Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called. +object Comparator: + given Comparator[Int] with + def compare(x: Int, y: Int): Int = Integer.compare(x, y) -`intMonoid` is an implicit definition that can be accessed directly in `main`. It is also of the correct type, so it's passed to the `sum` method automatically. + given Comparator[String] with + def compare(x: String, y: String): Int = x.compareTo(y) +end Comparator -The second call to `sum` passes a `List[String]`, which means that `A` is `String`. Implicit lookup will go the same way as with `Int`, but will this time find `stringMonoid`, and pass that automatically as `m`. +def max[A](x: A, y: A)(using comparator: Comparator[A]): A = + if comparator.compare(x, y) >= 0 then x + else y -The program will output +println(max(10, 6)) // 10 +println(max("hello", "world")) // world ``` -6 -abc + +```scala +// does not compile: +println(max(false, true)) +-- Error: ---------------------------------------------------------------------- +1 |println(max(false, true)) + | ^ + |no given instance of type Comparator[Boolean] was found for parameter comparator of method max ``` + +The `comparator` parameter is automatically filled in with the `given Comparator[Int]` for `max(10, 6)`, and with the `given Comparator[String]` for `max("hello", "world")`. +Since no `given Comparator[Boolean]` can be found, the call `max(false, true)` fails to compile. +{% endtab %} + +{% endtabs %} + +Scala will look for available given values in two places: + +* Scala will first look for given definitions and using parameters that can be accessed directly (without a prefix) at the call site of `max`. +* Then it looks for members marked `given`/`implicit` in the companion objects associated with the implicit candidate type (for example: `object Comparator` for the candidate type `Comparator[Int]`). + +A more detailed guide to where Scala looks for implicits can be found in [the FAQ](/tutorials/FAQ/finding-implicits.html). diff --git a/_tour/inner-classes.md b/_tour/inner-classes.md index fc9e4f150a..e2d94542b4 100644 --- a/_tour/inner-classes.md +++ b/_tour/inner-classes.md @@ -14,6 +14,8 @@ In Scala it is possible to let classes have other classes as members. As opposed To illustrate the difference, we quickly sketch the implementation of a graph datatype: +{% tabs inner-classes_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_1 %} ```scala mdoc class Graph { class Node { @@ -32,8 +34,29 @@ class Graph { } } ``` +{% endtab %} +{% tab 'Scala 3' for=inner-classes_1 %} +```scala +class Graph: + class Node: + var connectedNodes: List[Node] = Nil + def connectTo(node: Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` +{% endtab %} +{% endtabs %} + 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`. +{% tabs inner-classes_2 %} +{% tab 'Scala 2 and 3' for=inner-classes_2 %} ```scala mdoc val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -42,11 +65,16 @@ val node3: graph1.Node = graph1.newNode node1.connectTo(node2) node3.connectTo(node1) ``` +{% endtab %} +{% endtabs %} + 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: +{% tabs inner-classes_3 %} +{% tab 'Scala 2 and 3' for=inner-classes_3 %} ```scala mdoc:fail val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode @@ -56,8 +84,13 @@ val graph2: Graph = new Graph val node3: graph2.Node = graph2.newNode node1.connectTo(node3) // illegal! ``` +{% endtab %} +{% endtabs %} + 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: +{% tabs inner-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=inner-classes_4 %} ```scala mdoc:nest class Graph { class Node { @@ -76,3 +109,21 @@ class Graph { } } ``` +{% endtab %} +{% tab 'Scala 3' for=inner-classes_4 %} +```scala +class Graph: + class Node: + var connectedNodes: List[Graph#Node] = Nil + def connectTo(node: Graph#Node): Unit = + if !connectedNodes.exists(node.equals) then + connectedNodes = node :: connectedNodes + + var nodes: List[Node] = Nil + def newNode: Node = + val res = Node() + nodes = res :: nodes + res +``` +{% endtab %} +{% endtabs %} diff --git a/_tour/lower-type-bounds.md b/_tour/lower-type-bounds.md index f4191b425b..bac1883358 100644 --- a/_tour/lower-type-bounds.md +++ b/_tour/lower-type-bounds.md @@ -15,6 +15,8 @@ While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of a Here is an example where this is useful: +{% tabs upper-type-bounds_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_1 %} ```scala mdoc:fail trait List[+A] { def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) @@ -24,6 +26,18 @@ case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] object Nil extends List[Nothing] ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_1 %} +```scala +trait List[+A]: + def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] +``` +{% endtab %} +{% endtabs %} This program implements a singly-linked list. `Nil` represents an empty list with no elements. `class NonEmptyList` is a node which contains an element of type `A` (`head`) and a reference to the rest of the list (`tail`). The `trait List` and its subtypes are covariant because we have `+A`. @@ -31,6 +45,8 @@ However, this program does _not_ compile because the parameter `elem` in `prepen 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 `B` that has `A` as a lower type bound. +{% tabs upper-type-bounds_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_2 %} ```scala mdoc trait List[+A] { def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) @@ -40,7 +56,23 @@ case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] object Nil extends List[Nothing] ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_2 %} +```scala +trait List[+A]: + def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] +``` +{% endtab %} +{% endtabs %} + Now we can do the following: + +{% tabs upper-type-bounds_3 %} +{% tab 'Scala 2 and 3' for=upper-type-bounds_3 %} ```scala mdoc trait Bird case class AfricanSwallow() extends Bird @@ -65,6 +97,9 @@ val allBirds = africanSwallows.prepend(EuropeanSwallow()) // but this is a mistake! adding a list of birds widens the type arg too much. -Xlint will warn! val error = moreBirds.prepend(swallowsFromAntarctica) // List[Object] ``` +{% endtab %} +{% endtabs %} + The covariant type parameter allows `birds` to get the value of `africanSwallows`. The type bound on the type parameter for `prepend` allows adding different varieties of swallows and getting a wider type: instead of `List[AfricanSwallow]`, we get a `List[Bird]`. diff --git a/_tour/mixin-class-composition.md b/_tour/mixin-class-composition.md index fc362b2643..27eeafbf61 100644 --- a/_tour/mixin-class-composition.md +++ b/_tour/mixin-class-composition.md @@ -12,6 +12,9 @@ redirect_from: "/tutorials/tour/mixin-class-composition.html" --- Mixins are traits which are used to compose a class. +{% tabs mixin-first-exemple class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-first-exemple %} ```scala mdoc abstract class A { val message: String @@ -30,8 +33,33 @@ println(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. +{% endtab %} + +{% tab 'Scala 3' for=mixin-first-exemple %} +```scala +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, C + +val d = D() +println(d.message) // I'm an instance of class B +println(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 keyword `extends` and the separator `,` respectively). The mixins and the superclass may have the same supertype. + +{% endtab %} + +{% endtabs %} + Now let's look at a more interesting example starting with an abstract class: +{% tabs mixin-abstract-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-abstract-iterator %} ```scala mdoc abstract class AbsIterator { type T @@ -39,10 +67,26 @@ abstract class AbsIterator { def next(): T } ``` +{% endtab %} + +{% tab 'Scala 3' for=mixin-abstract-iterator %} +```scala +abstract class AbsIterator: + type T + def hasNext: Boolean + def next(): T +``` +{% endtab %} + +{% endtabs %} + 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): +{% tabs mixin-concrete-string-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-concrete-string-iterator %} ```scala mdoc class StringIterator(s: String) extends AbsIterator { type T = Char @@ -55,10 +99,30 @@ class StringIterator(s: String) extends AbsIterator { } } ``` +{% endtab %} + +{% tab 'Scala 3' for=mixin-concrete-string-iterator %} +```scala +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 +``` +{% endtab %} + +{% endtabs %} + `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`. +{% tabs mixin-extended-abstract-iterator class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-extended-abstract-iterator %} ```scala mdoc trait RichIterator extends AbsIterator { def foreach(f: T => Unit): Unit = while (hasNext) f(next()) @@ -66,13 +130,41 @@ trait RichIterator extends AbsIterator { ``` This trait implements `foreach` by continually calling the provided function `f: T => Unit` on the next element (`next()`) as long as there are further elements (`while (hasNext)`). Because `RichIterator` is a trait, it doesn't need to implement the abstract members of AbsIterator. +{% endtab %} + +{% tab 'Scala 3' for=mixin-extended-abstract-iterator %} +```scala +trait RichIterator extends AbsIterator: + def foreach(f: T => Unit): Unit = while hasNext do f(next()) +``` +This trait implements `foreach` by continually calling the provided function `f: T => Unit` on the next element (`next()`) as long as there are further elements (`while hasNext`). Because `RichIterator` is a trait, it doesn't need to implement the abstract members of AbsIterator. + +{% endtab %} + +{% endtabs %} + We would like to combine the functionality of `StringIterator` and `RichIterator` into a single class. +{% tabs mixin-combination-class class=tabs-scala-version %} + +{% tab 'Scala 2' for=mixin-combination-class %} ```scala mdoc class RichStringIter extends StringIterator("Scala") with RichIterator val richStringIter = new RichStringIter richStringIter.foreach(println) ``` +{% endtab %} + +{% tab 'Scala 3' for=mixin-combination-class %} +```scala +class RichStringIter extends StringIterator("Scala"), RichIterator +val richStringIter = RichStringIter() +richStringIter.foreach(println) +``` +{% endtab %} + +{% endtabs %} + 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/multiple-parameter-lists.md b/_tour/multiple-parameter-lists.md index 9aa3add880..324795b6c0 100644 --- a/_tour/multiple-parameter-lists.md +++ b/_tour/multiple-parameter-lists.md @@ -1,6 +1,6 @@ --- layout: tour -title: Multiple Parameter Lists (Currying) +title: Multiple Parameter Lists partof: scala-tour num: 12 @@ -16,6 +16,9 @@ Methods may have multiple parameter lists. Here is an example, as defined on the `Iterable` trait in Scala's collections API: +{% tabs foldLeft_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=foldLeft_definition %} ```scala trait Iterable[A] { ... @@ -23,18 +26,34 @@ trait Iterable[A] { ... } ``` +{% endtab %} + +{% tab 'Scala 3' for=foldLeft_definition %} +```scala +trait Iterable[A]: + ... + def foldLeft[B](z: B)(op: (B, A) => B): B + ... +``` +{% endtab %} + +{% endtabs %} `foldLeft` applies a two-parameter function `op` to an initial value `z` and all elements of this collection, going left to right. Shown below is an example of its usage. Starting with an initial value of 0, `foldLeft` here applies the function `(m, n) => m + n` to each element in the List and the previous accumulated value. -{% scalafiddle %} +{% tabs foldLeft_use %} + +{% tab 'Scala 2 and 3' for=foldLeft_use %} ```scala mdoc val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(0)((m, n) => m + n) println(res) // 55 ``` -{% endscalafiddle %} +{% endtab %} + +{% endtabs %} ### Use cases @@ -45,29 +64,53 @@ Suggested use cases for multiple parameter lists include: It so happens that in Scala, type inference proceeds one parameter list at a time. Say you have the following method: +{% tabs foldLeft1_definition %} + +{% tab 'Scala 2 and 3' for=foldLeft1_definition %} ```scala mdoc def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ??? ``` +{% endtab %} + +{% endtabs %} Then you'd like to call it in the following way, but will find that it doesn't compile: +{% tabs foldLeft1_wrong_use %} + +{% tab 'Scala 2 and 3' for=foldLeft1_wrong_use %} ```scala mdoc:fail def notPossible = foldLeft1(numbers, 0, _ + _) ``` +{% endtab %} + +{% endtabs %} you will have to call it like one of the below ways: +{% tabs foldLeft1_good_use %} + +{% tab 'Scala 2 and 3' for=foldLeft1_good_use %} ```scala mdoc def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _) def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b) ``` +{% endtab %} + +{% endtabs %} That's because Scala won't be able to infer the type of the function `_ + _`, as it's still inferring `A` and `B`. By moving the parameter `op` to its own parameter list, `A` and `B` are inferred in the first parameter list. These inferred types will then be available to the second parameter list and `_ + _` will match the inferred type `(Int, Int) => Int` +{% tabs foldLeft2_definition_and_use %} + +{% tab 'Scala 2 and 3' for=foldLeft2_definition_and_use %} ```scala mdoc def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ??? def possible = foldLeft2(numbers, 0)(_ + _) ``` +{% endtab %} + +{% endtabs %} This definition doesn't need any type hints and can infer all of its type parameters. @@ -78,9 +121,21 @@ To specify only certain parameters as [`implicit`](https://docs.scala-lang.org/t An example of this is: +{% tabs execute_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=execute_definition %} ```scala mdoc def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ??? ``` +{% endtab %} + +{% tab 'Scala 3' for=execute_definition %} +```scala +def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ??? +``` +{% endtab %} + +{% endtabs %} #### Partial application @@ -88,6 +143,9 @@ When a method is called with a fewer number of parameter lists, then this will y For example, +{% tabs foldLeft_partial class=tabs-scala-version %} + +{% tab 'Scala 2' for=foldLeft_partial %} ```scala mdoc:nest val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val numberFunc = numbers.foldLeft(List[Int]()) _ @@ -98,3 +156,63 @@ println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) val cubes = numberFunc((xs, x) => xs :+ x*x*x) println(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) ``` +{% endtab %} + +{% tab 'Scala 3' for=foldLeft_partial %} +```scala +val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +val numberFunc = numbers.foldLeft(List[Int]()) + +val squares = numberFunc((xs, x) => xs :+ x*x) +println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) + +val cubes = numberFunc((xs, x) => xs :+ x*x*x) +println(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +``` +{% endtab %} + +{% endtabs %} + +### Comparison with "currying" + +You may sometimes see a method with multiple parameter lists referred to as "curried". + +As the [Wikipedia article on currying](https://en.wikipedia.org/wiki/Currying) states, + +> Currying is the technique of converting a function that takes +> multiple arguments into a sequence of functions that each takes a +> single argument + +We discourage the use of the word "curry" in reference to Scala's multiple parameter lists, for two reasons: + +1) In Scala, multiple parameters and multiple parameter lists are +specified and implemented directly, as part of the language, rather +being derived from single-parameter functions. + +2) There is danger of confusion with the Scala standard library's +[`curried`](https://www.scala-lang.org/api/current/scala/Function2.html#curried:T1=%3E(T2=%3ER)) +and [`uncurried`](https://www.scala-lang.org/api/current/scala/Function$.html#uncurried[T1,T2,R](f:T1=%3E(T2=%3ER)):(T1,T2)=%3ER) methods, which don't involve multiple parameter lists at all. + +Regardless, there are certainly similarities to be found between +multiple parameter lists and currying. Though they are different at +the definition site, the call site might nonetheless look identical, +as in this example: + +{% tabs about_currying %} + +{% tab 'Scala 2 and 3' for=about_currying %} +```scala mdoc +// version with multiple parameter lists +def addMultiple(n1: Int)(n2: Int) = n1 + n2 +// two different ways of arriving at a curried version instead +def add(n1: Int, n2: Int) = n1 + n2 +val addCurried1 = (add _).curried +val addCurried2 = (n1: Int) => (n2: Int) => n1 + n2 +// regardless, all three call sites are identical +addMultiple(3)(4) // 7 +addCurried1(3)(4) // 7 +addCurried2(3)(4) // 7 +``` +{% endtab %} + +{% endtabs %} diff --git a/_tour/named-arguments.md b/_tour/named-arguments.md index af82d72844..cec14a5ebf 100644 --- a/_tour/named-arguments.md +++ b/_tour/named-arguments.md @@ -8,26 +8,50 @@ next-page: traits previous-page: default-parameter-values prerequisite-knowledge: function-syntax -redirect_from: +redirect_from: - "/tutorials/tour/named-arguments.html" - "/tutorials/tour/named-parameters.html" --- When calling methods, you can label the arguments with their parameter names like so: +{% tabs named-arguments-when-good %} + +{% tab 'Scala 2 and 3' for=named-arguments-when-good %} ```scala mdoc -def printName(first: String, last: String): Unit = { - println(first + " " + last) -} +def printName(first: String, last: String): Unit = + println(s"$first $last") -printName("John", "Smith") // Prints "John Smith" -printName(first = "John", last = "Smith") // Prints "John Smith" -printName(last = "Smith", first = "John") // Prints "John Smith" +printName("John", "Public") // Prints "John Public" +printName(first = "John", last = "Public") // Prints "John Public" +printName(last = "Public", first = "John") // Prints "John Public" +printName("Elton", last = "John") // Prints "Elton John" ``` -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. +{% endtab %} + +{% endtabs %} + +This is useful when two parameters have the same type and the arguments could be accidentally swapped. + +Notice that named arguments can be written in any order. However, once the arguments are not in parameter order, reading from left to right, then the rest of the arguments must be named. +In the following example, named arguments enable the middle parameter to be omitted. But in the error case, the first argument is out of order, so the second argument must be named. + +{% tabs named-arguments-when-error %} + +{% tab 'Scala 2 and 3' for=named-arguments-when-error %} ```scala mdoc:fail -printName(last = "Smith", "john") // error: positional after named argument +def printFullName(first: String, middle: String = "Q.", last: String): Unit = + println(s"$first $middle $last") + +printFullName(first = "John", last = "Public") // Prints "John Q. Public" +printFullName("John", last = "Public") // Prints "John Q. Public" +printFullName("John", middle = "Quincy", "Public") // Prints "John Quincy Public" +printFullName(last = "Public", first = "John") // Prints "John Q. Public" +printFullName(last = "Public", "John") // error: positional after named argument ``` +{% endtab %} + +{% endtabs %} -Named arguments work with calls to Java methods, but only if the Java library in question was compiled with `-parameters`. +Named arguments work with calls to Java methods, but only if the Java library in question was compiled with the `-parameters` flag. diff --git a/_tour/nested-functions.md b/_tour/nested-functions.md index a250fc2238..45d00e2db6 100644 --- a/_tour/nested-functions.md +++ b/_tour/nested-functions.md @@ -12,24 +12,48 @@ redirect_from: "/tutorials/tour/nested-functions.html" In Scala it is possible to nest method definitions. The following object provides a `factorial` method for computing the factorial of a given number: -{% scalafiddle %} +{% tabs Nested_functions_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=Nested_functions_definition %} ```scala mdoc - 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)) +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)) ``` -{% endscalafiddle %} +{% endtab %} + +{% tab 'Scala 3' for=Nested_functions_definition %} +```scala +def factorial(x: Int): Int = + def fact(x: Int, accumulator: Int): Int = + if x <= 1 then accumulator + else fact(x - 1, x * accumulator) + fact(x, 1) + +println("Factorial of 2: " + factorial(2)) +println("Factorial of 3: " + factorial(3)) + +``` +{% endtab %} + +{% endtabs %} The output of this program is: +{% tabs Nested_functions_result %} + +{% tab 'Scala 2 and 3' for=Nested_functions_result %} ``` Factorial of 2: 2 Factorial of 3: 6 ``` +{% endtab %} + +{% endtabs %} diff --git a/_tour/operators.md b/_tour/operators.md index 651c34ea4d..fb157ce334 100644 --- a/_tour/operators.md +++ b/_tour/operators.md @@ -11,17 +11,30 @@ prerequisite-knowledge: case-classes redirect_from: "/tutorials/tour/operators.html" --- 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: + +{% tabs operators_1 %} +{% tab 'Scala 2 and 3' for=operators_1 %} ``` 10.+(1) ``` +{% endtab %} +{% endtabs %} However, it's easier to read as an infix operator: + +{% tabs operators_2 %} +{% tab 'Scala 2 and 3' for=operators_2 %} ``` 10 + 1 ``` +{% endtab %} +{% endtabs %} ## Defining and using operators You can use any legal identifier as an operator. This includes a name like `add` or a symbol(s) like `+`. + +{% tabs operators_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_3 %} ```scala mdoc case class Vec(x: Double, y: Double) { def +(that: Vec) = Vec(this.x + that.x, this.y + that.y) @@ -34,8 +47,26 @@ val vector3 = vector1 + vector2 vector3.x // 3.0 vector3.y // 3.0 ``` +{% endtab %} +{% tab 'Scala 3' for=operators_3 %} +```scala +case class Vec(x: Double, y: Double): + def +(that: Vec) = 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 +``` +{% endtab %} +{% endtabs %} + 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`: +{% tabs operators_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_4 %} ```scala mdoc case class MyBool(x: Boolean) { def and(that: MyBool): MyBool = if (x) that else this @@ -43,13 +74,27 @@ case class MyBool(x: Boolean) { def negate: MyBool = MyBool(!x) } ``` +{% endtab %} +{% tab 'Scala 3' for=operators_4 %} +```scala +case class MyBool(x: Boolean): + def and(that: MyBool): MyBool = if x then that else this + def or(that: MyBool): MyBool = if x then this else that + def negate: MyBool = MyBool(!x) +``` +{% endtab %} +{% endtabs %} It is now possible to use `and` and `or` as infix operators: +{% tabs operators_5 %} +{% tab 'Scala 2 and 3' for=operators_5 %} ```scala mdoc def not(x: MyBool) = x.negate def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) ``` +{% endtab %} +{% endtabs %} This helps to make the definition of `xor` more readable. @@ -60,19 +105,31 @@ When an expression uses multiple operators, the operators are evaluated based on * / % + - : -= ! < > += ! & ^ | (all letters, $, _) ``` This applies to functions you define. For example, the following expression: + +{% tabs operators_7 %} +{% tab 'Scala 2 and 3' for=operators_7 %} ``` a + b ^? c ?^ d less a ==> b | c ``` +{% endtab %} +{% endtabs %} + Is equivalent to + +{% tabs operators_8 %} +{% tab 'Scala 2 and 3' for=operators_8 %} ``` ((a + b) ^? (c ?^ d)) less ((a ==> b) | c) ``` +{% endtab %} +{% endtabs %} + `?^` has the highest precedence because it starts with the character `?`. `+` has the second highest precedence, followed by `==>`, `^?`, `|`, and `less`. diff --git a/_tour/package-objects.md b/_tour/package-objects.md index 5285567ca0..8be239c933 100644 --- a/_tour/package-objects.md +++ b/_tour/package-objects.md @@ -1,29 +1,50 @@ --- layout: tour -title: Package Objects +title: Top Level Definitions in Packages partof: scala-tour num: 36 previous-page: packages-and-imports --- -# Package objects +Often, it is convenient to have definitions accessible across an entire package, and not need to invent a +name for a wrapper `object` to contain them. -Scala provides package objects as a convenient container shared across an entire package. +{% tabs pkg-obj-vs-top-lvl_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_1 %} +Scala 2 provides _package objects_ as a convenient container shared across an entire package. Package objects can contain arbitrary definitions, not just variable and method definitions. For instance, they are frequently used to hold package-wide type aliases and implicit conversions. Package objects can even inherit Scala classes and traits. +> In a future version of Scala 3, package objects will be removed in favor of top level definitions. + By convention, the source code for a package object is usually put in a source file named `package.scala`. Each package is allowed to have one package object. Any definitions placed in a package object are considered members of the package itself. +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_1 %} +In Scala 3, any kind of definition can be declared at the top level of a package. For example, classes, enums, +methods and variables. + +Any definitions placed at the top level of a package are considered members of the package itself. + +> In Scala 2, top-level method, type and variable definitions had to be wrapped in a **package object**. +> These are still usable in Scala 3 for backwards compatibility. You can see how they work by switching tabs. + +{% endtab %} +{% endtabs %} + See example below. Assume first a class `Fruit` and three `Fruit` objects in a package `gardening.fruits`: + +{% tabs pkg-obj-vs-top-lvl_2 %} +{% tab 'Scala 2 and 3' for=pkg-obj-vs-top-lvl_2 %} ``` // in file gardening/fruits/Fruit.scala package gardening.fruits @@ -33,10 +54,15 @@ object Apple extends Fruit("Apple", "green") object Plum extends Fruit("Plum", "blue") object Banana extends Fruit("Banana", "yellow") ``` +{% endtab %} +{% endtabs %} Now assume you want to place a variable `planted` and a method `showFruit` directly into package `gardening.fruits`. Here's how this is done: +{% tabs pkg-obj-vs-top-lvl_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_3 %} + ``` // in file gardening/fruits/package.scala package gardening @@ -47,13 +73,29 @@ package object fruits { } } ``` +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_3 %} + +``` +// in file gardening/fruits/package.scala +package gardening.fruits + +val planted = List(Apple, Plum, Banana) +def showFruit(fruit: Fruit): Unit = + println(s"${fruit.name}s are ${fruit.color}") +``` +{% endtab %} +{% endtabs %} -As an example of how to use this, the following object `PrintPlanted` imports `planted` and `showFruit` in exactly the same -way it imports class `Fruit`, using a wildcard import on package gardening.fruits: +As an example of how to use this, the following program imports `planted` and `showFruit` in exactly the same +way it imports class `Fruit`, using a wildcard import on package `gardening.fruits`: +{% tabs pkg-obj-vs-top-lvl_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_4 %} ``` // in file PrintPlanted.scala import gardening.fruits._ + object PrintPlanted { def main(args: Array[String]): Unit = { for (fruit <- planted) { @@ -62,11 +104,53 @@ object PrintPlanted { } } ``` +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_4 %} +``` +// in file printPlanted.scala +import gardening.fruits.* -Package objects are like other objects, which means you can use inheritance for building them. For example, one might mix in a couple of traits: +@main def printPlanted(): Unit = + for fruit <- planted do + showFruit(fruit) +``` +{% endtab %} +{% endtabs %} + +### Aggregating Several Definitions at the Package Level + +Often, your project may have several reusable definitions defined in various modules, that you +wish to aggregate at the top level of a package. + +For example, some helper methods in the trait `FruitHelpers` and +some term/type aliases in trait `FruitAliases`. Here is how you can put all their definitions at the level of the `fruit` +package: + +{% tabs pkg-obj-vs-top-lvl_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_5 %} + +Package objects are like other objects, which means you can use inheritance for building them. +So here we mix in the helper traits as parents of the package object. ``` -package object fruits extends FruitAliases with FruitHelpers { - // helpers and variables follows here -} +package gardening + +// `fruits` instead inherits its members from its parents. +package object fruits extends FruitAliases with FruitHelpers +``` +{% endtab %} +{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_5 %} + +In Scala 3, it is preferred to use `export` to compose members from several objects into a single scope. +Here we define private objects that mix in the helper traits, then export their members at the top level: + +``` +package gardening.fruits + +private object FruitAliases extends FruitAliases +private object FruitHelpers extends FruitHelpers + +export FruitHelpers.*, FruitAliases.* ``` +{% endtab %} +{% endtabs %} diff --git a/_tour/packages-and-imports.md b/_tour/packages-and-imports.md index f18f8ae5e3..3f07344f32 100644 --- a/_tour/packages-and-imports.md +++ b/_tour/packages-and-imports.md @@ -14,11 +14,16 @@ Scala uses packages to create namespaces which allow you to modularize programs. ## Creating a package Packages are created by declaring one or more package names at the top of a Scala file. +{% tabs packages-and-imports_1 %} +{% tab 'Scala 2 and 3' for=packages-and-imports_1 %} ``` package users class User ``` +{% endtab %} +{% endtabs %} + One convention is to name the package the same as the directory containing the Scala file. However, Scala is agnostic to file layout. The directory structure of an sbt project for `package users` might look like this: ``` - ExampleProject @@ -33,8 +38,11 @@ One convention is to name the package the same as the directory containing the S UserPreferences.scala - test ``` -Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by using braces: -``` +Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by nesting them inside each other: + +{% tabs packages-and-imports_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_2 %} +```scala package users { package administrators { class NormalUser @@ -44,39 +52,95 @@ package users { } } ``` +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_2 %} +```scala +package users: + package administrators: + class NormalUser + + package normalusers: + class NormalUser +``` +{% endtab %} +{% endtabs %} + As you can see, this allows for package nesting and provides greater control for scope and encapsulation. The package name should be all lower case and if the code is being developed within an organization which has a website, it should be the following format convention: `..`. For example, if Google had a project called `SelfDrivingCar`, the package name would look like this: -``` + +{% tabs packages-and-imports_3 %} +{% tab 'Scala 2 and 3' for=packages-and-imports_3 %} +```scala package com.google.selfdrivingcar.camera class Lens ``` +{% endtab %} +{% endtabs %} + This could correspond to the following directory structure: `SelfDrivingCar/src/main/scala/com/google/selfdrivingcar/camera/Lens.scala`. ## Imports `import` clauses are for accessing members (classes, traits, functions, etc.) in other packages. An `import` clause is not required for accessing members of the same package. Import clauses are selective: + +{% tabs packages-and-imports_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_4 %} ``` import users._ // import everything from the users package import users.User // import the class User import users.{User, UserPreferences} // Only imports selected members import users.{UserPreferences => UPrefs} // import and rename for convenience ``` +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_4 %} +``` +import users.* // import everything from the users package except given +import users.given // import all given from the users package +import users.User // import the class User +import users.{User, UserPreferences} // Only imports selected members +import users.UserPreferences as UPrefs // import and rename for convenience +``` +{% endtab %} +{% endtabs %} One way in which Scala is different from Java is that imports can be used anywhere: +{% tabs packages-and-imports_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_5 %} ```scala mdoc def sqrtplus1(x: Int) = { import scala.math.sqrt sqrt(x) + 1.0 } ``` -In the event there is a naming conflict and you need to import something from the root of the project, prefix the package name with `_root_`: +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_5 %} +```scala +def sqrtplus1(x: Int) = + import scala.math.sqrt + sqrt(x) + 1.0 ``` +{% endtab %} +{% endtabs %} + +In the event there is a naming conflict and you need to import something from the root of the project, prefix the package name with `_root_`: + +{% tabs packages-and-imports_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=packages-and-imports_6 %} +```scala package accounts import _root_.users._ ``` +{% endtab %} +{% tab 'Scala 3' for=packages-and-imports_6 %} +```scala +package accounts +import _root_.users.* +``` +{% endtab %} +{% endtabs %} Note: The `scala` and `java.lang` packages as well as `object Predef` are imported by default. diff --git a/_tour/pattern-matching.md b/_tour/pattern-matching.md index 72afeb1902..f48682afea 100644 --- a/_tour/pattern-matching.md +++ b/_tour/pattern-matching.md @@ -15,6 +15,8 @@ Pattern matching is a mechanism for checking a value against a pattern. A succes ## Syntax A match expression has a value, the `match` keyword, and at least one `case` clause. +{% tabs pattern-matching-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-1 %} ```scala mdoc import scala.util.Random @@ -27,9 +29,26 @@ x match { case _ => "other" } ``` -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 other possible `Int` values. Cases are also called _alternatives_. +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-1 %} +```scala +import scala.util.Random + +val x: Int = Random.nextInt(10) + +x match + case 0 => "zero" + case 1 => "one" + case 2 => "two" + case _ => "other" +``` +{% endtab %} +{% endtabs %} +The `val x` above is a random integer between 0 and 9. `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 other possible `Int` values. Cases are also called _alternatives_. Match expressions have a value. +{% tabs pattern-matching-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-2 %} ```scala mdoc def matchTest(x: Int): String = x match { case 1 => "one" @@ -39,26 +58,45 @@ def matchTest(x: Int): String = x match { matchTest(3) // returns other matchTest(1) // returns one ``` +{% endtab %} + +{% tab 'Scala 3' for=pattern-matching-2 %} +```scala +def matchTest(x: Int): String = x match + case 1 => "one" + case 2 => "two" + case _ => "other" + +matchTest(3) // returns other +matchTest(1) // returns one +``` +{% endtab %} +{% endtabs %} 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. +{% tabs notification %} +{% tab 'Scala 2 and 3' for=notification %} ```scala mdoc -abstract class Notification +sealed trait 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: +{% endtab %} +{% endtabs %} -``` +`Notification` is a sealed trait which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. (A [sealed trait](/tour/pattern-matching.html#sealed-types) can be extended only in the same file as its declaration.) Now we can do pattern matching on these case classes: + +{% tabs pattern-matching-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-4 %} +```scala def showNotification(notification: Notification): String = { notification match { case Email(sender, title, _) => @@ -76,12 +114,99 @@ println(showNotification(someSms)) // prints You got an SMS from 12345! Message println(showNotification(someVoiceRecording)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-4 %} +```scala +def showNotification(notification: Notification): String = + notification match + case Email(sender, title, _) => + s"You got an email from $sender 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)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +``` +{% endtab %} +{% endtabs %} + 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(sender, title, _)` the fields `sender` 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. +## Matching on string + +The `s`-interpolator allows embedding variables in strings and is also useful for pattern matching. + +{% tabs s-interpolator-pattern-matching class=tabs-scala-version %} +{% tab 'Scala 2' for=s-interpolator-pattern-matching %} +```scala +val input: String = "Alice is 25 years old" + +input match { + case s"$name is $age years old" => s"$name's age is $age" + case _ => "No match" +} +// Result: "Alice's age is 25" +``` +{% endtab %} +{% tab 'Scala 3' for=s-interpolator-pattern-matching %} +```scala +val input: String = "Alice is 25 years old" + +input match + case s"$name is $age years old" => s"$name's age is $age" + case _ => "No match" +// Result: "Alice's age is 25" ``` +{% endtab %} +{% endtabs %} + +In this example, name and age extract parts of the string based on the pattern. This is helpful for parsing structured text. + +We can also use extractor objects for string pattern matching. + +{% tabs s-interpolator-pattern-matching-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=s-interpolator-pattern-matching-2 %} +```scala +object Age { + def unapply(s: String): Option[Int] = s.toIntOption +} + +val input: String = "Alice is 25 years old" + +val (name, age) = input match { + case s"$name is ${Age(age)} years old" => (name, age) +} +// name: String = Alice +// age: Int = 25 +``` +{% endtab %} +{% tab 'Scala 3' for=s-interpolator-pattern-matching-2 %} +```scala +object Age: + def unapply(s: String): Option[Int] = s.toIntOption + +val input: String = "Alice is 25 years old" + +val (name, age) = input match + case s"$name is ${Age(age)} years old" => (name, age) +// name: String = Alice +// age: Int = 25 +``` +{% endtab %} +{% endtabs %} + +## Pattern guards +Pattern guards are boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. +{% tabs pattern-matching-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-5 %} +```scala def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { notification match { case Email(sender, _, _) if importantPeopleInfo.contains(sender) => @@ -106,13 +231,42 @@ println(showImportantNotification(importantEmail, importantPeopleInfo)) // print println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone! ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-5 %} +```scala +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = + notification match + case Email(sender, _, _) if importantPeopleInfo.contains(sender) => + "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("123-4567", "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)) // prints You got an SMS from 123-4567! Message: Are you there? +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone! + +println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone! +``` +{% endtab %} +{% endtabs %} In the `case Email(sender, _, _) if importantPeopleInfo.contains(sender)`, the pattern is matched only if the `sender` is in the list of important people. ## Matching on type only You can match on the type like so: +{% tabs pattern-matching-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-6 %} ```scala mdoc -abstract class Device +sealed trait Device case class Phone(model: String) extends Device { def screenOff = "Turning screen off" } @@ -120,27 +274,108 @@ case class Computer(model: String) extends Device { def screenSaverOn = "Turning screen saver on..." } -def goIdle(device: Device) = device match { +def goIdle(device: Device): String = device match { case p: Phone => p.screenOff case c: Computer => c.screenSaverOn } ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-6 %} +```scala +sealed trait 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): String = device match + case p: Phone => p.screenOff + case c: Computer => c.screenSaverOn +``` +{% endtab %} +{% endtabs %} + `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. This assures that all subtypes are known. +## Binding matched patterns to variables +You can use variable binding to get type-dependent behavior while simultaneously extracting fields from the matched pattern. +{% tabs pattern-matching-variable-binding class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-variable-binding %} ```scala mdoc -sealed abstract class Furniture -case class Couch() extends Furniture -case class Chair() extends Furniture +def goIdleWithModel(device: Device): String = device match { + case p @ Phone(model) => s"$model: ${p.screenOff}" + case c @ Computer(model) => s"$model: ${c.screenSaverOn}" +} +``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-variable-binding %} +```scala +def goIdleWithModel(device: Device): String = device match + case p @ Phone(model) => s"$model: ${p.screenOff}" + case c @ Computer(model) => s"$model: ${c.screenSaverOn}" +``` +{% endtab %} +{% endtabs %} + +## Sealed types + +You may have noticed that in the examples above the base types are qualified +with the keyword `sealed`. This provides extra safety because the compiler +checks that the `cases` of a `match` expression are exhaustive when the base +type is `sealed`. -def findPlaceToSit(piece: Furniture): String = piece match { - case a: Couch => "Lie on the couch" - case b: Chair => "Sit on the chair" +For instance, in the method `showNotification` defined above, if we forget +one case, say, `VoiceRecording`, the compiler emits a warning: + +{% tabs pattern-matching-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-7 %} +```scala +def showNotification(notification: Notification): String = { + notification match { + case Email(sender, title, _) => + s"You got an email from $sender with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" + } } ``` -This is useful for pattern matching because we don't need a "catch all" case. +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-7 %} +```scala +def showNotification(notification: Notification): String = + notification match + case Email(sender, title, _) => + s"You got an email from $sender with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" +``` +{% endtab %} +{% endtabs %} + +This definition produces the following warning: + +~~~ +match may not be exhaustive. + +It would fail on pattern case: VoiceRecording(_, _) +~~~ + +The compiler even provides examples of input that would fail! + +On the flip side, exhaustivity checking requires you to define all the subtypes +of the base type in the same file as the base type (otherwise, the compiler +would not know what are all the possible cases). For instance, if you try +to define a new type of `Notification` outside of the file that defines +the `sealed trait Notification`, it will produce a compilation error: + +~~~ +case class Telepathy(message: String) extends Notification + ^ + Cannot extend sealed trait Notification in a different source file +~~~ ## Notes @@ -149,4 +384,4 @@ Scala also allows the definition of patterns independently of case classes, usin ## More resources -* More details on match expressions in the [Scala Book](/overviews/scala-book/match-expressions.html) +* More details on match expressions in the [Scala Book](/scala3/book/control-structures.html#match-expressions) diff --git a/_tour/polymorphic-methods.md b/_tour/polymorphic-methods.md index ce124cb12a..e8f99858e9 100644 --- a/_tour/polymorphic-methods.md +++ b/_tour/polymorphic-methods.md @@ -12,10 +12,12 @@ prerequisite-knowledge: unified-types redirect_from: "/tutorials/tour/polymorphic-methods.html" --- -Methods in Scala can be parameterized by type as well as value. The syntax is similar to that of generic classes. Type parameters are enclosed in square brackets, while value parameters are enclosed in parentheses. +Methods in Scala can be parameterized by type as well as by value. The syntax is similar to that of generic classes. Type parameters are enclosed in square brackets, while value parameters are enclosed in parentheses. Here is an example: +{% tabs polymorphic-methods_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=polymorphic-methods_1 %} ```scala mdoc def listOfDuplicates[A](x: A, length: Int): List[A] = { if (length < 1) @@ -26,9 +28,23 @@ def listOfDuplicates[A](x: A, length: Int): List[A] = { println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) ``` +{% endtab %} +{% tab 'Scala 3' for=polymorphic-methods_1 %} +```scala +def listOfDuplicates[A](x: A, length: Int): List[A] = + if length < 1 then + 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) +``` +{% endtab %} +{% endtabs %} The method `listOfDuplicates` takes a type parameter `A` and value parameters `x` and `length`. Value `x` is of type `A`. If `length < 1` we return an empty list. Otherwise we prepend `x` to the list of duplicates returned by the recursive call. (Note that `::` means prepend an element on the left to a list on the right.) -In first example call, we explicitly provide the type parameter by writing `[Int]`. Therefore the first argument must be an `Int` and the return type will be `List[Int]`. +In the first example call, we explicitly provide the type parameter by writing `[Int]`. Therefore the first argument must be an `Int` and the return type will be a `List[Int]`. -The second example call shows that you don't always need to explicitly provide the type parameter. The compiler can often infer it based on context or on the types of the value arguments. In this example, `"La"` is a `String` so the compiler knows `A` must be `String`. +The second example call shows that you don't always need to explicitly provide the type parameter. The compiler can often infer it based on context or on the types of the value arguments. In this example, `"La"` is a `String` so the compiler knows that `A` must be a `String`. diff --git a/_tour/regular-expression-patterns.md b/_tour/regular-expression-patterns.md index aa51821463..40d12e5003 100644 --- a/_tour/regular-expression-patterns.md +++ b/_tour/regular-expression-patterns.md @@ -13,6 +13,9 @@ redirect_from: "/tutorials/tour/regular-expression-patterns.html" 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. +{% tabs regex-patterns_numberPattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_numberPattern %} ```scala mdoc import scala.util.matching.Regex @@ -23,12 +26,30 @@ numberPattern.findFirstMatchIn("awesomepassword") match { case None => println("Password must contain a number") } ``` +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_numberPattern %} +```scala +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") +``` +{% endtab %} + +{% endtabs %} 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. +{% tabs regex-patterns_keyValPattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_keyValPattern %} ```scala mdoc import scala.util.matching.Regex @@ -47,6 +68,31 @@ val input: String = for (patternMatch <- keyValPattern.findAllMatchIn(input)) println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") ``` +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_keyValPattern %} +```scala +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) do + println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") +``` +{% endtab %} + +{% endtabs %} + 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 @@ -58,3 +104,63 @@ key: margin value: 0 key: height value: 108px key: width value: 100 ``` + +Moreover, regular expressions can be used as patterns (in `match` expressions) to conveniently extract the matched groups: + +{% tabs regex-patterns_saveContactInformation class=tabs-scala-version %} + +{% tab 'Scala 2' for=regex-patterns_saveContactInformation %} +```scala mdoc +def saveContactInformation(contact: String): Unit = { + import scala.util.matching.Regex + + val emailPattern: Regex = """^(\w+)@(\w+(.\w+)+)$""".r + val phonePattern: Regex = """^(\d{3}-\d{3}-\d{4})$""".r + + contact match { + case emailPattern(localPart, domainName, _) => + println(s"Hi $localPart, we have saved your email address.") + case phonePattern(phoneNumber) => + println(s"Hi, we have saved your phone number $phoneNumber.") + case _ => + println("Invalid contact information, neither an email address nor phone number.") + } +} + +saveContactInformation("123-456-7890") +saveContactInformation("JohnSmith@sample.domain.com") +saveContactInformation("2 Franklin St, Mars, Milky Way") +``` +{% endtab %} + +{% tab 'Scala 3' for=regex-patterns_saveContactInformation %} +```scala +def saveContactInformation(contact: String): Unit = + import scala.util.matching.Regex + + val emailPattern: Regex = """^(\w+)@(\w+(.\w+)+)$""".r + val phonePattern: Regex = """^(\d{3}-\d{3}-\d{4})$""".r + + contact match + case emailPattern(localPart, domainName, _) => + println(s"Hi $localPart, we have saved your email address.") + case phonePattern(phoneNumber) => + println(s"Hi, we have saved your phone number $phoneNumber.") + case _ => + println("Invalid contact information, neither an email address nor phone number.") + +saveContactInformation("123-456-7890") +saveContactInformation("JohnSmith@sample.domain.com") +saveContactInformation("2 Franklin St, Mars, Milky Way") +``` +{% endtab %} + +{% endtabs %} + +The output would be: + +``` +Hi, we have saved your phone number 123-456-7890. +Hi JohnSmith, we have saved your email address. +Invalid contact information, neither an email address nor phone number. +``` diff --git a/_tour/self-types.md b/_tour/self-types.md index 49110f23f9..f209fd5101 100644 --- a/_tour/self-types.md +++ b/_tour/self-types.md @@ -16,6 +16,9 @@ Self-types are a way to declare that a trait must be mixed into another trait, e 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 =>`). + +{% tabs self-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=self-types_1 %} ```scala mdoc trait User { def username: String @@ -33,5 +36,25 @@ class VerifiedTweeter(val username_ : String) extends Tweeter with User { // We 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`). + +{% endtab %} +{% tab 'Scala 3' for=self-types_1 %} +```scala +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, User: // We mixin User because Tweeter required it + def username = s"real $username_" + +val realBeyoncé = 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 `, User`). + +{% endtab %} +{% endtabs %} diff --git a/_tour/singleton-objects.md b/_tour/singleton-objects.md index 037e855b0b..828d49a38a 100644 --- a/_tour/singleton-objects.md +++ b/_tour/singleton-objects.md @@ -16,23 +16,52 @@ As a top-level value, an object is a singleton. As a member of an enclosing class or as a local value, it behaves exactly like a lazy val. # Defining a singleton object An object is a value. The definition of an object looks like a class, but uses the keyword `object`: + + +{% tabs object-definition-box %} + +{% tab 'Scala 2 and 3' for=object-definition-box %} ```scala mdoc object Box ``` +{% endtab %} + +{% endtabs %} Here's an example of an object with a method: -``` +{% tabs singleton-logger-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=singleton-logger-example %} + +```scala package logging object Logger { def info(message: String): Unit = println(s"INFO: $message") } ``` +{% endtab %} + +{% tab 'Scala 3' for=singleton-logger-example %} + +```scala +package logging + +object Logger: + def info(message: String): Unit = println(s"INFO: $message") +``` +{% endtab %} + +{% endtabs %} + The method `info` can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects. Let's see how to use `info` in another package: +{% tabs singleton-usage-example class=tabs-scala-version %} -``` +{% tab 'Scala 2' for=singleton-usage-example %} + +```scala import logging.Logger.info class Project(name: String, daysToComplete: Int) @@ -43,6 +72,24 @@ class Test { info("Created projects") // Prints "INFO: Created projects" } ``` +{% endtab %} + +{% tab 'Scala 3' for=singleton-usage-example %} + +```scala +import logging.Logger.info + +class Project(name: String, daysToComplete: Int) + +class Test: + val project1 = Project("TPS Reports", 1) + val project2 = Project("Website redesign", 5) + info("Created projects") // Prints "INFO: Created projects" +``` +{% endtab %} + +{% endtabs %} + The `info` method is visible because of the import statement, `import logging.Logger.info`. @@ -53,8 +100,11 @@ Note: If an `object` is not top-level but is nested in another class or object, ## Companion objects An object with the same name as a class is called a _companion object_. Conversely, the class is the object's companion class. A companion class or object can access the private members of its companion. Use a companion object for methods and values which are not specific to instances of the companion class. -``` -import scala.math._ +{% tabs companion-object-circle class=tabs-scala-version %} + +{% tab 'Scala 2' for=companion-object-circle %} +```scala +import scala.math.{Pi, pow} case class Circle(radius: Double) { import Circle._ @@ -69,10 +119,34 @@ val circle1 = Circle(5.0) circle1.area ``` +{% endtab %} + +{% tab 'Scala 3' for=companion-object-circle %} +```scala +import scala.math.{Pi, pow} + +case class Circle(radius: Double): + import Circle.* + def area: Double = calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + + +val circle1 = Circle(5.0) + +circle1.area +``` +{% endtab %} + +{% endtabs %} The `class Circle` has a member `area` which is specific to each instance, and the singleton `object Circle` has a method `calculateArea` which is available to every instance. The companion object can also contain factory methods: +{% tabs companion-object-email class=tabs-scala-version %} + +{% tab 'Scala 2' for=companion-object-email %} ```scala mdoc class Email(val username: String, val domainName: String) @@ -95,8 +169,41 @@ scalaCenterEmail match { case None => println("Error: could not parse email") } ``` +{% endtab %} + +{% tab 'Scala 3' for=companion-object-email %} +```scala +class Email(val username: String, val domainName: String) + +object Email: + def fromString(emailString: String): Option[Email] = + emailString.split('@') match + case Array(a, b) => Some(Email(a, b)) + case _ => None + +val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") +scalaCenterEmail match + case Some(email) => println( + s"""Registered an email + |Username: ${email.username} + |Domain name: ${email.domainName} + """.stripMargin) + case None => println("Error: could not parse email") +``` +{% endtab %} + +{% endtabs %} + The `object Email` contains a factory `fromString` which creates an `Email` instance from a String. We return it as an `Option[Email]` in case of parsing errors. +A note about `Option`, `Some`, and `None` in the code above: +* `Option` is a data type which allows for optionality. It has two cases: `Some` and `None` + * `Some` above represents a match: the emailString, when split by a @, returns an array with two components. This allows creation of a valid instance of class Email. + * `None` above represents no match: the emailString, when split by a @, did not return an array with two components. It could not allow creation of a valid instance of class Email. +* The `Option` return type can then be used in a match/case: + * For a `Some` result, the match knows the returned value is an instance of `Email`, so it can access the inner `username` and `domainName`. + * For a `None` result, the match knows the returned value is not an instance of `Email`, so it prints an appropriate error message. + Note: If a class or object has a companion, both must be defined in the same file. To define companions in the REPL, either define them on the same line or enter `:paste` mode. ## Notes for Java programmers ## @@ -107,4 +214,4 @@ When using a companion object from Java code, the members will be defined in a c ## More resources -* Learn more about Companion objects in the [Scala Book](/overviews/scala-book/companion-objects.html) +* Learn more about Companion objects in the [Scala Book](/scala3/book/domain-modeling-tools.html#companion-objects) diff --git a/_tour/tour-of-scala.md b/_tour/tour-of-scala.md index 59e4072e3a..16052b51f8 100644 --- a/_tour/tour-of-scala.md +++ b/_tour/tour-of-scala.md @@ -18,8 +18,8 @@ This tour contains bite-sized introductions to the most frequently used features of Scala. It is intended for newcomers to the language. This is just a brief tour, not a full language tutorial. If -you want a more detailed guide, consider obtaining [a book](/books.html) or consulting -[other resources](/learn.html). +you want a more detailed guide, consider obtaining [a book](/books.html) or taking +[an online courses](/online-courses.html). ## What is Scala? Scala is a modern multi-paradigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It seamlessly integrates features of object-oriented and functional languages. @@ -28,9 +28,7 @@ Scala is a modern multi-paradigm programming language designed to express common Scala is a pure object-oriented language in the sense that [every value is an object](unified-types.html). Types and behaviors of objects are described by [classes](classes.html) and [traits](traits.html). Classes can be extended by subclassing, and by using 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](basics.html#functions) for defining anonymous functions, it supports [higher-order functions](higher-order-functions.html), it allows functions to be [nested](nested-functions.html), and it supports [currying](multiple-parameter-lists.html). Scala's [case classes](case-classes.html) and its built-in support for [pattern matching](pattern-matching.html) provide the functionality of algebraic types, which are used in many functional 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](https://github.com/scala/scala-xml/wiki/XML-Processing) 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 also a functional language in the sense that [every function is a value](unified-types.html). Scala provides a [lightweight syntax](basics.html#functions) for defining anonymous functions, supports [higher-order functions](higher-order-functions.html), allows functions to be [nested](nested-functions.html), and supports [currying](multiple-parameter-lists.html). Scala's [case classes](case-classes.html) and its built-in support for [pattern matching](pattern-matching.html) provide the functionality of algebraic types, which are used in many functional languages. [Singleton objects](singleton-objects.html) provide a convenient way to group functions that aren't members of a class. ## Scala is statically typed ## Scala's expressive type system enforces, at compile-time, that abstractions are used in a safe and coherent manner. In particular, the type system supports: diff --git a/_tour/traits.md b/_tour/traits.md index 6500490b7d..f719f96744 100644 --- a/_tour/traits.md +++ b/_tour/traits.md @@ -17,22 +17,45 @@ Traits are used to share interfaces and fields between classes. They are similar ## Defining a trait A minimal trait is simply the keyword `trait` and an identifier: +{% tabs trait-hair-color %} +{% tab 'Scala 2 and 3' for=trait-hair-color %} ```scala mdoc trait HairColor ``` +{% endtab %} +{% endtabs %} Traits become especially useful as generic types and with abstract methods. + +{% tabs trait-iterator-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-iterator-definition %} ```scala mdoc trait Iterator[A] { def hasNext: Boolean def next(): A } ``` +{% endtab %} + +{% tab 'Scala 3' for=trait-iterator-definition %} +```scala +trait Iterator[A]: + def hasNext: Boolean + def next(): A +``` +{% endtab %} + +{% endtabs %} 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: + +{% tabs trait-intiterator-definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-intiterator-definition %} ```scala mdoc:nest trait Iterator[A] { def hasNext: Boolean @@ -51,15 +74,46 @@ class IntIterator(to: Int) extends Iterator[Int] { } } - val iterator = new IntIterator(10) iterator.next() // returns 0 iterator.next() // returns 1 ``` +{% endtab %} + +{% tab 'Scala 3' for=trait-intiterator-definition %} +```scala +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 then + val t = current + current += 1 + t + else + 0 +end IntIterator + +val iterator = IntIterator(10) +iterator.next() // returns 0 +iterator.next() // returns 1 +``` +{% endtab %} + +{% endtabs %} + 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. + +{% tabs trait-pet-example class=tabs-scala-version %} + +{% tab 'Scala 2' for=trait-pet-example %} ```scala mdoc import scala.collection.mutable.ArrayBuffer @@ -78,10 +132,34 @@ animals.append(dog) animals.append(cat) animals.foreach(pet => println(pet.name)) // Prints Harry Sally ``` +{% endtab %} + +{% tab 'Scala 3' for=trait-pet-example %} +```scala +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 = Dog("Harry") +val cat = Cat("Sally") + +val animals = ArrayBuffer.empty[Pet] +animals.append(dog) +animals.append(cat) +animals.foreach(pet => println(pet.name)) // Prints Harry Sally +``` +{% endtab %} + +{% endtabs %} + The `trait Pet` has an abstract field `name` that 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`. ## More resources -* Learn more about traits in the [Scala Book](/overviews/scala-book/traits-intro.html) -* Use traits to define [Enum](/overviews/scala-book/enumerations-pizza-class.html) +* Learn more about traits in the [Scala Book](/scala3/book/domain-modeling-tools.html#traits) +* Use traits to define [Enum](/scala3/book/domain-modeling-fp.html#modeling-the-data) diff --git a/_tour/tuples.md b/_tour/tuples.md index 3f684f63bb..19166098fb 100644 --- a/_tour/tuples.md +++ b/_tour/tuples.md @@ -18,62 +18,106 @@ Tuples are especially handy for returning multiple values from a method. A tuple with two elements can be created as follows: +{% tabs tuple-construction %} + +{% tab 'Scala 2 and 3' for=tuple-construction %} ```scala mdoc -val ingredient = ("Sugar" , 25) +val ingredient = ("Sugar", 25) ``` +{% endtab %} -This creates a tuple containing a `String` element and an `Int` element. +{% endtabs %} -The inferred type of `ingredient` is `(String, Int)`, which is shorthand -for `Tuple2[String, Int]`. +This creates a tuple containing a `String` element and an `Int` element. -To represent tuples, Scala uses a series of classes: `Tuple2`, `Tuple3`, etc., through `Tuple22`. -Each class has as many type parameters as it has elements. +The inferred type of `ingredient` is `(String, Int)`. ## Accessing the elements -One way of accessing tuple elements is by position. The individual -elements are named `_1`, `_2`, and so forth. +{% tabs tuple-indexed-access class=tabs-scala-version %} + +{% tab 'Scala 2' for=tuple-indexed-access %} +One way of accessing tuple elements is their positions. +The individual elements are named `_1`, `_2`, and so forth. ```scala mdoc println(ingredient._1) // Sugar println(ingredient._2) // 25 ``` +{% endtab %} + +{% tab 'Scala 3' for=tuple-indexed-access %} +One way of accessing tuple elements is their positions. +The individual elements are accessed with `tuple(0)`, `tuple(1)`, and so forth. + +```scala +println(ingredient(0)) // Sugar +println(ingredient(1)) // 25 +``` +{% endtab %} + +{% endtabs %} ## Pattern matching on tuples A tuple can also be taken apart using pattern matching: +{% tabs tuple-extraction %} + +{% tab 'Scala 2 and 3' for=tuple-extraction %} ```scala mdoc val (name, quantity) = ingredient -println(name) // Sugar +println(name) // Sugar println(quantity) // 25 ``` +{% endtab %} + +{% endtabs %} Here `name`'s inferred type is `String` and `quantity`'s inferred type is `Int`. Here is another example of pattern-matching a tuple: +{% tabs tuple-foreach-patmat %} + +{% tab 'Scala 2 and 3' for=tuple-foreach-patmat %} ```scala mdoc val planets = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6), ("Mars", 227.9), ("Jupiter", 778.3)) -planets.foreach{ +planets.foreach { case ("Earth", distance) => println(s"Our planet is $distance million kilometers from the sun") case _ => } ``` +{% endtab %} + +{% endtabs %} -Or, in `for` comprehension: +Or, in a `for` comprehension: +{% tabs tuple-for-extraction class=tabs-scala-version %} + +{% tab 'Scala 2' for=tuple-for-extraction %} ```scala mdoc val numPairs = List((2, 5), (3, -7), (20, 56)) for ((a, b) <- numPairs) { println(a * b) } ``` +{% endtab %} + +{% tab 'Scala 3' for=tuple-for-extraction %} +```scala +val numPairs = List((2, 5), (3, -7), (20, 56)) +for (a, b) <- numPairs do + println(a * b) +``` +{% endtab %} + +{% endtabs %} ## Tuples and case classes @@ -82,4 +126,4 @@ Users may sometimes find it hard to choose between tuples and case classes. Case ## More resources -* Learn more about tuples in the [Scala Book](/overviews/scala-book/tuples.html) +* Learn more about tuples in the [Scala Book](/scala3/book/taste-collections.html#tuples) diff --git a/_tour/type-inference.md b/_tour/type-inference.md index 271906fe3c..3b5ea38cc0 100644 --- a/_tour/type-inference.md +++ b/_tour/type-inference.md @@ -12,26 +12,47 @@ The Scala compiler can often infer the type of an expression so you don't have t ## Omitting the type +{% tabs type-inference_1 %} +{% tab 'Scala 2 and 3' for=type-inference_1 %} ```scala mdoc val businessName = "Montreux Jazz Café" ``` +{% endtab %} +{% endtabs %} + The compiler can detect that `businessName` is a String. It works similarly with methods: +{% tabs type-inference_2 %} +{% tab 'Scala 2 and 3' for=type-inference_2 %} ```scala mdoc def squareOf(x: Int) = x * x ``` +{% endtab %} +{% endtabs %} + The compiler can infer that the return type is an `Int`, so no explicit return type is required. 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: +{% tabs type-inference_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=type-inference_3 %} ```scala mdoc:fail def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) ``` +{% endtab %} +{% tab 'Scala 3' for=type-inference_3 %} +```scala +def fac(n: Int) = if n == 0 then 1 else n * fac(n - 1) +``` +{% endtab %} +{% endtabs %} 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 are two examples: +{% tabs type-inference_4 %} +{% tab 'Scala 2 and 3' for=type-inference_4 %} ```scala mdoc case class MyPair[A, B](x: A, y: B) val p = MyPair(1, "scala") // type: MyPair[Int, String] @@ -39,6 +60,8 @@ val p = MyPair(1, "scala") // type: MyPair[Int, String] def id[T](x: T) = x val q = id(1) // type: Int ``` +{% endtab %} +{% endtabs %} The compiler uses the types of the arguments of `MyPair` to figure out what type `A` and `B` are. Likewise for the type of `x`. @@ -46,9 +69,13 @@ The compiler uses the types of the arguments of `MyPair` to figure out what type The compiler never infers method parameter types. However, in certain cases, it can infer anonymous function parameter types when the function is passed as argument. +{% tabs type-inference_5 %} +{% tab 'Scala 2 and 3' for=type-inference_5 %} ```scala mdoc Seq(1, 3, 4).map(x => x * 2) // List(2, 6, 8) ``` +{% endtab %} +{% endtabs %} The parameter for map is `f: A => B`. Because we put integers in the `Seq`, the compiler knows that `A` is `Int` (i.e. that `x` is an integer). Therefore, the compiler can infer from `x * 2` that `B` is type `Int`. @@ -58,14 +85,22 @@ It is generally considered more readable to declare the type of members exposed Also, type inference can sometimes infer a too-specific type. Suppose we write: +{% tabs type-inference_6 %} +{% tab 'Scala 2 and 3' for=type-inference_6 %} ```scala var obj = null ``` +{% endtab %} +{% endtabs %} We can't then go on and make this reassignment: +{% tabs type-inference_7 %} +{% tab 'Scala 2 and 3' for=type-inference_7 %} ```scala mdoc:fail obj = new AnyRef ``` +{% endtab %} +{% endtabs %} It won't compile, because the type inferred for `obj` was `Null`. Since the only value of that type is `null`, it is impossible to assign a different value. diff --git a/_tour/unified-types.md b/_tour/unified-types.md index 2bae3732db..a6f0e9c0dd 100644 --- a/_tour/unified-types.md +++ b/_tour/unified-types.md @@ -25,6 +25,8 @@ In Scala, all values have a type, including numerical values and functions. The Here is an example that demonstrates that strings, integers, characters, boolean values, and functions are all of type `Any` just like every other object: +{% tabs unified-types-1 %} +{% tab 'Scala 2 and 3' for=unified-types-1 %} ```scala mdoc val list: List[Any] = List( "a string", @@ -36,6 +38,8 @@ val list: List[Any] = List( list.foreach(element => println(element)) ``` +{% endtab %} +{% endtabs %} It defines a value `list` of type `List[Any]`. The list is initialized with elements of various types, but each is an instance of `scala.Any`, so you can add them to the list. @@ -53,23 +57,35 @@ true Value types can be cast in the following way:
Scala Type Hierarchy +Note that `Long` to `Float` conversion is deprecated in new versions of Scala, because of the potential precision lost. + For example: +{% tabs unified-types-2 %} +{% tab 'Scala 2 and 3' for=unified-types-2 %} ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) +val y: Float = x.toFloat // 9.8765434E8 (note that some precision is lost in this case) val face: Char = '☺' val number: Int = face // 9786 ``` +{% endtab %} +{% endtabs %} + Casting is unidirectional. This will not compile: -``` +{% tabs unified-types-3 %} +{% tab 'Scala 2 and 3' for=unified-types-3 %} +```scala val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // Does not conform ``` +{% endtab %} +{% endtabs %} + You can also cast a reference type to a subtype. This will be covered later in the tour. diff --git a/_tour/upper-type-bounds.md b/_tour/upper-type-bounds.md index 8c4b5815c7..e49f86dc3f 100644 --- a/_tour/upper-type-bounds.md +++ b/_tour/upper-type-bounds.md @@ -13,6 +13,8 @@ redirect_from: "/tutorials/tour/upper-type-bounds.html" In Scala, [type parameters](generic-classes.html) and [abstract type members](abstract-type-members.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 `PetContainer`: +{% tabs upper-type-bounds class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds %} ```scala mdoc abstract class Animal { def name: String @@ -39,11 +41,47 @@ class PetContainer[P <: Pet](p: P) { val dogContainer = new PetContainer[Dog](new Dog) val catContainer = new PetContainer[Cat](new Cat) ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds %} +```scala +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 = PetContainer[Dog](Dog()) +val catContainer = PetContainer[Cat](Cat()) +``` +{% endtab %} +{% endtabs %} + +{% tabs upper-type-bounds_error class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_error %} ```scala mdoc:fail // this would not compile val lionContainer = new PetContainer[Lion](new Lion) ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_error %} +```scala +// this would not compile +val lionContainer = PetContainer[Lion](Lion()) +``` +{% endtab %} +{% endtabs %} + The `class PetContainer` takes 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]` diff --git a/_tour/variances.md b/_tour/variances.md index bb685dcd96..14f40fb893 100644 --- a/_tour/variances.md +++ b/_tour/variances.md @@ -10,20 +10,34 @@ previous-page: generic-classes redirect_from: "/tutorials/tour/variances.html" --- -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. +Variance lets you control how type parameters behave with regard to subtyping. 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. +{% tabs variances_1 %} +{% tab 'Scala 2 and 3' for=variances_1 %} ```scala mdoc class Foo[+A] // A covariant class class Bar[-A] // A contravariant class class Baz[A] // An invariant class ``` +{% endtab %} +{% endtabs %} -### Covariance +### Invariance -A type parameter `T` of a generic class can be made covariant by using the annotation `+T`. For some `class List[+T]`, making `T` covariant implies that for two types `A` and `B` where `B` is a subtype of `A`, then `List[B]` is a subtype of `List[A]`. This allows us to make very useful and intuitive subtyping relationships using generics. +By default, type parameters in Scala are invariant: subtyping relationships between the type parameters aren't reflected in the parameterized type. To explore why this works the way it does, we look at a simple parameterized type, the mutable box. + +{% tabs invariance_1 %} +{% tab 'Scala 2 and 3' for=invariance_1 %} +```scala mdoc +class Box[A](var content: A) +``` +{% endtab %} +{% endtabs %} -Consider this simple class structure: +We're going to be putting values of type `Animal` in it. This type is defined as follows: +{% tabs invariance_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=invariance_2 %} ```scala mdoc abstract class Animal { def name: String @@ -31,111 +45,178 @@ abstract class Animal { case class Cat(name: String) extends Animal case class Dog(name: String) extends Animal ``` +{% endtab %} +{% tab 'Scala 3' for=invariance_2 %} +```scala +abstract class Animal: + def name: String -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]`. 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 use either of them for in place of `List[Animal]`. +case class Cat(name: String) extends Animal +case class Dog(name: String) extends Animal +``` +{% endtab %} +{% endtabs %} -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. +We can say that `Cat` is a subtype of `Animal`, and that `Dog` is also a subtype of `Animal`. That means that the following is well-typed: +{% tabs invariance_3 %} +{% tab 'Scala 2 and 3' for=invariance_3 %} ```scala mdoc -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")) +val myAnimal: Animal = Cat("Felix") +``` +{% endtab %} +{% endtabs %} -// prints: Whiskers, Tom -printAnimalNames(cats) +What about boxes? Is `Box[Cat]` a subtype of `Box[Animal]`, like `Cat` is a subtype of `Animal`? At first sight, it looks like that may be plausible, but if we try to do that, the compiler will tell us we have an error: -// prints: Fido, Rex -printAnimalNames(dogs) +{% tabs invariance_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=invariance_4 %} +```scala mdoc:fail +val myCatBox: Box[Cat] = new Box[Cat](Cat("Felix")) +val myAnimalBox: Box[Animal] = myCatBox // this doesn't compile +val myAnimal: Animal = myAnimalBox.content ``` +{% endtab %} +{% tab 'Scala 3' for=invariance_4 %} +```scala +val myCatBox: Box[Cat] = Box[Cat](Cat("Felix")) +val myAnimalBox: Box[Animal] = myCatBox // this doesn't compile +val myAnimal: Animal = myAnimalBox.content +``` +{% endtab %} +{% endtabs %} -### Contravariance +Why could this be a problem? We can get the cat from the box, and it's still an Animal, isn't it? Well, yes. But that's not all we can do. We can also replace the cat in the box with a different animal -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 Printer[-A]`, making `A` contravariant implies that for two types `A` and `B` where `A` is a subtype of `B`, `Printer[B]` is a subtype of `Printer[A]`. +{% tabs invariance_5 %} +{% tab 'Scala 2 and 3' for=invariance_5 %} +```scala + myAnimalBox.content = Dog("Fido") +``` +{% endtab %} +{% endtabs %} -Consider the `Cat`, `Dog`, and `Animal` classes defined above for the following example: +There now is a Dog in the Animal box. That's all fine, you can put Dogs in Animal boxes, because Dogs are Animals. But our Animal Box is a Cat Box! You can't put a Dog in a Cat box. If we could, and then try to get the cat from our Cat Box, it would turn out to be a dog, breaking type soundness. -```scala mdoc -abstract class Printer[-A] { - def print(value: A): Unit -} +{% tabs invariance_6 %} +{% tab 'Scala 2 and 3' for=invariance_6 %} +```scala + val myCat: Cat = myCatBox.content //myCat would be Fido the dog! ``` +{% endtab %} +{% endtabs %} -A `Printer[A]` is a simple class that knows how to print out some type `A`. Let's define some subclasses for specific types: +From this, we have to conclude that `Box[Cat]` and `Box[Animal]` can't have a subtyping relationship, even though `Cat` and `Animal` do. -```scala mdoc -class AnimalPrinter extends Printer[Animal] { - def print(animal: Animal): Unit = - println("The animal's name is: " + animal.name) -} +### Covariance -class CatPrinter extends Printer[Cat] { - def print(cat: Cat): Unit = - println("The cat's name is: " + cat.name) -} -``` +The problem we ran in to above, is that because we could put a Dog in an Animal Box, a Cat Box can't be an Animal Box. -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 use a `Printer[Animal]` in place of `Printer[Cat]`, if we wish, and making `Printer[A]` contravariant allows us to do exactly that. +But what if we couldn't put a Dog in the box? Then, we could just get our Cat back out without a problem, and it would adhere to the subtyping relationship. It turns out that that's possible to do. +{% tabs covariance_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=covariance_1 %} ```scala mdoc -def printMyCat(printer: Printer[Cat], cat: Cat): Unit = - printer.print(cat) +class ImmutableBox[+A](val content: A) +val catbox: ImmutableBox[Cat] = new ImmutableBox[Cat](Cat("Felix")) +val animalBox: ImmutableBox[Animal] = catbox // now this compiles +``` +{% endtab %} +{% tab 'Scala 3' for=covariance_1 %} +```scala +class ImmutableBox[+A](val content: A) +val catbox: ImmutableBox[Cat] = ImmutableBox[Cat](Cat("Felix")) +val animalBox: ImmutableBox[Animal] = catbox // now this compiles +``` +{% endtab %} +{% endtabs %} -val catPrinter: Printer[Cat] = new CatPrinter -val animalPrinter: Printer[Animal] = new AnimalPrinter +We say that `ImmutableBox` is *covariant* in `A`, and this is indicated by the `+` before the `A`. -printMyCat(catPrinter, Cat("Boots")) -printMyCat(animalPrinter, Cat("Boots")) -``` +More formally, that gives us the following relationship: given some `class Cov[+T]`, then if `A` is a subtype of `B`, `Cov[A]` is a subtype of `Cov[B]`. This allows us to make very useful and intuitive subtyping relationships using generics. -The output of this program will be: +In the following less contrived 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. +{% tabs covariance_2 %} +{% tab 'Scala 2 and 3' for=covariance_2 %} +```scala mdoc +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")) + +// prints: Whiskers, Tom +printAnimalNames(cats) + +// prints: Fido, Rex +printAnimalNames(dogs) ``` -The cat's name is: Boots -The animal's name is: Boots -``` +{% endtab %} +{% endtabs %} -### Invariance +### Contravariance -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. +We've seen we can accomplish covariance by making sure that we can't put something in the covariant type, but only get something out. What if we had the opposite, something you can put something in, but can't take out? This situation arises if we have something like a serializer, that takes values of type A, and converts them to a serialized format. +{% tabs contravariance_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=contravariance_1 %} ```scala mdoc -class Container[A](value: A) { - private var _value: A = value - def getValue: A = _value - def setValue(value: A): Unit = { - _value = value - } +abstract class Serializer[-A] { + def serialize(a: A): String } + +val animalSerializer: Serializer[Animal] = new Serializer[Animal] { + def serialize(animal: Animal): String = s"""{ "name": "${animal.name}" }""" +} +val catSerializer: Serializer[Cat] = animalSerializer +catSerializer.serialize(Cat("Felix")) ``` +{% endtab %} +{% tab 'Scala 3' for=contravariance_1 %} +```scala +abstract class Serializer[-A]: + def serialize(a: A): String -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 animalSerializer: Serializer[Animal] = new Serializer[Animal](): + def serialize(animal: Animal): String = s"""{ "name": "${animal.name}" }""" +val catSerializer: Serializer[Cat] = animalSerializer +catSerializer.serialize(Cat("Felix")) ``` -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 -``` +{% endtab %} +{% endtabs %} -Fortunately, the compiler stops us long before we could get this far. +We say that `Serializer` is *contravariant* in `A`, and this is indicated by the `-` before the `A`. A more general serializer is a subtype of a more specific serializer. -### Other Examples +More formally, that gives us the reverse relationship: given some `class Contra[-T]`, then if `A` is a subtype of `B`, `Contra[B]` is a subtype of `Contra[A]`. -Another example that can help one understand variance is `trait Function1[-T, +R]` from the Scala standard library. `Function1` represents a function with one parameter, where the first type parameter `T` represents the parameter type, and the second type parameter `R` represents the return type. A `Function1` is contravariant over its parameter type, and covariant over its return type. For this example we'll use the literal notation `A => B` to represent a `Function1[A, B]`. +### Immutability and Variance +Immutability constitutes an important part of the design decision behind using variance. For example, Scala's collections systematically distinguish between [mutable and immutable collections](https://docs.scala-lang.org/overviews/collections-2.13/overview.html). The main issue is that a covariant mutable collection can break type safety. This is why `List` is a covariant collection, while `scala.collection.mutable.ListBuffer` is an invariant collection. `List` is a collection in package `scala.collection.immutable`, therefore it is guaranteed to be immutable for everyone. Whereas, `ListBuffer` is mutable, that is, you can change, add, or remove elements of a `ListBuffer`. -Assume the similar `Cat`, `Dog`, `Animal` inheritance tree used earlier, plus the following: +To illustrate the problem of covariance and mutability, suppose that `ListBuffer` was covariant, then the following problematic example would compile (in reality it fails to compile): -```scala mdoc -abstract class SmallAnimal extends Animal -case class Mouse(name: String) extends SmallAnimal +{% tabs immutability_and_variance_2 %} +{% tab 'Scala 2 and 3' %} +```scala mdoc:fail +import scala.collection.mutable.ListBuffer + +val bufInt: ListBuffer[Int] = ListBuffer[Int](1,2,3) +val bufAny: ListBuffer[Any] = bufInt +bufAny(0) = "Hello" +val firstElem: Int = bufInt(0) ``` +{% endtab %} +{% endtabs %} + +If the above code was possible then evaluating `firstElem` would fail with `ClassCastException`, because `bufInt(0)` now contains a `String`, not an `Int`. -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 a `SmallAnimal`. Since we can safely and invisibly substitute the former with the latter, we can say `Animal => Mouse` is a subtype of `Cat => SmallAnimal`. +The invariance of `ListBuffer` means that `ListBuffer[Int]` is not a subtype of `ListBuffer[Any]`, despite the fact that `Int` is a subtype of `Any`, and so `bufInt` cannot be assigned as the value of `bufAny`. ### 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). + +Scala's tendency towards immutable types makes it that covariant and contravariant types are more common than in other languages, since a mutable generic type must be invariant. diff --git a/_uk/cheatsheets/index.md b/_uk/cheatsheets/index.md new file mode 100644 index 0000000000..24412df349 --- /dev/null +++ b/_uk/cheatsheets/index.md @@ -0,0 +1,624 @@ +--- +layout: cheatsheet +title: Scala Cheatsheet + +partof: cheatsheet + +by: Dmytro Kazanzhy +about: Ця шпаргалка створена завдяки Brendan O'Connor, та призначена для швидкого ознайомлення з синтаксичними конструкціями Scala. Ліцензовано Brendan O'Connor за ліцензією CC-BY-SA 3.0. + +language: uk +--- + +###### Contributed by {{ page.by }} + +{{ page.about }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
змінні
var x = 5

Вірно
x = 6
Змінна.
val x = 5

Невірно
x = 6
Константа (значення).
var x: Double = 5
Явне вказання типу.
функції
Вірно
def f(x: Int) = { x * x }

Невірно
def f(x: Int)   { x * x }
Визначення функції.
Прихована помилка: без = це процедура, що повертає Unit та може ввести в оману. Не підтримується зі Scala 2.13.
Вірно
def f(x: Any) = println(x)

Невірно
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)
Анонімна функція: щоб використати аргумент двічі, треба його назвати. Зліва від => задається ім'я змінної, якій буде присвоєно аргумент та яку можна використати справа.
(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(_+_)
Varargs (аргументи змінної довжини).
пакети
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

Пакет в певних межах:
package pkg {
+  ...
+}

Пакет одиночка (singleton):
package object pkg {
+  ...
+}
Оголошення пакету.
структури даних
(1, 2, 3)
Кортеж (Tuple). Трансформується у виклик Tuple3.
var (x, y, z) = (1, 2, 3)
Деструктивна прив'язка: кортеж розпаковується через зіставлення зі зразком (pattern matching).
Невірно
var x, y, z = (1, 2, 3)
Прихована помилка: кожна змінна прив'язана до всього кортежу.
var xs = List(1, 2, 3)
Список (імутабельний, тобто такий, що не змінюється).
xs(2)
Індексація через дужки (slides).
1 :: List(2, 3)
Додавання елементу до голови списку.
1 to 5
так само, як і
1 until 6

1 to 10 by 2
Синтаксичний цукор для діапазонів.
()
Пусті дужки це єдине значення для типу Unit.
Еквівалентно до void у C та Java.
управляючі конструкти
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).
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) {
+  val div = x / y.toFloat
+  println("%d/%d = %.1f".format(x, y, div))
+}
Цикл for: імперативізм.
стильsprintf.
for (i <- 1 to 5) {
+  println(i)
+}
Цикл for: ітерація з включенням верхньої межі.
for (i <- 1 until 5) {
+  println(i)
+}
Цикл for: ітерація без включення верхньої межі.
зіставлення із зразком (pattern matching)
Вірно
(xs zip ys) map {
+  case (x, y) => x * y
+}

Невірно
(xs zip ys) map {
+  (x, y) => x * y
+}
Для зіставлення зі зразком необхідно використати case перед аргументами анонімної функції.
Невірно
+
val v42 = 42
+24 match {
+  case v42 => println("42")
+  case _   => println("Not 42")
+}
v42 буде інтерпретовано як ім'я змінної у зразку, яка буде вірно зіставлена з будь-яким Int значенням, і буде виведено “42”.
Вірно
+
val v42 = 42
+24 match {
+  case `v42` => println("42")
+  case _     => println("Not 42")
+}
`v42` у зворотних галочках буде інтерпретовано як значення наявної змінної v42, і буде виведено “Not 42”.
Вірно
+
val UppercaseVal = 42
+24 match {
+  case UppercaseVal => println("42")
+  case _            => println("Not 42")
+}
UppercaseVal буде інтерпретовано так само як наявна змінна, а не нова змінна в патерні. Тому значення, що міститься в UppercaseVal буде порівняно з 24, і буде виведено “Not 42”.
об'єктна орієнтація
class C(x: R)
Параметри конструктора - тільки x доступний в тілі класу.
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)
+}
Тіло класу є конструктором.
Оголосити відкритий (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 { ... }
Визначити єдиний екземпляр (singleton).
trait T { ... }

class C extends T { ... }

class C extends D with T { ... }
Риси - трейти (traits).
Інтерфейси-з-імплементацією. У трейту немає параметрів конструктора. композиція з домішками (mixin).
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 = ...}
При реалізації вже наявного методу необхідно вказати overrides.
new java.io.File("f")
Створення об'єкту.
Невірно
new List[Int]

Вірно
List(1, 2, 3)
Помилка типу: абстрактний тип.
Натомість, існує конвенція у таких випадках використовувати фабричний метод обʼєкту компаньйону, що приховує конкретний тип.
classOf[String]
Літерал класу (Class[String] = class java.lang.String).
x.isInstanceOf[String]
Перевірка типу під час виконання (runtime).
x.asInstanceOf[String]
Приведення типу під час виконання (runtime).
x: String
Приписування типу під час компіляції (compile time).
опції (options)
Some(42)
Конструктор для непустого опціонального значення (тип Some[T]).
None
Одинак (Singleton) пустого опціонального значення (тип None).
Option(null) == None
+Option(24) == Some(24)
+
obj.unsafeMethod // number or null
+Option(obj.unsafeMethod) // Some or None
+ проте +
Some(null) != None
Null-safe фабрика опціональних значень.
val optStr: Option[String] = None
+ так само, як і +
val optStr = Option.empty[String]
Явна типізація опціонального значення.
Фабричний метод для створення пустих опціональних значень.
val name: Option[String] =
+  request.getParameter("name")
+val upper = name.map {
+  _.trim
+} filter {
+  _.length != 0
+} map {
+  _.toUpperCase
+}
+println(upper.getOrElse(""))
Конвеєрний стиль.
val upper = for {
+  name <- request.getParameter("name")
+  trimmed <- Some(name.trim)
+    if trimmed.length != 0
+  upper <- Some(trimmed.toUpperCase)
+} yield upper
+println(upper.getOrElse(""))
Синтаксис for-виразу.
option.map(f(_))
+ так само, як і +
option match {
+  case Some(x) => Some(f(x))
+  case None    => None
+}
Застосування функції до опціонального значення.
option.flatMap(f(_))
+ так само, як і +
option match {
+  case Some(x) => f(x)
+  case None    => None
+}
Так само, як і mapб але функція має повернути опціональне значення.
optionOfOption.flatten
+ так само, як і +
optionOfOption match {
+  case Some(Some(x)) => Some(x)
+  case _             => None
+}
Вилучення вкладених опціональних значень.
option.foreach(f(_))
+ так само, як і +
option match {
+  case Some(x) => f(x)
+  case None    => ()
+}
Застосувати процедуру на опціональному значенні.
option.fold(y)(f(_))
+ так само, як і +
option match {
+  case Some(x) => f(x)
+  case None    => y
+}
Застосувати функцію на опціональному значенні та повернути значення, якщо воно порожнє.
option.collect {
+  case x => ...
+}
+ так само, як і +
option match {
+  case Some(x) if f.isDefinedAt(x) => ...
+  case Some(_)                     => None
+  case None                        => None
+}
Виконати часткове зіставлення зі зразком опціонального значення.
option.isDefined
+ так само, як і +
option match {
+  case Some(_) => true
+  case None    => false
+}
true якщо не порожнє.
option.isEmpty
+ так само, як і +
option match {
+  case Some(_) => false
+  case None    => true
+}
true якщо порожнє.
option.nonEmpty
+ так само, як і +
option match {
+  case Some(_) => true
+  case None    => false
+}
true якщо не порожнє.
option.size
+ так само, як і +
option match {
+  case Some(_) => 1
+  case None    => 0
+}
0 якщо порожнє, інакше 1.
option.orElse(Some(y))
+ так само, як і +
option match {
+  case Some(x) => Some(x)
+  case None    => Some(y)
+}
Обчислити та повернути альтернативне опціональне значення, якщо порожнє.
option.getOrElse(y)
+ так само, як і +
option match {
+  case Some(x) => x
+  case None    => y
+}
Обчислити та повернути значення за замовчуванням, якщо порожнє.
option.get
+ так само, як і +
option match {
+  case Some(x) => x
+  case None    => throw new Exception
+}
Повернути значення, або згенерувати виключення, якщо порожнє.
option.orNull
+ так само, як і +
option match {
+  case Some(x) => x
+  case None    => null
+}
Повернути значення, null якщо порожнє.
option.filter(f)
+ так само, як і +
option match {
+  case Some(x) if f(x) => Some(x)
+  case _               => None
+}
Фільтрація опціонального значення. Повернути значення, якщо предикат істинний.
option.filterNot(f(_))
+ так само, як і +
option match {
+  case Some(x) if !f(x) => Some(x)
+  case _                => None
+}
Фільтрація опціонального значення. Повернути значення, якщо предикат хибний.
option.exists(f(_))
+ так само, як і +
option match {
+  case Some(x) if f(x) => true
+  case Some(_)         => false
+  case None            => false
+}
Повернути значення предикату на опціональному значенні або false якщо порожнє.
option.forall(f(_))
+ так само, як і +
option match {
+  case Some(x) if f(x) => true
+  case Some(_)         => false
+  case None            => true
+}
Повернути значення предикату на опціональному значенні або true якщо порожнє..
option.contains(y)
+ так само, як і +
option match {
+  case Some(x) => x == y
+  case None    => false
+}
Перевіряє чи дорівнює опціональне значення параметру, false якщо порожнє.
diff --git a/_uk/getting-started/install-scala.md b/_uk/getting-started/install-scala.md new file mode 100644 index 0000000000..d8ae3efbd9 --- /dev/null +++ b/_uk/getting-started/install-scala.md @@ -0,0 +1,221 @@ +--- +layout: singlepage-overview +title: Перші кроки +partof: getting-started +language: uk +includeTOC: true +redirect_from: + - /uk/scala3/getting-started.html # we deleted the scala 3 version of this page +--- + +Інструкції нижче стосуються як Scala 2 так, і та Scala 3. + +## Спробуйте Scala без інсталяції + +Щоб швидко почати експериментувати зі Scala, відкрийте “Scastie” у вашому браузері. +_Scastie_ це онлайн “пісочниця”, де ви можете експериментувати з прикладами на Scala та подивитись як все працює, з доступом до всіх компіляторів Scala та доступних бібліотек. + +> Scastie підтримує як Scala 2 так, і Scala 3, але за замовчування +> використовується Scala 3. Якщо ж ви шукаєте приклади на Scala 2, +> [натисніть тут](https://scastie.scala-lang.org/MHc7C9iiTbGfeSAvg8CKAA). + +## Встановіть Scala на ваш комп'ютер + +Інсталяція Scala означає встановлення різних command-line інструментів, таких як компілятор Scala та інструменти для збірки. +Ми радимо використовувати інсталятор "Coursier", який автоматично встановить всі необхідні залежності, але ви можете встановити окремо кожен інструмент. + +### За допомогою інсталятора Scala (рекомендовано) + +Інсталятор Scala називається [Coursier](https://get-coursier.io/docs/cli-overview), а його основна команда має назву `cs`. +Він гарантує, що JVM та стандартні інструменти Scala встановлені на вашій системі. +Щоб встановити його на вашій системі виконайте наступні інструкції. + + +{% tabs install-cs-setup-tabs class=platform-os-options %} + + +{% tab macOS for=install-cs-setup-tabs %} +Виконайте наступну команду в терміналі, виконуючи всі спливаючі інструкції: +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-brew %} +{% altDetails cs-setup-macos-nobrew "Якщо ви не використовуєте Homebrew:" %} +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.macOS-x86-64 %} +{% endaltDetails %} +{% endtab %} + + + +{% tab Linux for=install-cs-setup-tabs %} +Виконайте наступну команду в терміналі, виконуючи всі спливаючі інструкції: +{% include code-snippet.html language='bash' codeSnippet=site.data.setup-scala.linux-x86-64 %} +{% endtab %} + + + +{% tab Windows for=install-cs-setup-tabs %} +Завантажте та запустіть [the Scala installer for Windows]({{site.data.setup-scala.windows-link}}) +інсталятор на основі Coursier, виконуючи всі спливаючі інструкції. +{% endtab %} + + + +{% tab Other for=install-cs-setup-tabs defaultTab %} + +Дотримуйтесь документації від Coursier з того, +[як встановити і запустити `cs setup`](https://get-coursier.io/docs/cli-installation). +{% endtab %} + + +{% endtabs %} + + + +{% altDetails testing-your-setup 'Перевірити налаштування' %} +Перевірте ваші налаштування виконавши команду `scala -version`, яка має вивести: +```bash +$ scala -version +Scala code runner version: 1.4.3 +Scala version (default): {{site.scala-3-version}} +``` +Якщо це не спрацювало, необхідно завершити сеанс та зайти в систему знову (або перезавантажити), щоб зміни застосувались на вашій системі. +{% endaltDetails %} + + + +Разом з менеджментом JVM-ів, `cs setup` також встановлює корисні command-line інструменти: + +| Команда | Опис | +|---------------|----------------------------------------------------------------------------------------| +| `scalac` | компілятор Scala | +| `scala` | інтерактивне середовище Scala та інструмент для запуску скриптів | +| `scala-cli` | [Scala CLI](https://scala-cli.virtuslab.org), інтерактивні інструменти для Scala | +| `sbt`, `sbtn` | Інструмент збірки [sbt](https://www.scala-sbt.org/) | +| `amm` | [Ammonite](https://ammonite.io/) розширене інтерактивне середовище (REPL) | +| `scalafmt` | [Scalafmt](https://scalameta.org/scalafmt/) призначений для форматування коду на Scala | + +Для більш детальної інформації про `cs`, прочитайте +[документацію coursier-cli](https://get-coursier.io/docs/cli-overview). + +> `cs setup` встановлює компілятор Scala 3 та інтерактивне середовище за замовчування (команди `scalac` та +> `scala` відповідно). Незалежно від того, чи збираєтеся ви використовувати Scala 2 чи 3, +> тому що більшість проєктів використовує інструменти для збірки, +> які використовують правильні версії Scala незалежно від того, яка встановлена "глобально". +> Однак, ви завжди можете запустити певну версію Scala за допомогою +> ``` +> $ cs launch scala:{{ site.scala-version }} +> $ cs launch scalac:{{ site.scala-version }} +> ``` +> Якщо ви надаєте перевагу Scala 2 за замовчуванням, ви можете примусово встановити певну версію: +> ``` +> $ cs install scala:{{ site.scala-version }} scalac:{{ site.scala-version }} +> ``` + +### ...або вручну + +Вам необхідно лише два інструменти, для того, щоб скомпілювати, запустити, протестувати й упакувати Scala проєкт: Java 8 або 11, і sbt. +Щоб встановити їх вручну: + +1. Якщо Java 8 або 11 не встановлені, необхідно завантажити + Java з [Oracle Java 8](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html), [Oracle Java 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), + або [AdoptOpenJDK 8/11](https://adoptopenjdk.net/). Перевірте [сумісність JDK](/overviews/jdk-compatibility/overview.html) для Scala/Java. +1. Встановіть [sbt](https://www.scala-sbt.org/download.html) + +## Створити проєкт "Hello World" з sbt + +Після встановлення sbt ви готові до створення проєкту на Scala, який ми розглянемо в подальших розділах. + +Щоб створити проєкт, ви можете використати або термінал, або IDE. +Якщо ви знайомі з командним рядком, ми рекомендуємо такий підхід. + +### За допомогою командного рядка + +Інструмент sbt призначений для збірки проєкту на Scala. sbt компілює, запускає, +та тестує ваш код на Scala. (Також він публікує бібліотеки та виконує багато інших задач.) + +Щоб створити новий Scala проєкт за допомогою sbt: + +1. Перейдіть (`cd`) в пусту директорію. +1. Виконайте команду `sbt new scala/scala3.g8`, щоб створити проєкт на Scala 3, або `sbt new scala/hello-world.g8`, щоб створити проєкт на Scala 2. + Команда завантажує шаблон проєкту з GitHub. + Також, створює директорію `target`, яку ви можете проігнорувати. +1. Коли буде запропоновано, оберіть назву програми `hello-world`. В результаті буде створено проєкт "hello-world". +1. Подивимося, що щойно було створено: + +``` +- hello-world + - project (sbt uses this for its own files) + - build.properties + - build.sbt (sbt's build definition file) + - src + - main + - scala (весь ваш код на Scala буде тут) + - Main.scala (Точка входу в програму) <-- це все, що потрібно наразі +``` + +Більше документації про sbt можна знайти у [Книзі по Scala](/scala3/book/tools-sbt.html) (див. [тут](/overviews/scala-book/scala-build-tool-sbt.html) версію для Scala 2) +та в офіційній [документації](https://www.scala-sbt.org/1.x/docs/index.html) sbt + +### За допомогою IDE + +Ви можете пропустити подальші кроки та перейти до [Створення Scala проєкту з IntelliJ і sbt](/uk/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.html) + + +## Відкрити проєкт hello-world + +Використаймо IDE, щоб відкрити проєкт. Найбільш популярними є IntelliJ та VSCode. +Обидва з них мають багатий функціонал, але ви також можете використати [багато інших редакторів.](https://scalameta.org/metals/docs/editors/overview.html) + +### За допомогою IntelliJ + +1. Завантажте та встановіть [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Встановіть плагін Scala дотримуючись [інструкції з встановлення плагінів в IntelliJ](https://www.jetbrains.com/help/idea/managing-plugins.html) +1. Відкрийте файл `build.sbt` та оберіть *Відкрити як проєкт* (*Open as a project*) + +### За допомогою VSCode та metals + +1. Завантажте [VSCode](https://code.visualstudio.com/Download) +1. Встановіть розширення Metals з [the Marketplace](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) +1. Відкрийте директорію, що містить файл `build.sbt` (це має бути директорія `hello-world` якщо ви виконали попередні інструкції). Коли буде запропоновано, оберіть *Імпортувати збірку* (*Import build*). + +>[Metals](https://scalameta.org/metals) це “Сервер мови Scala” який забезпечує можливість написання коду на Scala в VS Code та інших редакторах на кшталт [Atom, Sublime Text, and more](https://scalameta.org/metals/docs/editors/overview.html), використовуючи Language Server Protocol. +> +> Під капотом, Metals комунікує з інструментом збірки використовуючи +> [Build Server Protocol (BSP)](https://build-server-protocol.github.io/). Більш детально про те, як працює Metals, можна подивитись на [“Write Scala in VS Code, Vim, Emacs, Atom and Sublime Text with Metals”](https://www.scala-lang.org/2019/04/16/metals.html). + +### Внесення змін в початковий код + +Перегляньте ці два файли у вашому IDE: + +- _build.sbt_ +- _src/main/scala/Main.scala_ + +Коли ви будете запускати ваш проєкт у наступному кроці, то будуть використані конфігурації з _build.sbt_ для запуску коду в _src/main/scala/Main.scala_. + +## Запустити Hello World + +Якщо вам зручно користуватися IDE, ви можете запустити код в _Main.scala_ з вашого IDE. + +В іншому випадку ви можете запустити програму через термінал, виконавши такі дії: + +1. `cd` в `hello-world`. +1. Запустіть `sbt`. Це відкриє консоль sbt. +1. Наберіть `~run`. Символ `~` опціональний і змушує sbt повторно запускатися після кожного збереження файлу, + що забезпечує швидкий цикл редагування/запуск/налагодження. sbt також створить директорію `target`, яку ви можете проігнорувати. + +Коли ви закінчите експериментувати з вашим проєктом, натисніть `[Enter]` щоб перервати команду `run`. +Потім наберіть `exit` або затисніть `[Ctrl+D]` щоб вийти з sbt та повернутись до вашого командного рядка. + +## Наступні кроки + +Після того, як ви закінчите наведені вище посібники, спробуйте пройти: + +* [Книга по Scala](/scala3/book/introduction.html) (версія по Scala 2 [тут](/overviews/scala-book/introduction.html)), яка містить коротких ознайомчих уроків з основних можливостей Scala. +* [Тур по Scala](/tour/tour-of-scala.html) for bite-sized introductions to Scala's features. +* [Навчальні ресурси](/online-courses.html), що містять інтерактивні онлайн путівники та курси. +* [Наш список деяких популярних книжок по Scala](/books.html). +* [Посібник з міграції](/scala3/guides/migration/compatibility-intro.html) допомагає перевести ваш наявний проєкт зі Scala 2 на Scala 3. + +## Отримати допомогу +Існує безліч поштових розсилок та чатів в режимі реального часу, якщо ви захочете зв'язатися з іншими користувачами Scala. Перейдіть на сторінку нашої [спільноти](https://scala-lang.org/community/), щоб побачити перелік можливих способів та попросити про допомогу. + diff --git a/_uk/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md b/_uk/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md new file mode 100644 index 0000000000..9a9d303f47 --- /dev/null +++ b/_uk/getting-started/intellij-track/building-a-scala-project-with-intellij-and-sbt.md @@ -0,0 +1,100 @@ +--- +title: Створення проєкту на Scala з IntelliJ і sbt +layout: singlepage-overview +partof: building-a-scala-project-with-intellij-and-sbt +language: uk +disqus: true +previous-page: /uk/getting-started/intellij-track/getting-started-with-scala-in-intellij +next-page: /uk/testing-scala-in-intellij-with-scalatest +--- + +В цьому посібнику ми побачимо як будувати Scala проєкти використовуючи [sbt](https://www.scala-sbt.org/1.x/docs/index.html). +sbt — популярний інструмент для компіляції, запуску та тестування проєктів Scala будь-якої складності. +Використання інструменту збірки, такого як sbt (або Maven/Gradle), стає необхідним, коли ви створюєте проєкти із залежностями або кількома файлами коду. +Ми припускаємо, що ви завершили [перший посібник](./getting-started-with-scala-in-intellij.html). + +## Створення проєкту +У цьому розділі ми покажемо вам, як створити проєкт в IntelliJ. Однак, якщо вам +комфортніше працювати у терміналі, ми рекомендуємо подивитись [початок роботи зі Scala і sbt у командному рядку](/uk/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.html) +і потім повернутися сюди до розділу «Написання коду на Scala». + +1. Якщо ви ще не створили проєкт у терміналі, запустіть IntelliJ та оберіть "Створити новий проєкт (Create New Project)" + * На панелі зліва оберіть Scala, а на панелі справа оберіть sbt + * Натисніть **Next** + * Назвіть ваш проєкт "SbtExampleProject" +1. Якщо ви вже створили проєкт через термінал, запустіть IntelliJ, оберіть *Імпортувати проєкт (Import Project)* та відкрийте файл `build.sbt` вашого проєкту +1. Впевніться, що **версія JDK** 1.8 або вище, та **версія sbt** 0.13.13 та вище +1. Натисніть **Use auto-import**, щоб залежності автоматично завантажились +1. Натисніть **Finish** + +## Розуміння структури директорій +Завдяки sbt створюються директорії, які можуть бути корисні у разі розробки складніших проєктів. +Поки що ви можете проігнорувати більшість із них, але ось для чого це все: + +``` +- .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) +``` + + +## Написання коду на Scala +1. На панелі **Project** зліва розкрийте `SbtExampleProject` => `src` => `main` +1. Натисніть праву кнопку миші, `scala` та оберіть **New** => **Package** +1. Назвіть пакет `example` та натисніть **OK** (або просто натисніть клавішу Enter або Return). +1. Натисніть праву кнопку миші на пакет `example` та оберіть **New** => **Scala class** (якщо ви не бачите цю опцію, натисніть праву кнопку миші на `SbtExampleProject`, натисніть **Add Frameworks Support**, оберіть **Scala** та продовжить) +1. Назвіть клас `Main` та змініть **Kind** на `Object`. +1. Змініть код у класі на наступний: + +``` +@main def run() = + val ages = Seq(42, 75, 29, 64) + println(s"The oldest person is ${ages.max}") +``` + +Примітка: IntelliJ має власну реалізацію компілятора Scala, тому іноді ваш код є правильним, навіть якщо IntelliJ вказує інше. +Ви завжди можете перевірити у командному рядку, чи може sbt запустити ваш проєкт. + +## Запуск проєкту +1. З меню **Run** оберіть **Edit configurations** +1. Натисніть кнопку **+** та оберіть **sbt Task**. +1. Назвіть його `Run the program`. +1. В полі **Tasks** наберіть `~run`. Опція `~` змушує sbt перебудовувати та перезапускати проєкт, коли ви зберігаєте зміни у файлі проєкту. +1. Натисніть **OK**. +1. В меню **Run** натисніть **Run 'Run the program'**. +1. В коді змініть `75` на `61` та подивіться оновлений результат в консолі. + +## Додавання залежностей +Давайте ненадовго змістимо фокус на використання опублікованих бібліотек для забезпечення додаткової функціональності ваших програм. +1. Відкрийте `build.sbt` та додайте наступний рядок: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +``` + +Тут `libraryDependencies` є набором залежностей та використовуючи `+=`, +ми додаємо залежність [scala-parser-combinators](https://github.com/scala/scala-parser-combinators) до набору залежностей, +які необхідні для sbt та які завантажаться при його запуску. Тепер в будь-якому Scala файлі ви можете використати +класи, об'єкти тощо з scala-parser-combinators через звичайний "import". + +Більше опублікованих бібліотек можна знайти на +[Scaladex](https://index.scala-lang.org/) - індекс бібліотек Scala, місце куди ви можете зайти, щоб скопіювати інформацію про бібліотеку +та додати у ваш `build.sbt` файл. + +## Наступні кроки + +Перейдіть до наступного навчального матеріалу з серії _початок роботи з IntelliJ_, та дізнайтесь про [тестування Scala в IntelliJ зі ScalaTest](testing-scala-in-intellij-with-scalatest.html). + +**або** + +* [Книга по Scala](/scala3/book/introduction.html), що є набором коротких вступних уроків з основних особливостей. +* [Тур по Scala](/tour/tour-of-scala.html) серія коротких оглядових статей про можливості Scala. +* Продовжить вчити Scala інтерактивно виконуючи + [вправи зі Scala](https://www.scala-exercises.org/scala_tutorial). \ No newline at end of file diff --git a/_uk/getting-started/intellij-track/getting-started-with-scala-in-intellij.md b/_uk/getting-started/intellij-track/getting-started-with-scala-in-intellij.md new file mode 100644 index 0000000000..2d44b3ef34 --- /dev/null +++ b/_uk/getting-started/intellij-track/getting-started-with-scala-in-intellij.md @@ -0,0 +1,77 @@ +--- +title: Перші кроки зі Scala в IntelliJ +layout: singlepage-overview +partof: getting-started-with-scala-in-intellij +language: uk +disqus: true +next-page: /uk/building-a-scala-project-with-intellij-and-sbt +--- + +У цьому посібнику буде розглянемо як створити мінімальний проєкт Scala за допомогою IntelliJ IDE з плагіном Scala. +У цьому посібнику IntelliJ завантажить для вас Scala. + +## Встановлення +1. Впевніться, що ви вже встановили Java 8 JDK (також відому як 1.8) + * Запустіть `javac -version` у командному рядку і впевніться, що бачите + `javac 1.8.___` + * Якщо у вас не встановлена версія 1.8 або вище, [встановіть JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +1. Далі, завантажте та встановіть [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Після того, як ви запустили IntelliJ, ви можете завантажити й встановити плагін Scala за інструкцією + [як встановлювати плагіни IntelliJ](https://www.jetbrains.com/help/idea/installing-updating-and-uninstalling-repository-plugins.html) (шукайте "Scala" в меню плагінів.) + +Під час створення проєкту встановиться остання версія Scala. +Примітка: якщо ви хочете відкрити наявний проєкт Scala, ви можете натиснути **Open** під час запуску IntelliJ. + +## Створення проєкту +1. Запустіть IntelliJ та натисніть **File** => **New** => **Project** +1. На панелі зліва оберіть Scala, а на панелі справа - IDEA. +1. Назвіть проєкт **HelloWorld** +1. Припускаємо, що ви вперше створюєте проєкт Scala за допомогою IntelliJ, + вам потрібно буде встановити Scala SDK. В полі праворуч від Scala SDK натисніть **Create**. +1. Оберіть останню версію (наприклад, {{ site.scala-version }}) та натисніть **Download**. Це може зайняти декілька хвилин, але наступні проєкти зможуть використати той же SDK. +1. Після того як створена SDK та ви повернулись до вікна "New Project", натисніть **Finish**. + + +## Написання коду + +1. Зліва, на панелі **Project** клацніть кнопкою миші на `src` та оберіть **New** => **Scala class**. + Якщо ви не бачите **Scala class**, клацніть правою кнопкою миші на **HelloWorld** та оберіть **Add Framework Support...**, натисніть **Scala** та продовжить. + Якщо ви бачите **Error: library is not specified**, ви можете або натиснути на кнопку завантаження або обрати шлях бібліотеки вручну. + Якщо ви бачите тільки **Scala Worksheet** спробуйте розкрити директорію `src` та піддиректорію `main` та клацніть правою кнопкою миші на теку `scala`. +1. Назвіть клас `Hello` та змініть його **Kind** на `object`. +1. Змініть код класу на наступний: + +``` +object Hello extends App { + println("Hello, World!") +} +``` + +## Запуск +* Клацніть правою кнопкою миші `Hello` та оберіть **Run 'Hello'**. +* Готово! + +## Експерименти зі Scala +Хорошим способом випробувати код є Scala Worksheets + +1. Зліва, на панелі проєкту, клацніть правою кнопкою миші на `src` та оберіть **New** => **Scala Worksheet**. +2. Назвіть робочий лист Scala "Mathematician". +3. Впишіть наступний код в робочий лист: + +``` +def square(x: Int) = x * x + +square(2) +``` + +Коли ви змінюєте свій код, ви побачите, як він виконується на панелі справа. +Якщо ви не бачите правої панелі, клацніть правою кнопкою миші на робочому аркуші Scala на панелі проєкту та натисніть Evaluate Worksheet. + + +## Наступні кроки + +Тепер ви знаєте, як створити простий проєкт Scala, який можна використовувати, +щоб почати вивчати мову. У наступному уроці ми познайомимося з важливим інструментом збірки під назвою sbt, +який можна використовувати як для простих проєктів, так і продакшн програм. + +Наступне: [Створення проєкту на Scala з IntelliJ і sbt](building-a-scala-project-with-intellij-and-sbt.html) diff --git a/_uk/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md b/_uk/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md new file mode 100644 index 0000000000..b2e1a9f801 --- /dev/null +++ b/_uk/getting-started/intellij-track/testing-scala-in-intellij-with-scalatest.md @@ -0,0 +1,70 @@ +--- +title: Тестування Scala в IntelliJ зі ScalaTest +layout: singlepage-overview +partof: testing-scala-in-intellij-with-scalatest +language: uk +disqus: true +previous-page: /uk/building-a-scala-project-with-intellij-and-sbt +--- + +Існує кілька бібліотек і методологій тестування для Scala, +але в цьому посібнику ми продемонструємо один популярний варіант для фреймворку ScalaTest, +що називається [FunSuite](https://www.scalatest.org/getting_started_with_fun_suite). + +Ми припускаємо, що ви знаєте [як створити проєкт з IntelliJ](building-a-scala-project-with-intellij-and-sbt.html). + +## Налаштування +1. Створіть sbt проєкт в IntelliJ. +1. Додайте залежність ScalaTest: + 1. Додайте залежність ScalaTest у файл `build.sbt` вашого проєкту: + ``` + libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test + ``` + 1. Ви побачите сповіщення "build.sbt was changed", оберіть **auto-import**. + 1. Ці дві дії призведуть до того, що `sbt` завантажить бібліотеку ScalaTest. + 1. Зачекайте завершення синхронізації `sbt`; інакше `AnyFunSuite` та `test()` не розпізнаються. +1. На панелі проєкту розкрийте `src` => `main`. +1. Клацніть правою кнопкою миші на `scala` та оберіть **New** => **Scala class**. +1. Назвіть його `CubeCalculator` та змініть **Kind** на `object` та натисніть Enter або двічі клацніть на `object`. +1. Замініть код на наступний: + ``` + object CubeCalculator: + def cube(x: Int) = + x * x * x + ``` + +## Створення тесту +1. Зліва на панелі проєкту розкрийте `src` => `test`. +1. Клацніть правою кнопкою миші на `scala` та оберіть **New** => **Scala class**. +1. Назвіть клас `CubeCalculatorTest` та натисніть Enter або двічі клацніть на `class`. +1. Замініть код на наступний: + ``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite: + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } + ``` +1. У початковому коді клацніть правою кнопкою миші на `CubeCalculatorTest` та оберіть **Run 'CubeCalculatorTest'**. + +## Розуміння коду + +Переглянемо кожний рядок окремо. + +* `class CubeCalculatorTest` означає, що ми тестуємо об'єкт `CubeCalculator` +* `extends AnyFunSuite` використовуємо функціональність класу AnyFunSuite з ScalaTest, насамперед функцію `test` +* `test` функція з AnyFunSuite, що збирає результати тверджень (assertions) у тілі функції. +* `"CubeCalculator.cube"` назва тесту. Ви можете обрати будь-яку назву, але існує домовленість називати "ClassName.methodName". +* `assert` приймає булеву умову і визначає, пройшов тест чи не пройшов. +* `CubeCalculator.cube(3) === 27` перевіряє чи дорівнює результат функції `cube` значенню 27. + Оператор `===` є частиною ScalaTest та надає чисті повідомлення про помилки. + +## Додати інший тест-кейс +1. Додайте інший тестовий блок з власним `assert`, що перевіряє значення куба `0`. +1. Виконайте `sbt test` знову, двічі клацнувши правою кнопкою миші на `CubeCalculatorTest` та обравши 'Run **CubeCalculatorTest**'. + +## Висновок +Ви побачили один шлях тестування вашого Scala коду. Більше про +FunSuite ScalaTest на [офіційному вебсайті](https://www.scalatest.org/getting_started_with_fun_suite). +Ви можете проглянути інші фреймворки для тестування такі як [ScalaCheck](https://www.scalacheck.org/) та [Specs2](https://etorreborre.github.io/specs2/). diff --git a/_uk/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md b/_uk/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md new file mode 100644 index 0000000000..58adf6baf9 --- /dev/null +++ b/_uk/getting-started/sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md @@ -0,0 +1,84 @@ +--- +title: Початок роботи зі Scala і sbt у командному рядку +layout: singlepage-overview +partof: getting-started-with-scala-and-sbt-on-the-command-line +language: uk +disqus: true +next-page: /uk/testing-scala-with-sbt-on-the-command-line +--- + +У цьому посібнику ви дізнаєтесь, як створити проєкт Scala шаблон. +Ви можете використовувати це як відправну точку для власного проєкту. +Ми використаємо [sbt](https://www.scala-sbt.org/1.x/docs/index.html), що де-факто є основним інструментом збірки для Scala. +sbt компілює, запускає, та тестує ваші проєкти поміж інших корисних задач. +Ми припускаємо, що ви знаєте, як користуватися терміналом. + +## Встановлення +1. Впевніться, що ви вже встановили Java 8 JDK (також відому як 1.8) + * Запустіть `javac -version` у командному рядку і впевніться, що бачите + `javac 1.8.___` + * Якщо у вас не встановлена версія 1.8 або вище, [встановіть JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +1. Встановіть sbt + * [Mac](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Mac.html) + * [Windows](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Windows.html) + * [Linux](https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Linux.html) + +## Створити проєкт +1. Перейдіть (`cd`) у пусту директорію. +1. Виконайте наступну команду `sbt new scala/hello-world.g8`. +Це завантажує шаблон 'hello-world' з GitHub. +Також буде створена директорія `target`, яку можна ігнорувати. +1. Коли буде запропоновано, назвіть застосунок `hello-world`. Це створить проєкт з назвою "hello-world". +1. А тепер подивимось що було згенеровано: + +``` +- hello-world + - project (sbt uses this to install and 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) +``` + +Після збірки вашого проєкту, sbt створить більше `target` директорій для згенерованих файлів. + +## Запуск проєкту +1. Перейдіть (`cd`) у `hello-world`. +1. Виконайте `sbt`. Це запустить sbt консоль. +1. Наберіть `~run`. Символ `~` є опціональним та означає перебудову при кожному збереженні файлу, + що дає можливість пришвидшити цикл редагування/запуск/відлагодження. + +## Модифікація коду +1. Відкрийте файл `src/main/scala/Main.scala` у вашому текстовому редакторі. +1. Змініть "Hello, World!" на "Hello, New York!" +1. Якщо ви не зупинили роботу sbt, ви побачите як на консолі з'явиться "Hello, New York!". +1. Ви можете продовжити робити зміни та бачити результати на консолі. + +## Додання залежностей +Давайте ненадовго змістимо фокус на використання опублікованих бібліотек для забезпечення додаткової функціональності ваших програм. + +1. Відкрийте `build.sbt` та додайте наступний рядок: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +``` + +Тут `libraryDependencies` є набором залежностей та використовуючи `+=`, +ми додаємо залежність [scala-parser-combinators](https://github.com/scala/scala-parser-combinators) до набору залежностей, +які необхідні для sbt та які завантажаться при його запуску. Тепер в будь-якому Scala файлі ви можете використати +класи, об'єкти тощо з scala-parser-combinators через звичайний "import". + +Більше опублікованих бібліотек можна знайти на +[Scaladex](https://index.scala-lang.org/) - індекс бібліотек Scala, місце куди ви можете зайти, щоб скопіювати інформацію про бібліотеку +та додати у ваш `build.sbt` файл. + +## Наступні кроки + +Перейдіть до наступного посібника з серії _початок роботи з sbt_, та дізнайтесь про [тестування Scala з sbt та ScalaTest в командному рядку](testing-scala-with-sbt-on-the-command-line.html). + +**або** + +- Продовжить вивчати Scala інтерактивно нам [Вправи зі Scala](https://www.scala-exercises.org/scala_tutorial). +- Дізнайтеся про можливості Scala у коротких статтях, переглянувши наш [Тур по Scala]({{ site.baseurl }}/tour/tour-of-scala.html). diff --git a/_uk/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md b/_uk/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md new file mode 100644 index 0000000000..fc0a6e62ac --- /dev/null +++ b/_uk/getting-started/sbt-track/testing-scala-with-sbt-on-the-command-line.md @@ -0,0 +1,104 @@ +--- +title: Тестування Scala з sbt та ScalaTest в командному рядку +layout: singlepage-overview +partof: testing-scala-with-sbt-on-the-command-line +language: uk +disqus: true +previous-page: /uk/getting-started-with-scala-and-sbt-on-the-command-line +--- + +Існує кілька бібліотек і методологій тестування для Scala, +але в цьому посібнику ми продемонструємо один популярний варіант для фреймворку ScalaTest, +що називається [AnyFunSuite](https://www.scalatest.org/scaladoc/3.2.2/org/scalatest/funsuite/AnyFunSuite.html). + +Ми припускаємо, що ви знаєте [як створити проєкт Scala за допомогою sbt](getting-started-with-scala-and-sbt-on-the-command-line.html). + +## Налаштування +1. Створіть десь новий каталог через командний рядок. +1. Перейдіть (`cd`) в директорію та запустіть `sbt new scala/scalatest-example.g8` +1. Назвіть проєкт `ScalaTestTutorial`. +1. Проєкт вже має ScalaTest як залежність у файлі `build.sbt`. +1. Перейдіть (`cd`) в директорію та запустіть `sbt test`. Це запустить тестове середовище `CubeCalculatorTest` з єдиним тестом `CubeCalculator.cube`. + +``` +sbt test +[info] Loading global plugins from /Users/username/.sbt/0.13/plugins +[info] Loading project definition from /Users/username/workspace/sandbox/my-something-project/project +[info] Set current project to scalatest-example (in build file:/Users/username/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 +``` + +## Розуміння тестів +1. Відкрийте два файли в текстовому редакторі: + * `src/main/scala/CubeCalculator.scala` + * `src/test/scala/CubeCalculatorTest.scala` +1. У файлі `CubeCalculator.scala`, визначте функцію `cube`. +1. У файлі `CubeCalculatorTest.scala`, ви побачите, що клас, що названий так само як і об'єкт, що ми тестуємо. + +``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite { + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } + } +``` + +Переглянемо кожний рядок окремо. + +* `class CubeCalculatorTest` означає, що ми тестуємо об'єкт `CubeCalculator` +* `extends AnyFunSuite` використовуємо функціональність класу AnyFunSuite з ScalaTest, насамперед функцію `test` +* `test` функція з AnyFunSuite, що збирає результати тверджень (assertions) у тілі функції. +* `"CubeCalculator.cube"` назва тесту. Ви можете обрати будь-яку назву, але існує домовленість називати "ClassName.methodName". +* `assert` приймає булеву умову і визначає, пройшов тест чи не пройшов. +* `CubeCalculator.cube(3) === 27` перевіряє чи дорівнює результат функції `cube` значенню 27. + Оператор `===` є частиною ScalaTest та надає чисті повідомлення про помилки. + +## Додати інший тест-кейс +1. Додайте інший тестовий блок з власним `assert`, що перевіряє значення куба '0'. + + ``` + import org.scalatest.funsuite.AnyFunSuite + + class CubeCalculatorTest extends AnyFunSuite { + test("CubeCalculator.cube 3 should be 27") { + assert(CubeCalculator.cube(3) === 27) + } + + test("CubeCalculator.cube 0 should be 0") { + assert(CubeCalculator.cube(0) === 0) + } + } + ``` + +1. Виконайте `sbt test` знову, щоб побачити результати. + + ``` + sbt test + [info] Loading project definition from C:\projects\scalaPlayground\scalatestpractice\project + [info] Loading settings for project root from build.sbt ... + [info] Set current project to scalatest-example (in build file:/C:/projects/scalaPlayground/scalatestpractice/) + [info] Compiling 1 Scala source to C:\projects\scalaPlayground\scalatestpractice\target\scala-2.13\test-classes ... + [info] CubeCalculatorTest: + [info] - CubeCalculator.cube 3 should be 27 + [info] - CubeCalculator.cube 0 should be 0 + [info] Run completed in 257 milliseconds. + [info] Total number of tests run: 2 + [info] Suites: completed 1, aborted 0 + [info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0 + [info] All tests passed. + [success] Total time: 3 s, completed Dec 4, 2019 10:34:04 PM + ``` + +## Висновок +Ви побачили один шлях тестування вашого Scala коду. Більше про +FunSuite ScalaTest на [офіційному вебсайті](https://www.scalatest.org/getting_started_with_fun_suite). +Ви можете проглянути інші фреймворки для тестування такі як [ScalaCheck](https://www.scalacheck.org/) та [Specs2](https://etorreborre.github.io/specs2/). diff --git a/_uk/index.md b/_uk/index.md new file mode 100644 index 0000000000..6242b6e807 --- /dev/null +++ b/_uk/index.md @@ -0,0 +1,105 @@ +--- +layout: landing-page +title: Документація +language: uk +partof: documentation +discourse: true +more-resources-label: Додаткові Матеріали + + +# Content masthead links + + +sections: + - title: "Перші кроки..." + links: + - title: "Початок роботи" + description: "Встанови Scala на свій комп'ютер і почни писати код Scala!" + icon: "fa fa-rocket" + link: /uk/getting-started/install-scala.html + - title: "Екскурсія по Скала" + description: "Короткі введення в основні особливості мови." + icon: "fa fa-flag" + link: /tour/tour-of-scala.html + - title: "Книга по Scala 3" + description: "Вивчи Scala, прочитавши серію коротких уроків." + icon: "fa fa-book-open" + link: /scala3/book/introduction.html + - title: "Онлайн курси" + description: "MOOC для вивчення Scala для початківців і досвідчених програмістів." + icon: "fa fa-cloud" + link: /online-courses.html + - title: "Книги" + description: "Друковані та цифрові книги по Scala." + icon: "fa fa-book" + link: /books.html + - title: "Посібники" + description: "Путівник з серії кроків для створення програм на Scala." + icon: "fa fa-tasks" + link: /tutorials.html + + - title: "Для досвічених" + links: + - title: "API" + description: "Документація API для кожної версії Scala." + icon: "fa fa-file-alt" + link: /api/all.html + - title: "Посібники та огляди" + description: "Поглиблена документація, що покриває багато особливостей Scala." + icon: "fa fa-database" + link: /uk/overviews/index.html + - title: "Довідник по стилю" + description: "Поглиблений посібник з написання ідіоматичного коду на Scala." + icon: "fa fa-bookmark" + link: /style/index.html + - title: "Шпаргалка" + description: "Зручна шпаргалка з основ синтаксису Scala." + icon: "fa fa-list" + link: /uk/cheatsheets/index.html + - title: "Питання-Відповіді" + description: "Відповіді на часті запитання про Scala." + icon: "fa fa-question-circle" + link: /tutorials/FAQ/index.html + - title: "Специфікація мови" + description: "Специфікація формальної мови Scala." + icon: "fa fa-book" + link: https://scala-lang.org/files/archive/spec/2.13/ + - title: "Довідник про мову" + description: "Довідкова інформація про мову Scala 3." + icon: "fa fa-book" + link: https://docs.scala-lang.org/scala3/reference + + - title: "Дослідіть Scala 3" + links: + - title: "Посібник з міграції" + description: "Посібник, що допоможе перейти від Scala 2 до Scala 3." + icon: "fa fa-suitcase" + link: /scala3/guides/migration/compatibility-intro.html + - title: "Нове у Scala 3" + description: "Огляд нових функцій у Scala 3." + icon: "fa fa-star" + link: /uk/scala3/new-in-scala3.html + - title: "Новий Scaladoc для Scala 3" + description: "Основні характеристики нових функцій у Scaladoc." + icon: "fa fa-star" + link: /uk/scala3/scaladoc.html + - title: "Доповіді" + description: "Онлайн доповіді про Scala 3." + icon: "fa fa-play-circle" + link: /uk/scala3/talks.html + + - title: "Еволюція Scala" + links: + - title: "SIPs" + description: "Процес удосконалення Scala (Scala Improvement Process). Еволюція мови та компілятора." + icon: "fa fa-cogs" + link: /sips/index.html + - title: "Внесок у Scala" + description: "Повний посібник із участі в проекті Scala." + icon: "fa fa-cogs" + link: /contribute/ + - title: "Посібник з внесення змін у Scala 3" + description: "Посібник з компілятора Scala 3 та вирішення проблем." + icon: "fa fa-cogs" + link: /scala3/guides/contribution/contribution-intro.html +--- diff --git a/_uk/overviews/index.md b/_uk/overviews/index.md new file mode 100644 index 0000000000..355fc560fa --- /dev/null +++ b/_uk/overviews/index.md @@ -0,0 +1,8 @@ +--- +layout: overviews +partof: overviews +title: Документація +language: uk +--- + + diff --git a/_uk/scala3/guides/tasty-overview.md b/_uk/scala3/guides/tasty-overview.md new file mode 100644 index 0000000000..53eed66f34 --- /dev/null +++ b/_uk/scala3/guides/tasty-overview.md @@ -0,0 +1,164 @@ +--- +layout: singlepage-overview +title: Огляд TASTy +partof: tasty-overview +language: uk +scala3: true +versionSpecific: true +--- +Створіть файл вихідного коду Scala 3 _Hello.scala_: + +```scala +@main def hello = println("Hello, world") +``` + +і скомпілюйте файл з `scalac`: + +```bash +$ scalac Hello.scala +``` + +ви помітите, що серед інших отриманих файлів, `scalac` створює файли з розширенням _.tasty_: + +```bash +$ ls -1 +Hello$package$.class +Hello$package.class +Hello$package.tasty +Hello.scala +hello.class +hello.tasty +``` + +Виникає питання: «Що таке tasty?» + + + +## Що таке TASTy? + +TASTy це акронім терміну, _Типізоване абстрактне синтаксичне дерево (Typed Abstract Syntax Trees)_. +Це високорівневий формат для Scala 3, і в цьому документі ми називатимемо його як _Tasty_. + +Перше, що важливо знати, це те, що файли Tasty генеруються компілятором `scalac`, +та містять _всю_ інформацію про ваш вихідний код, включаючи синтаксичну структуру вашої програми, +і _повну_ інформацію про типи, позицію та навіть документацію. +Файли Tasty містять набагато більше інформації, ніж файли _.class_, які створюються для роботи на JVM. (Детальніше далі.) + +У Scala 3 процес компіляції виглядає так: + +```text + +-------------+ +-------------+ +-------------+ +$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class | + +-------------+ +-------------+ +-------------+ + ^ ^ ^ + | | | + Ваш вихідний Файл TASTy Файл класу + код для scalac для JVM + (містить повну (неповна + інформацію) інформація) +``` + +Ви можете переглянути вміст файлу _.tasty_ у зрозумілій формі, запустивши на ньому компілятор із прапорцем `-print-tasty`. +Ви також можете переглянути вміст, декомпільований у формі, подібній до вихідного коду Scala, використовуючи прапор `-decompile`. +```bash +$ scalac -print-tasty hello.tasty +$ scalac -decompile hello.tasty +``` + +### Проблеми з файлами _.class_ + +Через проблему [стирання типів][erasure], файли _.class_ містять неповне представлення про ваш код. +Простий спосіб продемонструвати це приклад з `List`. + +_Стирання типів_ означає, що коли ви пишете наступний код Scala: + +```scala +val xs: List[Int] = List(1, 2, 3) +``` + +цей код компілюється у файл _.class_, який має бути сумісним із JVM. Результатом цієї вимоги сумісності код всередині цього файлу класу — який ви можете побачити за допомогою команди `javap` — виглядає так: + +```java +public scala.collection.immutable.List xs(); +``` + +Результат команди `javap` показує Java-уявлення того, що міститься у файлі класу. Зверніть увагу, що `xs` більше _не_ визначений як `List[Int]`; він по суті представлений як `List[java.lang.Object]`. Щоб ваш код Scala працював із JVM, тип `Int` має бути стертим. + +Далі, коли ви отримуєте елемент вашого `List[Int]` у вашому Scala коді, наприклад: + +```scala +val x = xs(0) +``` + +отриманий файл класу матиме операцію перетворення для цього рядка коду, яку ви можете уявити так: + +``` +int x = (Int) xs.get(0) // Java-подібно +val x = xs.get(0).asInstanceOf[Int] // більш Scala-подібно +``` + +Знову ж таки, це зроблено для сумісності, щоб ваш код Scala міг працювати на JVM. +Однак, інформація про те, що ми вже мали список цілих чисел, втрачається у файлах класу. +Це створює проблеми під час спроби збірки Scala програми з уже скомпільованою бібліотекою. +Для цього нам потрібно більше інформації, ніж зазвичай міститься у файлах класу. + +Ця дискусія охоплює лише тему стирання типу. +Існують подібні проблеми для кожної іншої конструкції Scala, про які JVM не знає, включно з union, intersection, trait with parameters та багатьма іншими відмінностями Scala 3. + +### На допомогу приходить TASTy +Таким чином, на відміну від відсутньої інформації про вихідні типи у _.class_ файлах або тільки публічного API (як у «Pickle» форматі Scala 2.13), формат TASTy зберігає повне абстрактне синтаксичне дерево (AST) після перевірки типів. +Зберігання всього AST має багато переваг: воно дає можливість окремої компіляції, перекомпіляції для іншої версії JVM, статичного аналізу програм і багато іншого. + +### Ключові моменти + +Отже, це перший висновок з цього розділу: типи, які ви вказуєте у своєму коді Scala, не зовсім точно представлені у файлах _.class_. + +Другим ключовим моментом є розуміння того, що існують відмінності між інформацією, яка доступна під час _компіляції_ та _виконання_: + +- Під **час компіляції**, `scalac` читає та аналізує ваш код, він знає, що `xs` є `List[Int]` +- Коли компілятор записує ваш код у файл класу, він записує `xs` як `List[Object]`, та додає інформацію про перетворення усюди, де йде звернення до `xs` +- Потім під **час виконання** — коли ваш код працює в JVM — JVM не знає, що ваш список є `List[Int]` + +Зі Scala 3 та Tasty, є ще одна важлива примітка про час компіляції: + +- Коли ви пишете код на Scala 3, що використовує інші Scala 3 бібліотеки, `scalac` більше не має читати їх _.class_ файли; + він може прочитати їх _.tasty_ файли, які, як згадувалось, є _точним_ представленням вашого коду. + Це важливо для забезпечення окремої компіляції та сумісності між Scala 2.13 і Scala 3. + + +## Переваги Tasty + +Як ви можете зрозуміти, доступ до повного представлення вашого коду має [багато переваг][benefits]: + +- Компілятор використовує його для підтримки окремої компіляції. +- Сервер мови, що базується на _Мовному серверному протоколі (Language Server Protocol)_ використовує його для підтримки гіперпосилань, завершення команд, документації, та таких глобальних операцій як, пошук звернень та перейменування. +- Tasty створює чудову основу для нового покоління [макросів основаних на рефлексії][macros]. +- Оптимізатори та аналізатори можуть використовувати його для глибокого аналізу коду та розширеної генерації коду. + +У відповідній примітці, Scala 2.13.6 має програму для читання TASTy, а компілятор Scala 3 також може читати формат 2.13 «Pickle». +У [сторінці з classpath сумісності][compatibility-ref] посібнику з міграції на Scala 3 пояснюється перевага можливості крос-компіляції. + + + +## Більше інформації + +Підсумовуючи, Tasty — це високорівневий формат обміну для Scala 3, а файли _.tasty_ містять повне представлення вашого вихідного коду, що надає до переваги, описані у попередніх розділах. +Щоб дізнатися більше, перегляньте ці ресурси: + +- У [цьому відео](https://www.youtube.com/watch?v=YQmVrUdx8TU), Jamie Thompson зі Scala Center детально розповідає про те, як працює Tasty, та його переваги +- Статті з [Бінарної сумісності для авторів бібліотек][binary] розглядаються теми бінарної сумісності, сумісності джерел та модель виконання JVM +- [Подальша сумісність для Scala 3](https://www.scala-lang.org/blog/2020/11/19/scala-3-forward-compat.html) демонструє методи використання Scala 2.13 і Scala 3 в одному проєкті + +Ці статті містять додаткову інформацію про макроси Scala 3: + +- [Бібліотеки макросів Scala](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) +- [Макроси: плани для Scala 3](https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html) +- [Довідник по рефлексії цитат (Quotes Reflect)][quotes-reflect] +- [Довідник по макросах](/scala3/guides/macros) + +[benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html +[erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure +[binary]: {% link _overviews/tutorials/binary-compatibility-for-library-authors.md %} +[compatibility-ref]: {% link _overviews/scala3-migration/compatibility-classpath.md %} +[quotes-reflect]: {{ site.scala3ref }}/metaprogramming/reflection.html +[macros]: {{ site.scala3ref }}/metaprogramming/macros.html diff --git a/_uk/scala3/new-in-scala3.md b/_uk/scala3/new-in-scala3.md new file mode 100644 index 0000000000..0b00d60ca5 --- /dev/null +++ b/_uk/scala3/new-in-scala3.md @@ -0,0 +1,139 @@ +--- +layout: singlepage-overview +title: Нове в Scala 3 +scala3: true +language: uk +--- +Нова версія Scala 3 принесла багато покращень і нові можливості. +Тут наведено короткий огляд найважливіших змін. +Якщо ви хочете розібратись детальніше глибше, у вашому розпорядженні є наступні посилання: + +- [Книга по Scala 3]({% link _overviews/scala3-book/introduction.md %}) націлений на розробників-початківців мови Scala. +- [Конспект синтаксису][syntax-summary] надає формальний опис нового синтаксису. +- [Довідник з мови][reference] дає детальний опис змін від Scala 2 до Scala 3. +- [Посібник з міграції][migration] надає вам всю інформацію, необхідну для переходу від Scala 2 до Scala 3. +- [Посібник для внесення змін в Scala 3][contribution] глибше занурюється в компілятор, включаючи посібник із розв'язання проблем. + +## Що нового в Scala 3 +Scala 3 - це повне перероблення мови Scala. Було змінено багато аспектів +системи типів на більш принципові. Хоч це і надає нові можливості (наприклад, об’єднання типів), +але в першу чергу це означає, що у вашій роботі стає менше системи типів та [наведення типів][type-inference]. +Також значно покращено процес перевантаження. + +### Нове і яскраве: Синтаксис +Окрім багатьох (невеликих) очищень, синтаксис Scala 3 пропонує такі покращення: + +- Новий «тихий» синтаксис для керуючих структур, таких як `if`, `while` та `for` ([новий синтаксис керуючих структур][syntax-control]) +- Ключеве слово `new` тепер опціональне (_або_ [creator applications][creator]) +- [Опціональні дужки][syntax-indentation] привертають до стилю програмування на основі відступів +- Зміна [символу підстановки типів][syntax-wildcard] з `_` на `?`. +- Імплісіти (та їх синтаксис) були [ґрунтовно переглянуті][implicits]. + +### Впертість: контекстні абстракції +Одним з основних концептів Scala було (і залишається певною мірою) +надання користувачам невеликого набору потужних можливостей, які можна комбінувати +заради великої (а іноді навіть непередбачуваної) виразності. Наприклад, _implicits_ +використовувалися для моделювання контекстної абстракції, для вираження обчислення +на рівні типів, моделювання типів-класів, виконання неявних приведень, кодування +розширення методів та багато іншого. +Базуючись на цих прикладах використання, Scala 3 використовує дещо інший підхід +і фокусується на **намірі**, а не на **механізмі**. +Замість того, щоб пропонувати одну дуже потужну функцію, Scala 3 пропонує кілька +спеціальних мовних конструкцій, що дозволяють програмістам прямо висловлювати свої наміри: + +- **Абстрагування над контекстною інформацією**. [Ключове слово using][contextual-using] дозволяє програмістам абстрагуватися від інформації, яка доступна в контексті виклику і повинна передаватися неявно. Конструкція using є удосконаленням implicit зі Scala 2 та може бути визначена за типом, звільняючи сигнатури функцій від термів, на які ніколи не посилаються явно. + +- **Надання екземплярів класів типів**. [Наведені екземпляри][contextual-givens] дозволяють програмістам визначати _канонічне значення_ певного типу. Це робить програмування з класами типів простішим без витоку деталей реалізації. + +- **Ретроспективне розширення класів**. У Scala 2 методи розширення повинні бути закодовані за допомогою неявних перетворень або неявних класів. На відміну від цього, у Scala 3 [методи розширення][contextual-extension] тепер безпосередньо вбудовані в мову, що призводить до кращих повідомлень про помилки та покращеного виведення типу. + +- **Відображення одного типу як іншого**. Неявні перетворення були [перероблені][contextual-conversions] з нуля як екземпляри класу типів `Conversion`. + +- **Контекстні абстракції вищого порядку**. _Абсолютно нова_ можливість [контекстних функцій][contextual-functions] робить контекстні абстракції first-class citizen. Вони є важливим інструментом для авторів бібліотек і дозволяють стисло виразити домен-специфічні мови. + +- **Дієвий відгук від компілятора**. Якщо неявний параметр не може бути розв'язаний компілятором, то надаються [пропозиції імпорту](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html), що можуть розв'язувати проблему. + +### Скажи що маєш на увазі: покращення системи типів +Окрім значно покращеного виведення типів, система типів Scala 3 також пропонує багато нових функцій, надаючи вам потужні інструменти для статичного вираження інваріантів у типах: + +- **Перерахування**. [Enum][enums] був перероблений, щоб добре поєднуватися з кейс-класами та сформувати новий стандарт для вираження [алгебраїчних типів даних][enums-adts]. + +- **Непрозорі типи**. Сховайте деталі реалізації за [псевдонімом непрозорого типу][types-opaque] без зниження перфомансу! Непрозорі типи замінюють класи значень і дозволяють налаштувати бар'єр абстракції без додаткових накладних витрат. + +- **Типи перетину та об'єднання**. Нові засади системи типів призвели до введення нових можливостей системи типів: екземпляр [типу Intersection][types-intersection], як `A & B`, є екземпляром _обох_ типів і `A` і `B`. Екземпляр [типу Union][types-union], як `A | B`, є екземпляром _або_ `A` або `B`. Обидві конструкції дозволяють програмістам гнучко обмежувати типи поза межами ієрархії наслідування. + +- **Залежні типи функцій**. Scala 2 вже дозволяє типам результату залежати від (значення) аргументів. У Scala 3 тепер можна абстрагуватися над цим шаблоном і виразити [залежні типи функцій][types-dependent]. В типі `type F = (e: Entry) => e.Key` результат _залежить_ від аргументу! + +- **Поліморфні типи функцій**. Як і типи функцій залежності, Scala 2 підтримувала методи, які дозволяють параметри типу, але не дозволяла абстрагуватися над цими методами. У Scala 3, [поліморфні типи функцій][types-polymorphic], наприклад `[A] => List[A] => List[A]` абстрагується над функцією, що приймає _аргумент типу_ на додачу до аргументів значень. + +- **Лямбда типу**. Те, що виражалося з використанням [плагіна компілятора](https://github.com/typelevel/kind-projector) в Scala 2 тепер є першокласною особливістю в Scala 3: Лямбда типу — це функції рівня типів, які можна передавати як аргументи типу (вищого роду) без визначення допоміжного типу. + +- **Відповідність типів**. Замість того, щоб кодувати обчислення на рівні типу з використанням імплісітів, Scala 3 пропонує пряму підтримку [відповідності за типами][types-match]. Інтеграція обчислень на рівні типів у процес перевірки типів дозволяє покращити повідомлення про помилки та усуває необхідність у складному кодуванні. + + +### Переосмислено: об'єктно-орієнтоване програмування +Scala завжди була на межі між функціональним програмуванням та об'єктноорієнтованим програмуванням -- і Scala 3 розширює межі в обох напрямках! +Вищезгадані зміни в системі типів і перероблення контекстних абстракцій роблять _функціональне програмування_ легшим, ніж раніше. +Водночас наступні нові функції дозволяють добре структурувати _об'єктноорієнтовані проєкти_ та підтримують найкращі практики. + +- **Pass it on**. Трейти наближаються до класів і тепер також можуть приймати [параметри][oo-trait-parameters], що робить їх ще більш потужними як інструмент для модульної декомпозиції. +- **План розширення**. Класи розширення, які не призначені для наслідування, є давньою проблемою в об'єктноорієнтованому програмуванні. Для розв'язання цього питання, [відкриті класи][oo-open] вимагають у розробників бібліотек _явно_ позначити класи як відкриті. +- **Приховати деталі реалізації**. Утилітні трейти, які іноді реалізують поведінку, не повинні входити до складу виведених типів. У Scala 3, такі трейти можуть бути позначені як [прозорі][oo-transparent] приховуючи наслідування від користувача (у виведених типах). +- **Композиція понад спадковістю**. Це поняття широко згадується, але є важким у реалізації. Але не з [export][oo-export] у Scala 3's: симетричні до імпорту, експорти дозволяють користувачеві визначати псевдоніми для вибраних членів об'єкта. +- **Більше без NPE**. Scala 3 безпечніша, ніж будь-коли: [явний null][oo-explicit-null] виводить `null` з ієрархії типів, допомагаючи статично виловлювати помилки; додаткові перевірки для [безпечної ініціалізації][oo-safe-init] виявляють доступ до неініціалізованих об'єктів. + + +### Батарейки в комплекті: метапрограмування +Хоча макроси в Scala 2 були лише експериментальною функцією, Scala 3 поставляється з потужним арсеналом інструментів для метапрограмування. +[Посібник по макросах]({% link _overviews/scala3-macros/tutorial/index.md %}) містить детальну інформацію про різні об'єкти. Зокрема, Scala 3 пропонує наступні можливості для метапрограмування: + +- **Inline**. Як відправна точка, [inline][meta-inline] дозволяє редукувати значення та методи під час компіляції. Ця проста функція вже охоплює багато варіантів використання і в той же час є точкою входу для більш розширених функцій. +- **Операції під час компіляції**. Пакет [`scala.compiletime`][meta-compiletime] містить додаткову функціональність, яку можна використовувати для реалізації вбудованих методів. +- **Цитування блоків коду**. Scala 3 додає нову можливість [квазі-цитування][meta-quotes] коду, що надає зручний інтерфейс високого рівня для побудови та аналізу коду. Побудувати код для додавання одиниці до одиниці так само просто, як і `'{ 1 + 1 }`. +- **API рефлексії**. Для більш просунутих випадків використання [quotes.reflect][meta-reflection] забезпечує більш детальний контроль для перевірки та створення дерев програм. + +Якщо ви хочете дізнатися більше про метапрограмування в Scala 3, пропонуємо подивитись на наш [посібник][meta-tutorial]. + + +[enums]: {{ site.scala3ref }}/enums/enums.html +[enums-adts]: {{ site.scala3ref }}/enums/adts.html + +[types-intersection]: {{ site.scala3ref }}/new-types/intersection-types.html +[types-union]: {{ site.scala3ref }}/new-types/union-types.html +[types-dependent]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[types-lambdas]: {{ site.scala3ref }}/new-types/type-lambdas.html +[types-polymorphic]: {{ site.scala3ref }}/new-types/polymorphic-function-types.html +[types-match]: {{ site.scala3ref }}/new-types/match-types.html +[types-opaque]: {{ site.scala3ref }}/other-new-features/opaques.html + +[type-inference]: {{ site.scala3ref }}/changed-features/type-inference.html +[overload-resolution]: {{ site.scala3ref }}/changed-features/overload-resolution.html +[reference]: {{ site.scala3ref }}/overview.html +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[migration]: {% link _overviews/scala3-migration/compatibility-intro.md %} +[contribution]: {% link _overviews/scala3-contribution/contribution-intro.md %} + +[implicits]: {{ site.scala3ref }}/contextual +[contextual-using]: {{ site.scala3ref }}/contextual/using-clauses.html +[contextual-givens]: {{ site.scala3ref }}/contextual/givens.html +[contextual-extension]: {{ site.scala3ref }}/contextual/extension-methods.html +[contextual-conversions]: {{ site.scala3ref }}/contextual/conversions.html +[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html + +[syntax-summary]: {{ site.scala3ref }}/syntax.html +[syntax-control]: {{ site.scala3ref }}/other-new-features/control-syntax.html +[syntax-indentation]: {{ site.scala3ref }}/other-new-features/indentation.html +[syntax-wildcard]: {{ site.scala3ref }}/changed-features/wildcards.html + +[meta-tutorial]: {% link _overviews/scala3-macros/tutorial/index.md %} +[meta-inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[meta-compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[meta-quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[meta-reflection]: {% link _overviews/scala3-macros/tutorial/reflection.md %} + +[oo-explicit-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html +[oo-safe-init]: {{ site.scala3ref }}/other-new-features/safe-initialization.html +[oo-trait-parameters]: {{ site.scala3ref }}/other-new-features/trait-parameters.html +[oo-open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[oo-transparent]: {{ site.scala3ref }}/other-new-features/transparent-traits.html +[oo-export]: {{ site.scala3ref }}/other-new-features/export.html diff --git a/_uk/scala3/scaladoc.md b/_uk/scala3/scaladoc.md new file mode 100644 index 0000000000..66a6d12805 --- /dev/null +++ b/_uk/scala3/scaladoc.md @@ -0,0 +1,89 @@ +--- +layout: singlepage-overview +title: Нові можливості в Scaladoc +partof: scala3-scaladoc +language: uk +scala3: true +--- + +Нова версія Scala 3 поставляється з абсолютно новою реалізацією генератора документації _Scaladoc_, переписаною з нуля. +У цій статті ви можете знайти огляд нових функцій, які є або будуть представлені в Scaladoc. +Для загальної довідки відвідайте [посібник Scaladoc]({% link _overviews/scala3-scaladoc/index.md %}). + +## Нові можливості + +### Синтаксис Markdown + +Найбільшою зміною, внесеною в нову версію Scaladoc, є зміна мови за замовчуванням для docstrings. Поки що Scaladoc підтримував лише синтаксис Wikidoc. +Новий Scaladoc все ще може аналізувати застарілий синтаксис `Wikidoc`, однак Markdown вибрано як основну мову для форматування коментарів. +Щоб повернутися до `Wikidoc`, можна передати глобальний прапор перед запуском `doc` або визначити його для конкретних коментарів за допомогою директиви `@syntax wiki`. + +Щоб отримати додаткову інформацію про те, як використовувати повну силу документації, перегляньте [Scaladoc docstrings][scaladoc-docstrings] + + +### Статичні вебсайти + +Scaladoc також забезпечує простий спосіб створення **статичних сайтів** як для документації, так і для публікацій у блозі, подібно до того, як це робить Jekyll. +Завдяки цій функції ви можете зберігати свою документацію разом зі згенерованим API Scaladoc дуже зручним способом. + +Щоб отримати додаткову інформацію про те, як налаштувати генерацію статичних сайтів, перегляньте абзац [Статична документація][static-documentation] + +![](/resources/images/scala3/scaladoc/static-site.png) + +### Публікації в блозі + +Дописи в блозі – це особливий тип статичних сайтів. У посібнику Scaladoc ви можете знайти додаткову інформацію про те, як працювати з [публікаціями в блозі][built-in-blog]. + +![](/resources/images/scala3/scaladoc/blog-post.png) + +### Посилання на соціальні мережі + +Крім того, Scaladoc надає простий спосіб налаштувати [посилання на соціальні мережі][social-links], наприклад Twitter чи Discord. + +![](/resources/images/scala3/scaladoc/social-links.png){: style="width: 180px"} + +## Експериментальні особливості + +На поточний час (травень 2021 р.) перелічені нижче функції не можуть бути випущені разом із Scaladoc, однак ми будемо раді почути ваші відгуки. +Кожна функція має власний розділ на сайті учасників scala-lang, де ви можете поділитися своїми думками. + +### Компіляція фрагментів + +Одним з експериментальних особливостей Scaladoc є компілятор фрагментів (snippets compiler). +Цей інструмент дозволить вам компілювати фрагменти, які ви додаєте до docstring, щоб перевірити, чи вони насправді поводяться належним чином. +Ця функція дуже схожа на інструменти `tut` або `mdoc`, але буде поставлятися разом із Scaladoc для легкого налаштування та інтеграції у ваш проєкт. +Зробити фрагменти інтерактивними, наприклад, дозволити користувачам редагувати та компілювати їх у браузері, наразі розглядається. + +Вітрина: +* Приховування коду ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/hiding-code.gif) +* Виявлення помилок компіляції ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/assert-compilation-errors.gif) +* Включення фрагментів ![]({{ site.baseurl }}/resources/images/scala3/scaladoc/snippet-includes.png) + +Для більш детальної інформації дивіться [Посібники](/scala3/guides/scaladoc/snippet-compiler.html), або перейдіть у [тред Scala Contributors](https://contributors.scala-lang.org/t/snippet-validation-in-scaladoc-for-scala-3/4976) + +### Пошук, оснований на типах + +Пошук функцій за їх символьними назвами може зайняти багато часу. +Саме тому новий Scaladoc дозволяє шукати методи та поля за їх типами. + +Тому для декларації: +``` +extension [T](arr: IArray[T]) def span(p: T => Boolean): (IArray[T], IArray[T]) = ... +``` +Замість того, щоб шукати `span`, ми можемо шукати `IArray[A] => (A => Boolean) => (IArray[A], IArray[A])`. + +Щоб скористатися цією функцією, просто введіть підпис функції, яку ви шукаєте, на панелі пошуку scaladoc. Ось як це працює: + +![](/resources/images/scala3/scaladoc/inkuire-1.0.0-M2_js_flatMap.gif) + +Ця функція забезпечується пошуковою системою [Inkuire](https://github.com/VirtusLab/Inkuire), яка працює для Scala 3 і Kotlin. Щоб бути в курсі розвитку цього інструменту, підпишіться на оновлення [репозиторію Inkuire](https://github.com/VirtusLab/Inkuire). + +Для отримання додаткової інформації дивіться [Посібники](/scala3/guides/scaladoc/search-engine.html) + +Зауважте, що ця функція все ще знаходиться на стадії розробки, тому вона може зазнати значних змін. +Якщо ви зіткнулися з помилкою або маєте ідею щодо покращення, не соромтеся створювати проблему на [Inkuire](https://github.com/VirtusLab/Inkuire/issues/new) або [dotty](https://github.com/scala/scala3/issues/new). + +[scaladoc-docstrings]: {% link _overviews/scala3-scaladoc/docstrings.md %} +[static-documentation]: {% link _overviews/scala3-scaladoc/static-site.md %} +[built-in-blog]: {% link _overviews/scala3-scaladoc/blog.md %} +[social-links]: {% link _overviews/scala3-scaladoc/settings.md %}#-social-links diff --git a/_uk/scala3/talks.md b/_uk/scala3/talks.md new file mode 100644 index 0000000000..01087ab671 --- /dev/null +++ b/_uk/scala3/talks.md @@ -0,0 +1,90 @@ +--- +layout: singlepage-overview +title: Розмови +partof: scala3-talks +language: uk +scala3: true +versionSpecific: true +--- + +Серія "Поговорімо про Scala 3" +------------------------------- + +[Поговорімо про Scala 3](https://www.youtube.com/playlist?list=PLTx-VKTe8yLxYQfX_eGHCxaTuWvvG28Ml) є серією +коротких (близько 15 хвилин) розмов про Scala 3. Він охоплює різноманітні теми, наприклад, як почати, як застосувати +переваги нових функцій мови, або як перейти з Scala 2. + +Talks on Scala 3 +---------------- +- (ScalaDays 2019, Lausanne) [Тур по Scala 3](https://www.youtube.com/watch?v=_Rnrx2lo9cw) + від [Martin Odersky](http://x.com/odersky) + +- (ScalaDays 2016, Berlin) [Попереду дорога Scala](https://www.youtube.com/watch?v=GHzWqJKFCk4) + від [Martin Odersky](http://x.com/odersky) + [\[слайди\]](http://www.slideshare.net/Odersky/scala-days-nyc-2016) + +- (JVMLS 2015) [Compilers are Databases](https://www.youtube.com/watch?v=WxyyJyB_Ssc) + від [Martin Odersky](http://x.com/odersky) + [\[слайди\]](http://www.slideshare.net/Odersky/compilers-are-databases) + +- (Scala World 2015) [Dotty: Досліджуємо майбутнє Scala](https://www.youtube.com/watch?v=aftdOFuVU1o) + від [Dmitry Petrashko](http://x.com/darkdimius) + [\[слайди\]](https://d-d.me/scalaworld2015/#/). + Розповідь Дмітрія охоплює багато нових функцій, які приносить Dotty, наприклад типи Intersection та Union, покращена ініціалізація lazy val тощо. + Дмітрій також розповідає внутрішню архітектуру Dotty і, зокрема, високий рівень контекстуальних абстракцій Dotty. Ви + ознайомитесь з багатьма базовими поняттями, такими як «Denotations» та їх особливостями. + +Deep Dive with Scala 3 +---------------------- +- (ScalaDays 2019, Lausanne) [Метапрограмування in Dotty](https://www.youtube.com/watch?v=ZfDS_gJyPTc) + від [Nicolas Stucki](https://github.com/nicolasstucki). + +- (ScalaDays 2019, Lausanne) [Future-proofing в Scala: проміжна репрезентація TASTY](https://www.youtube.com/watch?v=zQFjC3zLYwo) + від [[Guillaume Martres](http://guillaume.martres.me/)](http://guillaume.martres.me/). + +- (Mar 21, 2017) [Dotty Internals 1: Trees та Symbols](https://www.youtube.com/watch?v=yYd-zuDd3S8) + від [Dmitry Petrashko](http://x.com/darkdimius) + [\[meeting notes\]](https://dotty.epfl.ch/docs/internals/dotty-internals-1-notes.html). + Це запис зустрічі EPFL та Waterloo, де були представлені перші нотатки про Dotty: Trees та Symbols. + +- (Mar 21, 2017) [Dotty Internals 2: Types](https://www.youtube.com/watch?v=3gmLIYlGbKc) + від [Martin Odersky](http://x.com/odersky) та [Dmitry Petrashko](http://x.com/darkdimius). + Це запис зустрічі EPFL та Waterloo, де були представлено як представлені типи всередині Dotty. + +- (Jun 15, 2017) [Dotty Internals 3: Denotations](https://youtu.be/9iPA7zMRGKY) + від [Martin Odersky](http://x.com/odersky) та [Dmitry Petrashko](http://x.com/darkdimius). + Це запис зустрічі EPFL та Waterloo, де були представлена денотація в Dotty. + +- (JVM Language Summit) [Як зробити компілятор Dotty швидким](https://www.youtube.com/watch?v=9xYoSwnSPz0) + від [Dmitry Petrashko](http://x.com/darkdimius). + Дмітрій дає високорівневий вступ до того, що було зроблено для створення Dotty . + +- (Typelevel Summit Oslo, May 2016) [Dotty та типи: поки що історія](https://www.youtube.com/watch?v=YIQjfCKDR5A) + від [Guillaume Martres](http://guillaume.martres.me/) + [\[слайди\]](http://guillaume.martres.me/talks/typelevel-summit-oslo/). + Гійом зосередився на деяких практичних вдосконаленнях системи типів, які робить Dotty. Це новий алгоритм параметру типу, + який здатний робити висновки про безпеку типів для більшої кількості ситуацій ніж scalac. + +- (flatMap(Oslo) 2016) [AutoSpecialization в Dotty](https://vimeo.com/165928176) + від [Dmitry Petrashko](http://x.com/darkdimius) + [\[слайди\]](https://d-d.me/talks/flatmap2016/#/). + Компонувальник Dotty аналізує вашу програму та її залежності, щоб застосувати нову схему спеціалізації. + Віна ґрунтується на нашому досвіді з Specialization, Miniboxing та Valhalla Project, + і різко зменшує розмір байт-коду. І, що найкраще, це завжди ввімкнено, відбувається за кулісами без анотацій, + що призводить до прискорення понад 20 разів. Крім того, він «просто працює» на колекціях Scala. + +- (ScalaSphere 2016) [Hacking on Dotty: жива демонстрація](https://www.youtube.com/watch?v=0OOYGeZLHs4) + від [Guillaume Martres](http://guillaume.martres.me/) + [\[слайди\]](http://guillaume.martres.me/talks/dotty-live-demo/). + Прийоми Гійома для Dotty: демонстрація в реальному часі, під час якої він створює просту фазу компілятора для відстеження викликів методів під час виконання. + +- (Scala By the Bay 2016) [Dotty: що це і як працює](https://www.youtube.com/watch?v=wCFbYu7xEJA) + від [Guillaume Martres](http://guillaume.martres.me/) + [\[слайди\]](http://guillaume.martres.me/talks/dotty-tutorial/#/). + Гійом демонструє високорівневе представлення пайплайну компіляції в Dotty. + +- (ScalaDays 2015, Amsterdam) [Як зробити ваші програми на Scala меншими та швидшими за допомогою компонувальника Dotty](https://www.youtube.com/watch?v=xCeI1ArdXM4) + від [Dmitry Petrashko](http://x.com/darkdimius) + [\[слайди\]](https://d-d.me/scaladays2015/#/). + Дмитрій представляє алгоритм аналізу графу виклик у Dotty та переваги продуктивності, які ми можемо отримати з точки зору кількості методів, + розміру байт-коду, розміру коду JVM і кількість об'єктів, виділених в кінці. diff --git a/_zh-cn/glossary/index.md b/_zh-cn/glossary/index.md index dc82232e3f..055eff8026 100644 --- a/_zh-cn/glossary/index.md +++ b/_zh-cn/glossary/index.md @@ -16,381 +16,381 @@ language: zh-cn  
-* #### 代数数据类型(algebraic data type) -通过提供若干个带有独立构造器的备选项来定义的类型。它一般通过模式匹配的方式来结构类型,在规约语言和函数式编程语言中常见到这个概念。Scala可通过案例类来模拟代数数据类型。 +* ### 代数数据类型(algebraic data type) +通过提供若干个带有独立构造器的备选项来定义的类型。它一般通过模式匹配的方式来结构类型,在规约语言和函数式编程语言中常见到这个概念。Scala可通过样例类来模拟代数数据类型。 -* #### 备选项(alternative) +* ### 备选项(alternative) match表达式的一个分支,形如 “`case` _pattern_ => _expression_”。备选项的别名是 _案例_(_case_)。 -* #### 注解(annotation) +* ### 注解(annotation) 注解一般出现在源码中,并附加到语法的某个部分。注解对于计算机来说都是可处理的,所以可以用来有效的增加Scala扩展。 -* #### 匿名类(anonymous class) +* ### 匿名类(anonymous class) 匿名类是由Scala编译器根据一种new表达式生成的合成子类,这种new表达式由类名或特质名后面紧跟一对花括号组成。花括号内包含了匿名子类的构造体,可为空,不过一旦跟在new后面名称指向的特质或类包含了抽象成员,则这些抽象成员就必须在其匿名子类的构造体内具化,即在花括号内要实现这些成员。 -* #### 匿名函数(anonymous function) +* ### 匿名函数(anonymous function) [函数字面量](#函数字面量function-literal)的另一种叫法。 -* #### 应用(apply) +* ### 应用(apply) 方法、函数或闭包应用于参数,意思是说通过这些实参来调用方法、函数或闭包。 -* #### 实参(argument) +* ### 实参(argument) 在函数调用过程中实参被传给函数的各个参数,其中参数就是指向实参的变量,实参则是调用发生时被传入的对象。另外,应用程序都可以获取被传入单例对象的main方法且类型为`Array[String]`的实参(来自命令行)。 -* #### 赋值(assign) +* ### 赋值(assign) 可把对象赋值给变量,之后,变量就会指向对象。 -* #### 辅助构造器(auxiliary constructor) +* ### 辅助构造器(auxiliary constructor) 在类定义体花括号里面定义的所有附加构造器,其形似名为`this`但无结果类型的方法定义。 -* #### 块(block) +* ### 块(block) 被花括号围起来的一个或多个表达式和声明。求值块的时候,块内所有表达式和声明会被顺序处理,然后会返回最后一个表达式的值作为其自身的值。块通常被用来作为构造体,诸如函数、[for表达式](#for表达式for-expression)、`while`循环以及其他任何需要把语句组织到一起的地方,都会用到块。更正式点说,块是一个只其副作用和结果值对外可见的封装构造体。因此,类或对象的花括号是不会形成块的,因其内部定义字段和方法均对外可见。这样的花括号形成的是模板。 -* #### 绑定变量(bound variable) +* ### 绑定变量(bound variable) 表达式的绑定变量是定义和使用都在表达式内部的变量。例如,在函数字面量表达式`(x: Int) => (x, y)`里面,`x`和`y`都被用到了,但只有`x`被绑定了,因为它在表达式中被定义为一个`Int`变量,并且它也是表达式所描述函数的唯一实参。 -* #### 传名参数(by-name parameter) +* ### 传名参数(by-name parameter) 参数类型前面带有`=>`的参数,如`(x: => Int)`。传名参数对应的实参求值不在方法被调用前,而是在每次方法通过名称引用到参数的时候。参数若不是传名的,则定是传值的。 -* #### 传值参数(by-value parameter) +* ### 传值参数(by-value parameter) 参数类型前面不带`=>`的参数,如`(x: Int)`。传值参数对应的实参是在方法调用前被求值的。传值参数是相对传名参数而言的。 -* #### 类(class) +* ### 类(class) 通过关键字`class`来定义的类,可抽象可具体,且在实例化时可用类型和值对其进行参数化处理。比如`new Array[String](2)`,被实例化的类是`Array`,产生的值类型为`Array[String]`。带有类型参数的类被称为 _类型构造器_ ,也可说成是类型具有类属性,如:类型`Array[String]`具有的类属性是`Array`。 -* #### 闭包(closure) +* ### 闭包(closure) 可以捕获自由变量,或者说"关闭"函数创建时可见变量的函数对象。 -* #### 伴生类(companion class) +* ### 伴生类(companion class) 和定义在相同源文件中的单例对象共享同一个名称的类,这样的类就叫做那个单例对象的伴生类。 -* #### 伴生对象(companion object) +* ### 伴生对象(companion object) 和定义在相同源文件中的类共享同一个名称的单例对象。伴生对象和伴生类具备访问彼此私有成员的能力。另外,类不管被用在何处,其伴生对象中定义的任何隐式转换都在其作用域内。 -* #### 逆变(contravariant) +* ### 逆变(contravariant) 逆变标注可应用于类或特质的类型参数上,把减号(-)置于类型参数前即可。标注为逆变后类或特质的子类型将逆向(向相反的方向)协变于类型标注的参数。比如,`Function1`的第一个类型参数就是逆变的,所以`Function1[Any, Any]`是`Function1[String, Any]`的子类。 -* #### 协变(covariant) -协变标注可应用于类或特质的类型参数上,把加号(+)置于类型参数前即可。标注为协变后类或特质的子类型将正向(向相同的方向)协变于类型标注的参数。比如,`List`的类型参数是协变的,所以`List[String]`是`List[Any]`的子类。 +* ### 协变(covariant) +协变标注可应用于类或特质的类型参数上,把加号(+)置于类型参数前即可。标注为协变后类或特质的子类型将正向(向相同的方向)协变于类型标注的参数。比如,`List`的类型参数是协变的,所以`List[String]`是`List[Any]`的子类。 -* #### 柯里化(currying) +* ### 柯里化(currying) 把函数写成多个参数列表的方式。例如:`def f(x: Int)(y: Int)`是一个带有两个参数列表的柯里化函数。应用柯里化函数时需传入若干个实参列表,像`f(3)(4)`这样。不过也可以写成柯里化函数的 _部分应用_(partial application),像`f(3)`这样。 -* #### 声明(declare) +* ### 声明(declare) 可以通过 _声明_ 抽象的字段、方法或类型来赋给实体一个名称,但是没有具体实现。声明和定义最关键的差异就是定义会为命名实体创建具体实现,而声明则不会。 -* #### 定义(define) +* ### 定义(define) 在Scala程序中若提到 _定义_ 什么东西,就是说给它赋个名称并给出实现。可以定义的东西包括类、特质、单例对象、字段、方法、局部函数、局部变量等。由于提到定义常常会涉及到某种具体实现,故而抽象成员应为声明而非定义。 -* #### 直接子类(direct subclass) +* ### 直接子类(direct subclass) 类是其 _直接子类_ 的直接超类。 -* #### 直接超类(direct superclass) +* ### 直接超类(direct superclass) 从某个类直接衍生出类或特质,或者说在继承层级结构最接近自己的上层的某个类,这样的类就是直接超类。若类`Child`的可选的extends子句中含有类`Parent`,则`Parent`就是`Child`的直接超类。若`Child`的可选extends子句中含有特质,则特质的直接超类也是`Child`的直接超类。若`Child`没有extends子句,则`AnyRef`就是`Child`的直接超类。若类的直接超类带有类型参数,比如`Child extends Parent[String]`,`Child`的直接超类依旧是`Parent`,而不是`Parent[String]`。另一方面,`Parent[String]`应该叫做`Child`的直接超类型。参见[超类型](#超类型supertype)了解更多关于类和类型间的区别。 -* #### 相等性(equality) +* ### 相等性(equality) 在没有条件限制的情况下使用时,_相等性_ 就是`==`所表达的两个值之间的关系。参见[引用相等性](#引用相等性reference-equality)。 -* #### 存在类型(existential type) +* ### 存在类型(existential type) 存在类型包含未知类型变量的引用。比如:`Array[T] forSome { type T }`是个存在类型,是`T`的数组,而`T`是某个完全未知的类型,关于`T`唯一能够假定的是它是确定存在的。尽管这个假定很虚,但是至少意味着`Array[T] forSome { type T }`确实是个数组,而不是香蕉什么的东西。 -* #### 表达式(expression) +* ### 表达式(expression) 任何能够得到结果的Scala代码,也可说成表达式求值为某个结果或结果为某个值。 -* #### 过滤器(filter) +* ### 过滤器(filter) [for表达式](#for表达式for-expression)中的`if`及跟在其后的布尔表达式。在`for(i <- 1 to 10; if i % 2 == 0)`中,过滤器为"`if i % 2 == 0`"。`if`右边的值就是[过滤器表达式](#过滤器表达式filter-expression),也称为守卫。 -* #### 过滤器表达式(filter expression) +* ### 过滤器表达式(filter expression) 过滤器表达式就是[for表达式](#for表达式for-expression)里面跟在`if`后面的布尔表达式。`for( i <- 1 to 10 ; if i % 2 == 0)`的过滤器表达式为"`i % 2 == 0`"。 -* #### 头等函数(first-class function) +* ### 头等函数(first-class function) Scala支持 _头等函数_ ,意味着可以通过函数字面量语法来表达函数。如:`(x: Int) => x + 1`,并且函数可由对象来表达,叫做[函数值](#函数值function-value)。 -* #### for推解式(for comprehension) +* ### for推解式(for comprehension) _for推解式_ 是[for表达式](#for表达式for-expression)的一种,一般用来创建新的集合。对`for`推解式的每次迭代,[yield](#产生yield)子句都会定义新集合的一个元素。比如:`for (i <- (0 until 2); j <- (2 until 4)) yield (i, j)`将返回集合`Vector((0,2), (0,3), (1,2), (1,3))`。 -* #### for表达式(for expression) +* ### for表达式(for expression) _for表达式_ 要么是个[for循环](#for循环for-loop),可以迭代一个或多个集合,要么是个[for推解式](#for推解式for-comprehension),可以从一个或多个集合的元素中推解出一个新的集合。`for`表达式建于[生成器](#生成器generator)、[过滤器](#过滤器filter)、变量定义和[yield](#产生yield)子句(针对[for推解式](#for推解式for-comprehension))基础之上, -* #### for循环(for loop) +* ### for循环(for loop) _for循环_ 是[for表达式](#for表达式for-expression)的一种,一般用来循环迭代一个或多个集合。因为`for`循环返回unit,所以经常被用来产生副作用。比如:`for (i <- 0 until 100) println(i)`打印数字0到99。 -* #### 自由变量(free variable) +* ### 自由变量(free variable) 一个表达式的 _自由变量_ 指的是在表达式中使用但不定义在其中的变量。例如,在函数字面量表达式`(x: Int) => (x, y)`中,变量`x`和`y`都被用到了,但只有`y`是自由变量,因其未在表达式中定义。 -* #### 函数(function) +* ### 函数(function) _函数_ 可通过一列实参来[调用](#调用invoke)然后产生结果。函数一般具有参数列表、函数体和结果类型。作为类、特质或单例对象的函数叫做[方法](#方法method)。定义在其他函数内部的函数叫做[局部函数](#局部函数local-function)。结果类型为`Unit`的函数叫做[过程](#过程procedure)。源码里面的匿名函数叫做[函数字面量](#函数字面量function-literal)。运行时,函数字面量被实例化为对象,叫做[函数值](#函数值function-value)。 -* #### 函数字面量(function literal) +* ### 函数字面量(function literal) 在Scala源码中的无名函数,通过函数字面量语法来特别对待。比如:`(x: Int, y: Int) => x + y`。 -* #### 函数值(function value) +* ### 函数值(function value) 可以像其他函数一样被调用的函数对象。函数值的类一般是继承了`scala`包中的`FunctionN`(比如`Function0`,`Function1`等)这类特质的其中之一,且在源码中常通过[函数字面量](#函数字面量function-literal)语法来表示。当函数值的apply方法被调用时就说这个函数值被调用。捕获自由变量的函数值为[闭包](#闭包closure)。 -* #### 函数式风格(functional style) +* ### 函数式风格(functional style) _函数式风格_ 编程注重函数和求值结果而非操作发生的顺序。这种风格的特征是可传递函数值给循环方法、不可变数据、方法无副作用,是像Haskell和Erlang等这些语言的主要范式,与[命令式风格](#命令式风格imperative-style)相对应。 -* #### 生成器(generator) +* ### 生成器(generator) 生成器在[for表达式](#for表达式for-expression)中定义一个命名的val变量并赋予其一系列值。比如:`for(i <- 1 to 10)`的生成器是"`i <- 1 to 10`",`<-`右边的值是[生成器表达式](#生成器表达式generator-expression)。 -* #### 生成器表达式(generator expression) +* ### 生成器表达式(generator expression) 生成器表达式在[for表达式](#for表达式for-expression)中生成一些列值。比如:`for(i <- 1 to 10)`的生成器表达式是"`1 to 10`"。 -* #### 泛型类(generic class) +* ### 泛型类(generic class) 带有类型参数的类。例如,因`scala.List`带一类型参数,故其为泛型类。 -* #### 泛型特质(generic trait) +* ### 泛型特质(generic trait) 带有类型参数的特质。例如,因`scala.collection.Set`带一类型参数,故其为泛型特质。 -* #### 守卫(guard) +* ### 守卫(guard) 参见[过滤器](#过滤器filter). -* #### 辅助函数(helper function) +* ### 辅助函数(helper function) 目的是为一个或多个其他邻近函数提供服务的函数。辅助函数常实现为局部函数。 -* #### 辅助方法(helper method) +* ### 辅助方法(helper method) 作为类成员的[辅助函数](#辅助函数helper-function)。辅助方法常为私有方法。 -* #### 不可变(immutable) +* ### 不可变(immutable) 若对象的值在任何对客户端可见的方式下创建后不会被修改则称对象是 _不可变_ 的。对象既可以是不可变的,也可以是可变的。 -* #### 命令式风格(imperative style) +* ### 命令式风格(imperative style) _命令式风格_ 编程强调严谨的操作序列以令效用能在正确的顺序发生。这种风格的特征是循环迭代、适当变更数据、方法有副作用,是像C, C++, C#和Java等这些语言的主要范式,与[函数式风格](#函数式风格functional-style)相对应。 -* #### 初始化(initialize) +* ### 初始化(initialize) 变量在Scala源码中被定义时,必须用对象对其进行初始化。 -* #### 实例(instance) +* ### 实例(instance) _实例_ ,或叫类实例,是个对象,是个仅在运行时存在的概念 -* #### 实例化(instantiate) +* ### 实例化(instantiate) _实例化_ 类是根据类创建一个新对象,是仅在运行时发生的动作。 -* #### 不变性(invariant) +* ### 不变性(invariant) _不变性_ 用在两个地方。首先在数据结构组织良好的情况下它可以表示某个属性始终为真。比如,若排序二叉树具有右子节点,则其各节点就会在其右子节点前完成排序,这就属于排序二叉树的不变性。其次有时不变性也作为非协变的同义词,如:"类`Array`在类型参数上具备不变性"。 -* #### 调用(invoke) +* ### 调用(invoke) 在实参上 _调用_ 方法、函数或闭包,意即其方法体会在指定实参上执行。 -* #### Java虚拟机(JVM) +* ### Java虚拟机(JVM) _JVM_ 是Java虚拟机(#runtime)的缩写,或叫[运行时](#运行时runtime),是运行Scala程序的宿主。 -* #### 字面量(literal) +* ### 字面量(literal) `1`,`"One"`,和`(x: Int) => x + 1`是 _字面量_ 的例子,字面量是描述对象的便捷方式,便捷在这种方式正好反映了所创建对象的结构。 -* #### 局部函数(local function) +* ### 局部函数(local function) _局部函数_ 是块内`def`定义的,作为对比,同为`def`定义的作为类、特质或单例对象的成员则被称作[方法](#方法method)。 -* #### 局部变量(local variable) +* ### 局部变量(local variable) _局部变量_ 是块内`val`或`var`定义的。尽管函数参数和[局部变量](#局部变量local-variable)类似,但并不叫局部变量,而是去掉"局部"直接叫"参数"或"变量"。 -* #### 成员(member) +* ### 成员(member) _成员_ 是类、特质或单例对象模板中被命名的元素。成员可通过所有者名称,点及其简名访问。例如,定义在类的最顶层字段和方法是这个类的成员。定义在类中的特质是包围它的类的成员。类中通过type关键字定义的类型是这个类的成员。类是其定义所在的包的成员。相比之下,局部变量或局部函数就不是包围他们的块的成员。 -* #### 消息(message) +* ### 消息(message) Actor是通过给彼此之间发送 _消息_ 来进行通信的。发送消息不会打断接收者正在处理的事情,接收者可一直等到当前活动结束且其不变性被重建之后。 -* #### 元编程(meta-programming) +* ### 元编程(meta-programming) 元编程程序是指其输入是其自身程序的程序。编译器都是元程序,像`scaladoc`这样的工具也是。要用注解做任何事都需要元编程程序。 -* #### 方法(method) +* ### 方法(method) _方法_ 就是类、特质或单例对象的成员函数。 -* #### 混入(mixin) +* ### 混入(mixin) _混入_ 就是特质用在混入组合时的名称。换言之,在"`trait Hat`"里面,`Hat`仅为特质,而"`new Cat extends AnyRef with Hat`"里面的`Hat`就可叫混入。用作动词时,"混"和"入"("mix in")是两个词。比如,可 _混_ 特质 _入_ 至类或其他特质。 -* #### 混入组合(mixin composition) +* ### 混入组合(mixin composition) 把特质混入类或其他特质的过程。_混入组合_ 与传统的多重继承不同之处在于父级引用的类型不是在特质定义时已知的,而是在每次特质每次混入到类或其他特质时才被重新确定。 -* #### 修饰符(modifier) +* ### 修饰符(modifier) 用来以某种方式限定类、特质、字段或方法等的定义的关键字。比如,`private`修饰符表明被定义的类、特质、字段或方法是私有的。 -* #### 多重定义(multiple definitions) +* ### 多重定义(multiple definitions) 通过使用类似这样的语法`val v1, v2, v3 = exp`,同一个表达式根据 _多重定义_ 概念可被赋值给多个变量。 -* #### 非协变(nonvariant) +* ### 非协变(nonvariant) 类或特质的类型参数默认是 _非协变_ 的,故而参数变化并不会子类化相应的类或特质。比如,因类`Array`非协变于其类型参数,故`Array[String]`既非`Array[Any]`之子类,亦非其超类。 -* #### 操作(operation) +* ### 操作(operation) 在Scala中,每个 _操作_ 都是一个方法调用。方法也可以 _操作符符号_ 的方式被调用,像在`b + 2`里面符号`+`就是一个 _操作符_。 -* #### 参数(parameter) +* ### 参数(parameter) 函数可带有零至多个 _参数_,每个参数都有名称和类型。参数与实参之间的区别在于函数调用时实参指向具体的对象,参数则是指向这些传入实参的变量。 -* #### 无参函数(parameterless function) +* ### 无参函数(parameterless function) 不带参数的函数,其定义时没有任何空括号。无参函数可不带括号调用,这种方式符合[统一访问原则](#统一访问原则uniform-access-principle),就是在客户端不改变任何代码的情况下把`def`改成`val`。 -* #### 无参方法(parameterless method) +* ### 无参方法(parameterless method) _无参方法_ 就是作为类、特质或单例对象成员的无参函数。 -* #### 参数化字段(parametric field) +* ### 参数化字段(parametric field) 定义为类参数的字段。 -* #### 偏应用函数(部分应用函数)(partially applied function) +* ### 偏应用函数(部分应用函数)(partially applied function) 用在表达式中,省掉某些参数的函数。例如:若函数`f`的类型为`Int => Int => Int`,则`f`和`f(1)`就是 _偏应用函数_。 -* #### 路径依赖类型(path-dependent type) +* ### 路径依赖类型(path-dependent type) 类似`swiss.cow.Food`的一种类型,`swiss.cow`部分形成一个对象引用的路径。这种类型的含义对于用来访问它的路径是敏感的,比如,`swiss.cow.Food`和`fish.Food`这两个是不同的类型。 -* #### 模式(pattern) +* ### 模式(pattern) 在`match`表达式的某个备选项中,_模式_ 跟在`case`关键字后,先于 _模式守卫_ 或`=>`符号二者之一。 -* #### 模式守卫(pattern guard) +* ### 模式守卫(pattern guard) 在`match`表达式的某个备选项中,_模式守卫_ 可跟在某个[模式](#模式pattern)后面。比如,"`case x if x % 2 == 0 => x + 1`"中的模式守卫为"`if x % 2 == 0`"。带有模式守卫的备选项(case)仅当其模式匹配了并且模式守卫为真的时候才会被选中。 -* #### 断言(predicate) +* ### 断言(predicate) 断言是结果类型为`Boolean`的函数。 -* #### 主构造器(primary constructor) +* ### 主构造器(primary constructor) 类的主要构造器,会调用超类构造器,如果有必要,也会初始化字段进行传值,并且会执行类的花括号内定义的的顶层(top-level)代码。字段仅由不传给超类构造器的值参数做初始化,那些类构造体内因未用到而被优化掉的除外。 -* #### 过程(procedure) +* ### 过程(procedure) _过程_ 是结果类型为`Unit`的函数,其存在的理由仅为产生副作用。 -* #### 可重新赋值(reassignable) +* ### 可重新赋值(reassignable) 变量可以是可重新赋值的,也可以不是可重新赋值的。`var`是可重新赋值的,而`val`则不是。 -* #### 递归的(recursive) +* ### 递归的(recursive) 若函数可调用自身就说它是 _递归的_。若函数调用自身的唯一位置是函数的最后一个表达式,则函数是[尾递归](#尾递归tail-recursive)的。 -* #### 引用(reference) +* ### 引用(reference) _引用_ 是指针的Java抽象,可唯一确定存在JVM堆中的对象。引用类型变量持有对象的引用,因为引用类型(`AnyRef`的实例)是存在JVM堆上的Java对象实现的。相比之下,值类型变量有时会持有一个(装箱类型的)引用,也有时(当对象表示基础类型值的时候)不会。一般说来,Scala变量[指向](#指向refers)对象。术语"指向"比"持有引用"更加抽象。如果类型为`scala.Int`的变量当前代表Java基础类型`int`的值,则这个变量仍然指向`Int`对象,但并不涉及任何引用。 -* #### 引用相等性(reference equality) +* ### 引用相等性(reference equality) _引用相等性_ 意思是两个引用指向同一个Java对象。引用相等性仅针对引用类型有意义,是可以通过调用`AnyRef`中的`eq`来确定的(在Java程序中,引用相等性通过在Java[引用类型](#引用类型reference-type)上调用`==`来确定)。 -* #### 引用类型(reference type) +* ### 引用类型(reference type) _引用类型_ 是`AnyRef`的子类。在运行时,引用类型的实例常驻JVM堆中。 -* #### 引用透明(referential transparency) +* ### 引用透明(referential transparency) 独立于临时上下文且无副作用的函数属性。对于特定输入,引用透明函数的调用可由其结果替换而不改变程序语义。 -* #### 指向(refers) +* ### 指向(refers) 运行的Scala程序中的变量常 _指向_ 某个对象。变量即使被赋为`null`,概念上也是指向`Null`对象。在运行时,对象可由Java对象或基础类型值来实现,不过Scala允许程序员在更高层次抽象代码以他们设想的方式运行。参见[引用](#引用reference). -* #### 精化类型(refinement type) +* ### 精化类型(refinement type) 通过提供基础类型及其构造体花括号内若干成员而形成的类型。花括号内的成员精细化了基础类型所体现的类型。比如,"食草动物"(animal that eats grass)的类型为`Animal { type SuitableFood = Grass }`。 A type formed by supplying a base type a number of members inside curly braces. The members in the curly braces refine the types that are present in the base type. For example, the type of “animal that eats grass” is `Animal { type SuitableFood = Grass }`. -* #### 结果(result) +* ### 结果(result) Scala程序中的表达式会产生 _结果_。Scala中的每个表达式的结果都是对象。 -* #### 结果类型(result type) +* ### 结果类型(result type) 方法的 _结果类型_ 是调用方法所产生的值的类型。(在Java中,这个概念被称为返回类型) -* #### 返回(return) +* ### 返回(return) Scala程序中的函数会 _返回_ 值,可把这个值叫做函数的[结果](#结果result)。也可以说函数 _结果是_ 某个值。Scala中的每个函数结果都是一个对象。 -* #### 运行时(runtime) +* ### 运行时(runtime) 正在运行Scala程序的宿主Java虚拟机,或宿主[JVM](#java虚拟机jvm)。运行时这个概念包含了Java虚拟机规范中定义的虚拟机以及Java API和标准Scala API的运行时库。在运行时的这个阶段,意味着程序正在运行中,与编译时是相对的概念。 -* #### 运行时类型(runtime type) +* ### 运行时类型(runtime type) 对象在运行时的类型。相比之下,[静态类型](#静态类型static-type)指的是表达式在编译时的类型。多数运行时类型都是无类型参数的裸类,比如,`"Hi"`的运行时类型是`String`,`(x: Int) => x + 1`的运行时类型是`Function1`。运行时类型可通过`isInstanceOf`来检测。 -* #### 脚本(script) +* ### 脚本(script) 包含顶层定义和语句,可直接通过`scala`命令来跑而无需显式编译的文件就是脚本,脚本结尾必须是表达式,而不能是定义。 -* #### 选择器(selector) +* ### 选择器(selector) `match`表达式中被匹配的值。比如,在"`s match { case _ => }`"中,选择器是`s`。 -* #### 自身类型(self type) +* ### 自身类型(self type) 特质的 _自身类型_ 是特质中用到的接收者`this`的假想类型。任何混入特质的具体类必须要确保其类型符合特质的自身类型。自身类型常被用来把大类分解为若干个特质([Programming in Scala](https://www.artima.com/shop/programming_in_scala)第29章有述)。 -* #### 半结构化数据(semi-structured data) +* ### 半结构化数据(semi-structured data) XML数据就是半结构化的,因其相比于普通的二进制文件或文本文件更加结构化,而又不像编程语言的数据结构具备完全结构化。 -* #### 序列化(serialization) +* ### 序列化(serialization) 可把对象 _序列化_ 成字节流,以便将其保存至文件或通过网络传输。之后可对字节流进行 _反序列化_ (可发生在不同计算机上)来获取和原始被序列化的对象一样的对象。 -* #### 遮掩(shadow) +* ### 遮掩(shadow) 局部变量的重新声明会 _遮掩_ 作用域内相同名称的变量声明。 -* #### 签名(signature) +* ### 签名(signature) _签名_ 是[类型签名](#类型签名type-signature)的简写。 -* #### 单例对象(singleton object) +* ### 单例对象(singleton object) 由object关键字定义的对象。每个单例对象有且仅有一个实例。与某个类共享名称且与这个类定义在同一源文件中的单例对象,叫这个类的[伴生对象](#伴生对象companion-object),类则叫单列对象的[伴生类](#伴生类companion-class)。无伴随类的单例对象叫[独立对象](#独立对象standalone-object)。 -* #### 独立对象(standalone object) +* ### 独立对象(standalone object) 没有[伴生类](#伴生类companion-class)的[单例对象](#单例对象singleton-object)。 -* #### 语句(statement) +* ### 语句(statement) 指的是表达式、定义或包导入等这些可放到Scala源码的模板或块中的东西。 -* #### 静态类型(static type) +* ### 静态类型(static type) 参见[类型](#类型type)。 -* #### 结构类型(structural type) +* ### 结构类型(structural type) 也是一种[精化类型](#精化类型refinement-type),只是精化的目标是未在基类型中的成员。比如`{ def close(): Unit }`就是结构类型,因其基类型是`AnyRef`,而`AnyRef`并无名为`close`的成员。 -* #### 子类(subclass) +* ### 子类(subclass) 一个类是其所有[超类](#超类superclass)和[超特质](#超特质supertrait)的 _子类_。 -* #### 子特质(subtrait) +* ### 子特质(subtrait) 一个特质是其所有[超特质](#超特质supertrait)的 _子特质_。 -* #### 子类型(subtype) +* ### 子类型(subtype) Scala编译器允许任何类型在需要该类型的地方使用其 _子类型_ 作为替代。对不带类型参数的类和特质来说,子类型的关系会反映子类的关系。比如,若类`Cat`是抽象类`Animal`的子类,且也不带类型参数,则类型`Cat`就是类型`Animal`的子类型。同样,若特质`Apple`是特质`Fruit`的子特质且无类型参数,则类型`Apple`就是类型`Fruit`的子类型。而对于带有类型参数的类和特质来说,协变就起作用了。比如,由于抽象类`List`被声明为在其长类型参数上是协变的(例,`List`被声明为`List[+A]`),`List[Cat]`是`List[Animal]`的子类型,`List[Apple]`是`List[Fruit]`的子类型。尽管这些类型的类都是`List`,但其子类型的关系依旧是存在的。对比来看,因为`Set`未被声明在其类型参数上是协变的(例,`Set`被声明为`Set[A]`,不带加号),所以`Set[Cat]`并不是`Set[Animal]`的子类型。子类型应该正确实现其超类型的契约,以便应用里氏替换原则(Liskov Substitution Principle),不过编译器仅会在类型检查级别核验此属性。 -* #### 超类(superclass) +* ### 超类(superclass) 一个类的 _超类_ 包括其直接超类,其直接超类的直接超类,等等一直到`Any`。 -* #### 超特质(supertrait) +* ### 超特质(supertrait) 类或特质的 _超特质_,如果有的话,就包括所有直接混入类或特质或其任意超类的特质,以及这些特质的所有超特质。 -* #### 超类型(supertype) +* ### 超类型(supertype) 类型是其所有子类型的 _超类型_。 -* #### 合成类(synthetic class) +* ### 合成类(synthetic class) 合成类是编译器自动生成的而不是程序员手写的。 -* #### 尾递归(tail recursive) +* ### 尾递归(tail recursive) 函数是 _尾递归_ 的,仅当函数调用自身的地方是函数的最后一条操作。 -* #### 目标类型化(target typing) +* ### 目标类型化(target typing) _目标类型化_ 是参考所需类型来进行类型推导的一种形式。比如在`nums.filter((x) => x > 0)`中,Scala编译器能推导出`x`的类型是`nums`的元素类型,因为`filter`方法会在`nums`的每个元素上调用函数。 -* #### 模板(template) +* ### 模板(template) _模板_ 是类、特质或单例对象定义体,它定义了类、特质或对象的类型签名,行为以及初始状态。 -* #### 特质(trait) +* ### 特质(trait) _特质_ 通过`trait`关键字定义,是像抽象类一样不带任何值参数,并且可通过被称为[混入组合](#混入组合mixin-composition)的过程"混入到"类或其他特质。当某个特质被混入到其他类或特质,它就被叫做[混入](#混入mixin)。特质可通过一个或多个类型参数化。用类型来参数化后,特质就形成了类型。比如,`Set`是带有单类型参数的特质,而`Set[Int]`却是一个类型。`Set`也被说成是类型`Set[Int]`的"特质"。 -* #### 类型(type) +* ### 类型(type) Scala程序中每个变量和表达式都有编译时确定的 _类型_。类型可以在运行时限定变量能指向的值和表达式所能产生的值。如果有必要从对象的[运行时类型](#运行时类型runtime-type)的角度对变量和表达式的类型进行区分的话,他们也被称为 _静态类型_。换句话说,"类型"这个词本身意味着静态类型。类型与类是区分开的因为带有类型参数的类可以构成许多类型。比如,`List`是类不是类型,`List[T]`则是一个带有自由类型参数的类型,`List[Int]`和`List[String]`也是类型(称为实类型因为他们没有自由类型参数)。类型可以有"[类](#类class)"或"[特质](#特质trait)",比如类型`List[Int]`的类是`List`,类型`Set[String]`的特质是`Set`。 -* #### 类型约束(type constraint) +* ### 类型约束(type constraint) 有些[注解](#注解annotation)是 _类型约束_,意味着他们会对类型能够包含的取值增加额外的限制或约束。比如,`@positive`可以是类型`Int`的类型约束,用来限制32位整型的取值为正的整数。类型约束虽然不会被标准Scala编译器检查,但相应的必须可被额外的工具或编译器插件检查。 -* #### 类型构造器(type constructor) +* ### 类型构造器(type constructor) 带类型参数的类或特质。 -* #### 类型参数(type parameter) +* ### 类型参数(type parameter) 必须被填入类型的泛型类或泛型方法的参数。比如,类`List`定义为"`class List[T] { . . . `",对象`Predef`的一个成员方法`identity`定义为"`def identity[T](x:T) = x`",二者定义中的`T`就是类型参数。 -* #### 类型签名(type signature) +* ### 类型签名(type signature) 方法的 _类型签名_ 包括名称,参数(如果有的话)的数量、顺序和类型,以及结果类型。类、特质或单例对象的类型签名包括名称,所有成员和构造器的类型签名,及其声明的继承和混入关系。 -* #### 统一访问原则(uniform access principle) +* ### 统一访问原则(uniform access principle) _统一访问原则_ 指的是变量和无参函数应以相同的语法来访问。Scala通过不允许在无参函数的调用点放置括号来支持该原则。这样的话,无参函数定义就可以改成`val`而不影响客户端代码,_反之亦然_。 -* #### 不可达(unreachable) +* ### 不可达(unreachable) 在Scala层面,对象可以是 _不可达_ 的,此时其所占据的内存可被运行时回收。不可达并不一定意味着未被引用。引用类型(`AnyRef`的实例)被实现为驻于JVM堆上的对象。当引用类型的实例不可达后,它也确实不被引用了,且可被垃圾回收。值类型(`AnyVal`的实例)可被实现为驻于堆中的基础类型值或Java包装类型实例(如`java.lang.Integer`)。值类型实例可在指向他们的变量的整个生命周期内被装箱(从基础类型值转成包装类型对象)或拆箱(从包装类型对象转成基础类型值)。若表现为JVM堆上的包装类型对象的值类型实例不可达,那就确实不会被引用并且可被垃圾回收。但是若正在表现为基础类型值的值类型不可达,则其仍可被引用,因为此时它并不会以作为对象驻于JVM堆上。运行时可回收不可达对象所占据的内存,但是假如一个Int在运行时被实现为Java基础类型int,在一个运行中的方法的栈帧上占据了一些内存,则这个对象的内存将在方法运行完成且栈帧弹出时才被回收。引用类型的内存,比如`Strings`,可在不可达之后由JVM的垃圾收集器回收。 -* #### 未引用(unreferenced) +* ### 未引用(unreferenced) 参见[不可达](#不可达unreachable)。 -* #### 值(value) +* ### 值(value) Scala中的任何计算或表达式的结果都是一个 _值_,而Scala中的每个值都是一个对象。值这个术语本质上是指对象在内存中(在JVM堆或栈上)的镜像。 -* #### 值类型(value type) +* ### 值类型(value type) _值类型_ 是`AnyVal`的任意子类,像`Int`,`Double`或`Unit`。该术语具有Scala源码级别的意味。在运行时,对应于Java基础类型的值类型实例可由基础类型值或包装类型实例来实现,比如`java.lang.Integer`。在值类型实例的整个生命周期内,运行时可将其在基础类型和包装类型间来回转换(如,对其装箱和拆箱)。 -* #### 变量(variable) +* ### 变量(variable) 指向对象的命名实体。变量要么是`val`,要么是 `var`,`val`变量和`var`变量在定义时都必须被初始化,但仅`var`变量可被重新赋值来指向不同对象。 -* #### 型变(variance) +* ### 型变(variance) 类或特质的类型参数可用 _型变_ 标号来做标记,即[协变](#协变covariant)(+)或[逆变](#逆变contravariant)(-)。这样的型变标号表明了泛型类或特质的子类化是如何开展的,比如,泛型类`List`在其类型参数上是协变的,因此`List[String]`就是`List[Any]`的子类型。默认情况下,即缺少标号`+`或`-`的类型参数是[非协变](#非协变nonvariant)的。 -* #### 产生(yield) +* ### 产生(yield) 表达式可以 _产生_ 结果。`yield`关键字指定了[for推解式](#for推解式for-comprehension)的结果。 diff --git a/_zh-cn/index.md b/_zh-cn/index.md index fd73fb62fc..53df7a316e 100644 --- a/_zh-cn/index.md +++ b/_zh-cn/index.md @@ -1,112 +1,110 @@ --- -layout: documentation -title: 文档 +layout: landing-page language: zh-cn -partof: documentation -discourse: true -# Content masthead links +title: 学习 Scala +namespace: root +discourse: true +partof: documentation more-resources-label: 更多资源 +redirect_from: + - /scala3/ + - /scala3/index.html -scala3-sections: -- title: "第一步" - links: - - title: "Scala 3 中的新东西" - description: "Scala 3 现存新特性概览" - icon: "fa fa-star" - link: /scala3/new-in-scala3.html - - title: "快速开始" - description: "安装 Scala 3 并开始写些 Scala 代码" - icon: "fa fa-rocket" - link: /scala3/getting-started.html - - title: "Scala 3 书籍" - description: "一部介绍主要语言特性的线上书" - icon: "fa fa-book" - link: /scala3/book/introduction.html -- title: "更多细节" - links: - - title: "迁移指引" - description: "一份帮你从 Scala 2 迁移到 Scala 3 的指引" - icon: "fa fa-suitcase" - link: /scala3/guides/migration/compatibility-intro.html - - title: "导览" - description: "关于语言特别之处的详细导览" - icon: "fa fa-map" - link: /zh-cn/scala3/guides.html - - title: "Scala 库 API" - description: "Scala 3 标准库API文档(多个小版本)" - icon: "fa fa-file-alt" - link: https://scala-lang.org/api/3.x/ - - title: "语言参考手册" - description: "Scala 3 语言参考手册" - icon: "fa fa-book" - link: /scala3/reference/overview.html - - title: "贡献指南" - description: "Scala 3 编译器指南及如何贡献代码" - icon: "fa fa-cogs" - link: /scala3/guides/contribution/contribution-intro.html - - title: "Scala 3 全新的 Scaladoc" - description: "Scaladoc 新特性重点介绍" - icon: "fa fa-star" - link: /scala3/scaladoc.html - - title: "演讲" - description: "可在线观看的关于 Scala 3 的演讲" - icon: "fa fa-play-circle" - link: /scala3/talks.html - -scala2-sections: - +sections: - title: "第一步..." links: - title: "快速开始" - description: "在电脑上安装Scala然后开始写些Scala代码吧!" + description: "在电脑上安装 Scala 然后开始写些 Scala 代码吧!" icon: "fa fa-rocket" link: /getting-started.html - - title: "Scala之旅" + - title: "scala之旅" description: "核心语言特性简介" icon: "fa fa-flag" link: /zh-cn/tour/tour-of-scala.html - - title: "Java程序员Scala入门" - description: "为具有Java背景的程序员准备的Scala简介" - icon: "fa fa-coffee" - link: /zh-cn/tutorials/scala-for-java-programmers.html - more-resources: - - title: 在线课程、练习和博客 - url: /learn.html + - title: "Scala 3 册子" + description: "通过一系列小课程来学习 Scala。" + icon: "fa fa-book" + link: /zh-cn/scala3/book/introduction.html + - title: "Scala 工具箱" + description: "发送 HTTP 请求,写文件,运行进程,解析 JSON... " + icon: "fa fa-toolbox" + link: /toolkit/introduction.html + - title: "在线课程" + description: "新手和有经验的程序员在 MOOCS 学习 Scala。" + icon: "fa fa-cloud" + link: /online-courses.html - title: 书籍 - url: /books.html + description: "有关 Scala 的印刷和数字化的书籍。" + icon: "fa fa-book" + link: /books.html + - title: 教程 + description: "通过一系列步骤,手把手教你创建 Scala 应用。" + icon: "fa fa-tasks" + link: /tutorials.html - - title: "回归用户" + - title: "回归用户" links: - - title: "API" - description: "各个Scala版本的API文档" + - title: "api" + description: "各个 Scala 版本的 api 文档" icon: "fa fa-file-alt" link: /api/all.html - - title: "总览" - description: "涵盖Scala各种特性的深度分析文档" + - title: "导引和总览" + description: "涵盖 Scala 各种特性的深度分析文档" icon: "fa fa-database" link: /zh-cn/overviews/index.html - title: "风格引导" - description: "深度指导如何写出地道的Scala代码" + description: "深度指导如何写出地道的 Scala 代码" icon: "fa fa-bookmark" link: /style/index.html - title: "速查" - description: "包含Scala基础语法的速查手册" + description: "包含 Scala 基础语法的速查手册" icon: "fa fa-list" link: /zh-cn/cheatsheets/index.html - - title: "Scala常见问题" - description: "Scala语言特性的常见问题及答案" + - title: "Scala 常见问题" + description: "Scala 语言特性的常见问题及答案。" icon: "fa fa-question-circle" link: /tutorials/FAQ/index.html - - title: "语言规范" - description: "Scala正式语言规范" + - title: "语言规范 v2.x" + description: "Scala 2 正式的语言规范。" + icon: "fa fa-book" + link: https://scala-lang.org/files/archive/spec/2.13/ + - title: "语言规范 v3.x" + description: "Scala 3 正式的语言规范。" icon: "fa fa-book" - link: https://scala-lang.org/files/archive/spec/2.12/ + link: https://scala-lang.org/files/archive/spec/3.4/ + - title: "Scala 3 语言参考" + description: "Scala 3 语言参考" + icon: "fa fa-book" + link: https://docs.scala-lang.org/scala3/reference + + - title: "探索 Scala 3" + links: + - title: "迁移指引" + description: "一份帮你从 Scala 2 迁移到 Scala 3 的指引。" + icon: "fa fa-suitcase" + link: /scala3/guides/migration/compatibility-intro.html + - title: "Scala 3 中的新东西" + description: "Scala 3 中令人兴奋的新特性概览" + icon: "fa fa-star" + link: /zh-cn/scala3/new-in-scala3.html + - title: "Scala 3 全新的 Scaladoc" + description: "Scaladoc 新特性重点介绍" + icon: "fa fa-star" + link: /scala3/scaladoc.html + - title: "演讲" + description: "可在线观看的关于 Scala 3 的演讲" + icon: "fa fa-play-circle" + link: /scala3/talks.html - - title: "Scala进展" + - title: "Scala 进展" links: - - title: "SIPs" - description: "Scala改进过程(Scala Improvement Process),语言及编译器进展" + - title: "Scala 改进过程" + description: "描述涉及语言的过程,和所有 Scala 改进提案(SIPs)的列表。" icon: "fa fa-cogs" link: /sips/index.html + - title: "成为 Scala OSS 贡献者" + description: "从头到尾:发现你如何帮助 Scala 开源生态系统。" + icon: "fa fa-code-branch" + link: /contribute/ --- 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 72ad6c8e1b..0000000000 --- a/_zh-cn/overviews/core/actors-migration-guide.md +++ /dev/null @@ -1,463 +0,0 @@ ---- -layout: singlepage-overview -title: Scala Actors迁移指南 - -partof: actor-migration - -language: zh-cn ---- - -**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](https://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means)机制。Monitoring不同于连接,因为它是单向(unindirectional)的并且终止的原因是现在已知的。如果仅仅是monitoring机制是无法满足需求的,迁移的链接必须推迟到最后一刻(步骤5的迁移)。然后,当迁移到Akka,用户必须创建一个[监督层次(supervision hierarchy)](https://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](https://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库和[Scala distribution](https://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](https://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means)来完成,一部分由[actor monitoring](https://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](https://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](https://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](https://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](https://doc.akka.io/docs/akka/2.1.0/scala/remoting.html)。 - -#### 样例和问题 - -这篇文档中的所有程序片段可以在[Actors Migration test suite](https://github.com/scala/actors-migration/tree/master/src/test/)中找到,这些程序做为测试文件,前缀为actmig。 - -这篇文档和Actor移植组件由 [Vojin Jovanovic](https://people.epfl.ch/vojin.jovanovic)和[Philipp Haller](https://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 ab584a6a0e..0000000000 --- a/_zh-cn/overviews/core/actors.md +++ /dev/null @@ -1,306 +0,0 @@ ---- -layout: singlepage-overview -title: The Scala Actors API - -partof: actors - -language: zh-cn ---- - -**Philipp Haller 和 Stephen Tu 著** - -## 简介 - -本指南介绍了Scala 2.8和2.9中`scala.actors`包的API。这个包的内容因为逻辑上相通,所以放到了同一个类型的包内。这个trait在每个章节里面都会有所涉及。这章的重点在于这些traits所定义的各种方法在运行状态时的行为,由此来补充现有的Scala基础API。 - -注意:在Scala 2.10版本中这个Actors库将是过时的,并且在未来Scala发布的版本中将会被移除。开发者应该使用在`akka.actor`包中[Akka](https://akka.io/) actors来替代它。想了解如何将代码从Scala actors迁移到Akka请参考[Actors 迁移指南](https://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/futures.md b/_zh-cn/overviews/core/futures.md index 26da10969d..97f64ed7aa 100644 --- a/_zh-cn/overviews/core/futures.md +++ b/_zh-cn/overviews/core/futures.md @@ -11,491 +11,1254 @@ language: zh-cn ## 简介 -Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓Future,指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。 +Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓 [`Future`](https://www.scala-lang.org/api/current/scala/concurrent/Future.html),指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。 -默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了flatMap、foreach和filter等算子,使得我们能够以非阻塞的方式对future进行组合。当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。 +默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了 `flatMap`、`foreach` 和 `filter` 等算子,使得我们能够以非阻塞的方式对future进行组合。 +当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。 + + + +典型的 future 像这样: + +{% tabs futures-00 %} +{% tab 'Scala 2 and 3' for=futures-00 %} + +```scala +val inverseFuture: Future[Matrix] = Future { + fatMatrix.inverse() // non-blocking long lasting computation +}(executionContext) +``` + +{% endtab %} +{% endtabs %} + +或者更习惯的用法: + +{% tabs futures-01 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-01 %} + +```scala +implicit val ec: ExecutionContext = ... +val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() +} // ec is implicitly passed +``` + +{% endtab %} + +{% tab 'Scala 3' for=futures-01 %} + +```scala +given ExecutionContext = ... +val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() +} // execution context is implicitly passed +``` + +{% endtab %} +{% endtabs %} + +这两个代码片段都将 `fatMatrix.inverse()` 的执行委托给 `ExecutionContext`,并在 `inverseFuture` 中体现计算结果。 + +## 执行上下文 + +Future 和 Promises 围绕 [`ExecutionContext`s](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext.html) 展开,负责执行计算。 + +`ExecutionContext` 类似于 [Executor](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): +它可以在新线程、线程池或当前线程中自由地执行计算(尽管不鼓励在当前线程中执行计算 -- 更多内容见下文)。 + +`scala.concurrent` 包是开箱即用的,它带有 `ExecutionContext` 实现,一个全局静态线程池。 +它也可以将 `Exector` 转换为 `ExecutionContext`。 +最后,用户可以自由扩展 `ExecutionContext` trait来实现自己的执行上下文,但只有极少数情况下需要这么做。 + +### 全局执行上下文 + +`ExecutionContext.global` 是由 [ForkJoinPool](https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html) 支持的 `ExecutionContext`。 +它应该满足大部分情况,但有几点需要注意。 +`ForkJoinPool` 管理有限数量的线程(线程的最大数量由 *parallelism level* 指定)。 +仅当每个阻塞调用都包装在 `blocking` 调用中时(更多内容见下文),并发阻塞计算的数量才能超过并行度级别。 +否则,全局执行上下文中的线程池会有饥饿死锁风险,致使任何计算无法继续进行。 +缺省情况下,`ExecutionContext.global` 将其底层ForkJoin连接池的并行级别设置为可用处理器的数量([Runtime.availableProcessors](https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29))。 +通过设置以下一个(或多个) VM 属性,来重载这个设置: + + * scala.concurrent.context.minThreads - 缺省为 `Runtime.availableProcessors` + * scala.concurrent.context.numThreads - 可以是一个数字,或者是 “xN” 这样形式中的乘数(N);缺省为 `Runtime.availableProcessors` + * scala.concurrent.context.maxThreads - 缺省为 `Runtime.availableProcessors` + +只要并行度的数值在 `[minThreads; maxThreads]` 范围内,它就可以给 `numThreads` 赋值。 + +如上所述,在存在阻塞计算的情况下,`ForkJoinPool` 可以将线程数增加到超过 `parallelismLevel`。 +如 `ForkJoinPool` API 中所述,这只有在明确通知 `ForkJoinPool` 时才有可能: + +{% tabs futures-02 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-02 %} + +```scala +import scala.concurrent.{ Future, ExecutionContext } +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 + } + ) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-02 %} + +```scala +import scala.concurrent.{ Future, ExecutionContext } +import scala.concurrent.forkjoin.* + +// the following is equivalent to `given ExecutionContext = 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 + } + ) +} +``` +{% endtab %} +{% endtabs %} + +幸运的是,并发包为这提供了便捷的方法: + +{% tabs blocking %} +{% tab 'Scala 2 and 3' for=blocking %} + +```scala +import scala.concurrent.Future +import scala.concurrent.blocking + +Future { + blocking { + myLock.lock() + // ... + } +} +``` + +{% endtab %} +{% endtabs %} + +注意 `blocking` 是一个通用结构,它将会在[下面](#future-内的阻塞)作深入探讨。 + +最后你必须记住 `ForkJoinPool` 不是设计用来长连接阻塞操作。 +即使收到 `blocking` 通知,池也可能无法像预期的那样生成新工作,而创建新工作线程时,它们的数量也可能多达 32767。 +为了给您有个概念,以下代码将使用 32000 个线程: + +{% tabs futures-03 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-03 %} + +```scala +implicit val ec = ExecutionContext.global + +for (i <- 1 to 32000) { + Future { + blocking { + Thread.sleep(999999) + } + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-03 %} + +```scala +given ExecutionContext = ExecutionContext.global + +for i <- 1 to 32000 do + Future { + blocking { + Thread.sleep(999999) + } + } +``` + +{% endtab %} +{% endtabs %} + +如果您需要包装持久的阻塞操作,我们建议使用专用的 `ExecutionContext`,例如通过包装Java 的 `Executor`。 + +### 适配 Java Executor + +使用 `ExecutionContext.fromExecutor` 方法,你可以把 `Executor` 包装进 `ExecutionContext`。 +例如: + +{% tabs executor class=tabs-scala-version %} +{% tab 'Scala 2' for=executor %} + +```scala +ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) +``` + +{% endtab %} +{% tab 'Scala 3' for=executor %} + +```scala +ExecutionContext.fromExecutor(ThreadPoolExecutor( /* your configuration */ )) +``` + +{% endtab %} +{% endtabs %} + +### 同步执行上下文 + +也许试图得到一个在当前线程中运行计算的 `ExecutionContext`: + +{% tabs bad-example %} +{% tab 'Scala 2 and 3' for=bad-example %} + +```scala +val currentThreadExecutionContext = ExecutionContext.fromExecutor( + new Executor { + // Do not do this! + def execute(runnable: Runnable) = runnable.run() + }) +``` + +{% endtab %} +{% endtabs %} + +应该避免这种情况,因为它会在执行 future 时引入不确定性。 + +{% tabs bad-example-2 %} +{% tab 'Scala 2 and 3' for=bad-example-2 %} + +```scala +Future { + doSomething +}(ExecutionContext.global).map { + doSomethingElse +}(currentThreadExecutionContext) +``` + +{% endtab %} +{% endtabs %} + +`doSomethingElse` 调用,可能在 `doSomething` 的线程中执行或者在主线程中执行,这样可以是同步的或者是异步的。 +正如[这里](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)解释的,一个调用不能两者都是。 ## Future 所谓Future,是一种用于指代某个尚未就绪的值的对象。而这个值,往往是某个计算过程的结果: -- 若该计算过程尚未完成,我们就说该Future未就位; -- 若该计算过程正常结束,或中途抛出异常,我们就说该Future已就位。 +1. 若该计算过程尚未完成,我们就说该Future **未就位**; +2. 若该计算过程正常结束,或中途抛出异常,我们就说该Future **已就位**。 Future的就位分为两种情况: -- 当Future带着某个值就位时,我们就说该Future携带计算结果成功就位。 -- 当Future因对应计算过程抛出异常而就绪,我们就说这个Future因该异常而失败。 +1. 当 `Future` 带着某个值就位时,我们就说该 future 携带计算结果**成功就位**。 +2. 当 `Future` 因对应计算过程抛出异常而就绪,我们就说这个 future因该异常而**失败**。 -Future的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,future对象就变成了不可变对象——无法再被改写。 +`Future` 的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,`Future` 对象就变成了不可变对象——无法再被改写。 -创建future对象最简单的方法是调用future方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。 +创建future对象最简单的方法是调用 `Future.apply` 方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。 -注意_Future[T]_ 是表示future对象的类型,而future是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。 +注意 _Future[T]_ 是表示future对象的类型,而 `Future.apply` 是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。 这最好通过一个例子予以说明。 假设我们使用某些流行的社交网络的假定API获取某个用户的朋友列表,我们将打开一个新对话(session),然后发送一个请求来获取某个特定用户的好友列表。 - import scala.concurrent._ - import ExecutionContext.Implicits.global +{% tabs futures-04 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-04 %} - val session = socialNetwork.createSessionFor("user", credentials) - val f: Future[List[Friend]] = Future { - session.getFriends() - } +```scala +import scala.concurrent._ +import ExecutionContext.Implicits.global -以上,首先导入scala.concurrent 包使得Future类型和future构造函数可见。我们将马上解释第二个导入。 +val session = socialNetwork.createSessionFor("user", credentials) +val f: Future[List[Friend]] = Future { + session.getFriends() +} +``` -然后我们初始化一个session变量来用作向服务器发送请求,用一个假想的 createSessionFor 方法来返回一个List[Friend]。为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。这能从调用getFriends方法得到解释。为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。future方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。 +{% endtab %} +{% tab 'Scala 3' for=futures-04 %} -一旦服务器响应,future f 中的好友列表将变得可用。 +```scala +import scala.concurrent.* +import ExecutionContext.Implicits.global -未成功的尝试可能会导致一个异常(exception)。在下面的例子中,session的值未被正确的初始化,于是在future的计算中将抛出NullPointerException,future f 不会圆满完成,而是以此异常失败。 +val session = socialNetwork.createSessionFor("user", credentials) +val f: Future[List[Friend]] = Future { + session.getFriends() +} +``` - val session = null - val f: Future[List[Friend]] = Future { - session.getFriends - } +{% endtab %} +{% endtabs %} -`import ExecutionContext.Implicits.global` 上面的线条导入默认的全局执行上下文(global execution context),执行上下文执行执行提交给他们的任务,也可把执行上下文看作线程池,这对于future方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。我们可以定义自己的执行上下文,并在future上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。 +以上,首先导入 `scala.concurrent` 包使得 `Future` 类型可见。 +我们将马上解释第二个导入。 -我们的例子是基于一个假定的社交网络API,此API的计算包含发送网络请求和等待响应。提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。 +然后我们用一个假想的 `createSessionFor` 方法去初始化一个session变量,该变量用作向服务器发送请求。 +为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。 +这能从调用 `getFriends` 方法得到解释,该方法返回 `List[Friend]`。 +为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。`Future.apply` 方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。 - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } +一旦服务器响应,future `f` 中的好友列表将变得可用。 + +未成功的尝试可能会导致一个异常(exception)。在下面的例子中,`session` 的值未被正确的初始化,于是在 `Future` 阻塞中的计算将抛出 `NullPointerException`。 +该future `f` 不会圆满完成,而是以此异常失败: + +{% tabs futures-04b %} +{% tab 'Scala 2 and 3' for=futures-04b %} + +```scala +val session = null +val f: Future[List[Friend]] = Future { + session.getFriends +} +``` + +{% endtab %} +{% endtabs %} + +上面 `import ExecutionContext.Implicits.global` 这行,导入默认的全局执行上下文(global execution context)。 +执行上下文执行提交给他们的任务,也可把执行上下文看作线程池。 +这对于 `Future.apply` 方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。 +可以定义自己的执行上下文,并在 `Future` 上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。 + +我们的例子是基于一个假定的社交网络 API,此 API 的计算包含发送网络请求和等待响应。 +提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。 +当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。 + +{% tabs futures-04c %} +{% tab 'Scala 2 and 3' for=futures-04c %} + +```scala +val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") +} +``` + +{% endtab %} +{% endtabs %} ### Callbacks(回调函数) -现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。我们经常对计算结果感兴趣而不仅仅是它的副作用。 +现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。 +我们经常对计算结果感兴趣而不仅仅是它的副作用。 -在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调,future完成后这个回调称为异步回调。如果当注册回调时future已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。 +在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。 +虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调。 +future 完成后这个回调称为异步回调。如果当注册回调时 future 已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。 -注册回调最通常的形式是使用OnComplete方法,即创建一个`Try[T] => U`类型的回调函数。如果future成功完成,回调则会应用到Success[T]类型的值中,否则应用到` Failure[T] `类型的值中。 +注册回调最通常的形式是使用 `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)的情形。 + `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]——一个近期帖子的列表文本: +回到我们的社交网络的例子,假设我们想要获取我们最近的帖子并显示在屏幕上,我们通过调用 `getRecentPosts` 方法获得一个返回值 `List[String]` -- 一个近期帖子的列表文本: - val f: Future[List[String]] = Future { - session.getRecentPosts - } +{% tabs futures-05 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-05 %} - f onComplete { - case Success(posts) => for (post <- posts) println(post) - case Failure(t) => println("An error has occured: " + t.getMessage) - } +```scala +import scala.util.{Success, Failure} +val f: Future[List[String]] = Future { + session.getRecentPosts +} -onComplete方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,onSuccess 回调使用如下(该回调以一个偏函数(partial function)为参数): +f.onComplete { + case Success(posts) => for (post <- posts) println(post) + case Failure(t) => println("An error has occured: " + t.getMessage) +} +``` - val f: Future[List[String]] = Future { - session.getRecentPosts - } +{% endtab %} +{% tab 'Scala 3' for=futures-05 %} - f onSuccess { - case posts => for (post <- posts) println(post) - } +```scala +import scala.util.{Success, Failure} -对于处理失败结果,onFailure回调使用如下: +val f: Future[List[String]] = Future { + session.getRecentPosts() +} - val f: Future[List[String]] = Future { - session.getRecentPosts - } +f.onComplete { + case Success(posts) => for post <- posts do println(post) + case Failure(t) => println("An error has occurred: " + t.getMessage) +} +``` - f onFailure { - case t => println("An error has occured: " + t.getMessage) - } +{% endtab %} +{% endtabs %} - f onSuccess { - case posts => for (post <- posts) println(post) - } +`onComplete` 方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,可以使用 `foreach` 回调: -如果future失败,即future抛出异常,则执行onFailure回调。 +{% tabs futures-06 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-06 %} -因为偏函数具有 isDefinedAt方法, onFailure方法只有在特定的Throwable类型对象中被定义才会触发。下面例子中的onFailure回调永远不会被触发: +```scala +val f: Future[List[String]] = Future { + session.getRecentPosts() +} - val f = Future { - 2 / 0 - } +for { + posts <- f + post <- posts +} println(post) +``` - f onFailure { - case npe: NullPointerException => - println("I'd be amazed if this printed out.") - } +{% endtab %} +{% tab 'Scala 3' for=futures-06 %} + +```scala +val f: Future[List[String]] = Future { + session.getRecentPosts() +} + +for + posts <- f + post <- posts +do println(post) +``` + +{% endtab %} +{% endtabs %} + +`Future` 提供了一个清晰的手段只用来处理失败的结果,这个手段是使用 `failed` 投影,这个投影把 `Failure[Throwable]` 转换成 `Success[Throwable]`。下面的[投影](#投影)章节提供了这样一个例子。 回到前面查找某个关键字第一次出现的例子,我们想要在屏幕上打印出此关键字的位置: - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } +{% tabs futures-oncomplete %} +{% tab 'Scala 2 and 3' for=futures-oncomplete %} - firstOccurrence onSuccess { - case idx => println("The keyword first appears at position: " + idx) - } +```scala +val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") +} - firstOccurrence onFailure { - case t => println("Could not process file: " + t.getMessage) - } +firstOccurrence.onComplete { + case Success(idx) => println("The keyword first appears at position: " + idx) + case Failure(t) => println("Could not process file: " + t.getMessage) +} +``` - onComplete,、onSuccess 和 onFailure 方法都具有Unit的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个future中是无序的)。 +{% endtab %} +{% endtabs %} -也就是说,我们现在应讨论论何时调用callback。因为callback需要future的值是可用的,所有回调只能在future完成之后被调用。然而,不能保证callback在完成future的线程或创建callback的线程中被调用。反而, 回调(callback)会在future对象完成之后的一些线程和一段时间内执行。所以我们说回调(callback)最终会被执行。 +`onComplete` 和 `foreach` 方法都具有 `Unit` 的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个 future 中是无序的)。 -此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中callback的执行顺序也不尽相同。事实上,callback也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。这意味着在下面的例子中,变量totalA也许不能在计算上下文中被设置为正确的大写或者小写字母。 +也就是说,我们现在应讨论**何时**正好在调用回调(callback)。因为回调需要 future 的值是可用的,所有回调只能在 future 完成之后被调用。 +然而,不能保证回调在完成 future 的线程或创建回调的线程中被调用。 +反而, 回调会在 future 对象完成之后的一些线程和一段时间内执行。所以我们说回调最终会被执行。 - @volatile var totalA = 0 +此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中回调的执行顺序也不尽相同。 +事实上,回调也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。 +这意味着在下面的例子中,变量 `totalA` 也许不能在计算上下文中被设置为正确的大写或者小写字母 `a`。 - val text = Future { - "na" * 16 + "BATMAN!!!" - } +{% tabs volatile %} +{% tab 'Scala 2 and 3' for=volatile %} + +```scala +@volatile var totalA = 0 - text onSuccess { - case txt => totalA += txt.count(_ == 'a') - } +val text = Future { + "na" * 16 + "BATMAN!!!" +} - text onSuccess { - case txt => totalA += txt.count(_ == 'A') - } -以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量totalA得到的预期值为18。然而,它们也可能是并发执行的,于是totalA最终可能是16或2,因为+= 是一个不可分割的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。 +text.foreach { txt => + totalA += txt.count(_ == 'a') +} + +text.foreach { txt => + totalA += txt.count(_ == 'A') +} +``` + +{% endtab %} +{% endtabs %} + +以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量 `totalA` 得到的预期值为`18`。 +然而,它们也可能是并发执行的,于是 `totalA` 最终可能是`16`或`2`,因为 `+=` 不是一个原子性的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。 考虑到完整性,回调的使用情景列在这儿: -- 在future中注册onComplete回调的时候要确保最后future执行完成之后调用相应的终止回调。 +1. 在 future 中注册 `onComplete` 回调的时候要确保最后 future 执行完成之后调用相应的终止回调。 -- 注册onSuccess或者onFailure回调时也和注册onComplete一样,不同之处在于future执行成功或失败分别调用onSuccess或onSuccess的对应的闭包。 +2. 注册 `foreach` 回调时也和注册 `onComplete` 一样,不同之处在于 future 成功完成才会调用闭包。 -- 注册一个已经完成的future的回调最后将导致此回调一直处于执行状态(1所隐含的)。 +3. 在一个已经完成的 future 上注册回调将导致此该回调最终被执行(1所隐含的)。 -- 在future中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的,然而,特定的ExecutionContext执行可能导致明确的顺序。 +4. 在 future 中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的。然而,特定的 `ExecutionContext` 实现可能导致明确的顺序。 -- 在一些回调抛出异常的情况下,其他的回调的执行不受影响。 +5. 在一些回调抛出异常的情况下,其他的回调的执行不受影响。 -- 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用阻塞的构造(例子如下)。 +6. 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用 `blocking` 的构造(例子如下)。 -- 一旦执行完,回调将从future对象中移除,这样更适合JVM的垃圾回收机制(GC)。 +7. 一旦执行完,回调将从 future 对象中移除,这样更适合垃圾回收机制(GC)。 ### 函数组合(Functional Composition)和For解构(For-Comprehensions) -尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的,但是有些时候回调机制并不易于使用,且容易造成冗余的代码。我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的API,我们想要在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题: +尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的。 +但是有些时候回调机制并不易于使用,且容易造成冗余的代码。 +我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的 API,我们只想在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题: - val rateQuote = Future { - connection.getCurrentValue(USD) - } +{% tabs futures-07 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-07 %} + +```scala +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") - } +for (quote <- rateQuote) { + val purchase = Future { + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } - } + for (amount <- purchase) + println("Purchased " + amount + " USD") +} +``` -首先,我们创建一个名为rateQuote的future对象并获得当前的汇率。在服务器返回了汇率且该future对象成功完成了之后,计算操作才会从onSuccess回调中执行,这时我们就可以开始判断买还是不买了。所以我们创建了另一个名为purchase的future对象,用来在可盈利的情况下做出购买决定,并在稍后发送一个请求。最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。 +{% endtab %} +{% tab 'Scala 3' for=futures-07 %} -这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用onSuccess,且不得不在其中嵌套purchase future对象。试想一下,如果在purchase执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在onSuccess的回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。 +```scala +val rateQuote = Future { + connection.getCurrentValue(USD) +} -其二,purchase只是定义在局部范围内--它只能被来自onSuccess内部的回调响应。这也就是说,这个应用的其他部分看不到purchase,而且不能为它注册其他的onSuccess回调,比如说卖掉些别的货币。 +for quote <- rateQuote do + val purchase = Future { + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") + } -为解决上述的两个问题,futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个future对象和一个通过映射来获得该future值的函数,映射方法将创建一个新Future对象,一旦原来的Future成功完成了计算操作,新的Future会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解future的map。 + for amount <- purchase do + println("Purchased " + amount + " USD") +``` -让我们用map的方法来重构一下前面的例子: +{% endtab %} +{% endtabs %} - val rateQuote = Future { - connection.getCurrentValue(USD) - } +首先,我们创建一个名为 `rateQuote` 的 future 对象并获得当前的汇率。 +在服务器返回了汇率且该 future 对象成功完成了之后,计算操作才会从 `foreach` 回调中执行,这时我们就可以开始判断买还是不买了。 +所以我们创建了另一个名为 `purchase` 的 future 对象,用来只在可盈利的情况下做出购买决定,然后发送一个请求。 +最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。 - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } +这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用 `foreach`,在其中嵌套第二个 `purchase` future 对象。试想一下,如果在 `purchase` 执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在 `foreach` 回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。 - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } +其二,`purchase` future 不在余下的代码范围内 -- 它只能被来自 `foreach` 内部的回调响应。这也就是说,这个应用的其他部分看不到 `purchase`,而且不能为它注册其他的 `foreach` 回调,比如说卖掉些别的货币。 -通过对rateQuote的映射我们减少了一次onSuccess的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次使用purchase方法上的映射了。 +为解决上述的两个问题, futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个 future 对象和一个通过映射来获得该 future 值的函数,映射方法将创建一个新 Future 对象,一旦原来的 Future 成功完成了计算操作,新的 Future 会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解 future 的map。 -可是如果isProfitable方法返回了false将会发生些什么?会引发异常?这种情况下,purchase的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和getCurrentValue方法抛出异常会使rateQuote的操作失败。在这些情况下映射将不会返回任何值,而purchase也会会自动的以和rateQuote相同的异常而执行失败。 +让我们用 `map` 组合器来重构一下前面的例子: -总之,如果原Future的计算成功完成了,那么返回的Future将会使用原Future的映射值来完成计算。如果映射函数抛出了异常则Future也会带着该异常完成计算。如果原Future由于异常而计算失败,那么返回的Future也会包含相同的异常。这种异常的传导方式也同样适用于其他的组合器(combinators)。 +{% tabs futures-08 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-08 %} -使之能够在For-comprehensions原则下使用,是设计Future的目的之一。也正是因为这个原因,Future还拥有flatMap,filter和foreach等组合器。其中flatMap方法可以构造一个函数,它可以把值映射到一个姑且称为g的新future,然后返回一个随g的完成而完成的Future对象。 +```scala +val rateQuote = Future { + connection.getCurrentValue(USD) +} -让我们假设我们想把一些美元兑换成瑞士法郎。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。下面是一个在for-comprehensions中使用flatMap和withFilter的例子: +val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") +} - val usdQuote = Future { connection.getCurrentValue(USD) } - val chfQuote = Future { connection.getCurrentValue(CHF) } +purchase.foreach { amount => + println("Purchased " + amount + " USD") +} +``` - val purchase = for { - usd <- usdQuote - chf <- chfQuote - if isProfitable(usd, chf) - } yield connection.buy(amount, chf) +{% endtab %} +{% tab 'Scala 3' for=futures-08 %} - purchase onSuccess { - case _ => println("Purchased " + amount + " CHF") - } +```scala +val rateQuote = Future { + connection.getCurrentValue(USD) +} -purchase只有当usdQuote和chfQuote都完成计算以后才能完成-- 它以其他两个Future的计算值为前提所以它自己的计算不能更早的开始。 +val purchase = rateQuote.map { quote => + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") +} -上面的for-comprhension将被转换为: +purchase.foreach { amount => + println("Purchased " + amount + " USD") +} +``` - val purchase = usdQuote flatMap { - usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) - } +{% endtab %} +{% endtabs %} + +通过在 `rateQuote` 上使用 `map`,我们减少了一次 `foreach` 的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次在 `purchase` 上使用 `map` 了。 + +可是如果 `isProfitable` 方法返回了 `false` 将会发生些什么?会引发异常? +这种情况下,`purchase` 的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和 `getCurrentValue` 方法抛出异常会使 `rateQuote` 的操作失败。 +在这些情况下映射将不会返回任何值,而 `purchase` 也会自动的以和 `rateQuote` 相同的异常而失败。 + +总之,如果原 future 成功完成了,那么返回的 future 将会使用从原 future来的映射值完成。如果映射函数抛出了异常则返回的 future 也会带着一样的异常。如果原 future 由于异常而计算失败,那么返回的 future 也会包含相同的异常。这种异常的传导语义也存在于其余的组合器(combinators)。 + +使之能够在for-comprehensions 中使用,是设计 future 的目的之一。 +因为这个原因,future 还拥有 `flatMap` 和 `withFilter` 组合器。`flatMap` 方法获取一个函数,该函数把值映射到一个新 future `g`,然后返回一个随 `g` 的完成而完成的 future。 + +让我们假设我们想把一些美元兑换成瑞士法郎(CHF)。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。 +下面是一个在 for-comprehensions 中使用 `flatMap` 和 `withFilter` 的例子: + +{% tabs futures-09 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-09 %} + +```scala +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) -这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解flatMap的操作。FlatMap操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。在我们的例子里,flatMap用usdQuote的值把chfQuote的值映射到第三个futrue对象里,该对象用于发送一定量瑞士法郎的购入请求。只有当通过映射返回的第三个future对象完成了计算,purchase才能完成计算。 +purchase foreach { amount => + println("Purchased " + amount + " CHF") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-09 %} + +```scala +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.foreach { amount => + println("Purchased " + amount + " CHF") +} +``` + +{% endtab %} +{% endtabs %} + +`purchase` 只有当 `usdQuote` 和 `chfQuote` 都完成计算以后才能完成-- 它以其他两个 future 的计算值为前提所以它自己的计算不能更早的开始。 + +上面的 for-comprhension 将被转换为: + +{% tabs for-translation %} +{% tab 'Scala 2 and 3' for=for-translation %} + +```scala +val purchase = usdQuote flatMap { + usd => + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) +} +``` + +{% endtab %} +{% endtabs %} + +这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解 `flatMap` 的操作。`flatMap` 操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。 +在我们的例子里,`flatMap` 用 `usdQuote` 的值把 `chfQuote` 的值映射到第三个 futrue 对象里,该对象用于发送一定量瑞士法郎的购入请求。 +只有当通过 `map` 返回的第三个 future 对象完成,结果 future `purchase` 才能完成。 这可能有些难以置信,但幸运的是faltMap操作在for-comprhensions模式以外很少使用,因为for-comprehensions本身更容易理解和使用。 -再说说filter,它可以用于创建一个新的future对象,该对象只有在满足某些特定条件的前提下才会得到原始future的计算值,否则就会抛出一个NoSuchElementException的异常而失败。调用了filter的future,其效果与直接调用withFilter完全一样。 +`filter` 组合器可以用于创建一个新的 future 对象,该对象只有在满足某些特定条件的前提下才会得到原始future的值。否则新 future 就会有 `NoSuchElementException` 的失败。调用了 `filter` 的future,其效果与直接调用 `withFilter` 完全一样。 -作为组合器的collect同filter之间的关系有些类似容器(collections)API里的那些方法之间的关系。 +作为组合器的 `collect` 同 `filter` 之间的关系有些类似容器(collections)API里的那些方法之间的关系。 -值得注意的是,调用foreach组合器并不会在计算值可用的时候阻塞当前的进程去获取计算值。恰恰相反,只有当future对象成功计算完成了,foreach所迭代的函数才能够被异步的执行。这意味着foreach与onSuccess回调意义完全相同。 +由于 `Future` trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看可以包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。 -由于Future trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。 +比方说我们准备在 `rateQuote` 的基础上决定购入一定量的货币,那么 `connection.buy` 方法需要获取购入的 `amount` 和期望的 `quote`。它返回完成购买的数量。假如 `quote` 偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个 `QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的 future 对象返回`0`而不是抛出那个异常,那我们需要使用 `recover` 组合器: -比方说我们准备在rateQuote的基础上决定购入一定量的货币,那么`connection.buy`方法需要知道购入的数量和期望的报价值,最终完成购买的数量将会被返回。假如报价值偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个`QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的Future对象返回0而不是抛出那个该死的异常,那我们需要使用recover组合器: - val purchase: Future[Int] = rateQuote map { - quote => connection.buy(amount, quote) - } recover { - case QuoteChangedException() => 0 - } +{% tabs recover %} +{% tab 'Scala 2 and 3' for=recover %} -这里用到的recover能够创建一个新future对象,该对象当计算完成时持有和原future对象一样的值。如果执行不成功则偏函数的参数会被传递给使原Future失败的那个Throwable异常。如果它把Throwable映射到了某个值,那么新的Future就会成功完成并返回该值。如果偏函数没有定义在Throwable中,那么最终产生结果的future也会失败并返回同样的Throwable。 +```scala +val purchase: Future[Int] = rateQuote.map { + quote => connection.buy(amount, quote) +} recover { + case QuoteChangedException() => 0 +} +``` +{% endtab %} +{% endtabs %} -组合器recoverWith能够创建一个新future对象,当原future对象成功完成计算时,新future对象包含有和原future对象相同的计算结果。若原future失败或异常,偏函数将会返回造成原future失败的相同的Throwable异常。如果此时Throwable又被映射给了别的future,那么新Future就会完成并返回这个future的结果。recoverWith同recover的关系跟flatMap和map之间的关系很像。 +这里用到的 `recover` 能够创建一个新 future 对象,该对象当成功完成时持有和原 future 对象一样的值。如果执行不成功则偏函数的参数会被传递给使原 future 失败的那个 `Throwable` 异常。如果它把 `Throwable` 映射到了某个值,那么新的 future 就会成功完成并返回该值。 +如果偏函数没有定义在 `Throwable` 中,那么最终产生结果的 future 也会失败并返回同样的 `Throwable`。 -fallbackTo组合器生成的future对象可以在该原future成功完成计算时返回结果,如果原future失败或异常返回future参数对象的成功值。在原future和参数future都失败的情况下,新future对象会完成并返回原future对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率: +组合器 `recoverWith` 能够创建一个新 future 对象,当原 future 对象成功完成计算时,新 future 包含有和原 future 相同的结果。若原 future 失败或异常,偏函数将会返回造成原 future 失败的相同的 `Throwable` 异常。如果此时 `Throwable` 又被映射给了别的 future ,那么新 future 就会完成并返回这个 future 的结果。 +`recoverWith` 同 `recover` 的关系跟 `flatMap` 和 `map` 之间的关系很像。 - val usdQuote = Future { - connection.getCurrentValue(USD) - } map { - usd => "Value: " + usd + "$" - } - val chfQuote = Future { - connection.getCurrentValue(CHF) - } map { - chf => "Value: " + chf + "CHF" - } +`fallbackTo` 组合器生成的 future 对象可以在该原 future 成功完成计算时返回结果,如果原 future 失败或异常返回 future 参数对象的成功值。在原 future 和参数 future 都失败的情况下,新 future 对象会完成并返回原 future 对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率: - al anyQuote = usdQuote fallbackTo chfQuote +{% tabs fallback-to %} +{% tab 'Scala 2 and 3' for=fallback-to %} - anyQuote onSuccess { println(_) } +```scala +val usdQuote = Future { + connection.getCurrentValue(USD) +}.map { + usd => "Value: " + usd + "$" +} +val chfQuote = Future { + connection.getCurrentValue(CHF) +} map { + chf => "Value: " + chf + "CHF" +} -组合器andThen的用法是出于纯粹的side-effecting目的。经andThen返回的新Future无论原Future成功或失败都会返回与原Future一模一样的结果。一旦原Future完成并返回结果,andThen后跟的代码块就会被调用,且新Future将返回与原Future一样的结果,这确保了多个andThen调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上: +val anyQuote = usdQuote.fallbackTo(chfQuote) - val allposts = mutable.Set[String]() +anyQuote.foreach { println(_) } +``` - Future { - session.getRecentPosts - } andThen { - case Success(posts) => allposts ++= posts - } andThen { - case _ => - clearAll() - for (post <- allposts) render(post) - } +{% endtab %} +{% endtabs %} -综上所述,Future的组合器功能是纯函数式的,每种组合器都会返回一个与原Future相关的新Future对象。 +组合器 `andThen` 的用法是出于纯粹的side-effecting目的。经 `andThen` 返回的新 future 无论原 future 成功或失败都会返回与原 future 一模一样的结果。 +一旦原 future 完成并返回结果,`andThen` 后跟的代码块就会被调用,且新 future 将返回与原 future 一样的结果,这确保了多个 `andThen` 调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上: -### 投影(Projections) +{% tabs futures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-10 %} -为了确保for解构(for-comprehensions)能够返回异常,futures也提供了投影(projections)。如果原future对象失败了,失败的投影(projection)会返回一个带有Throwable类型返回值的future对象。如果原Future成功了,失败的投影(projection)会抛出一个NoSuchElementException异常。下面就是一个在屏幕上打印出异常的例子: +```scala +val allposts = mutable.Set[String]() - val f = Future { - 2 / 0 - } - for (exc <- f.failed) println(exc) +Future { + session.getRecentPosts +} andThen { + case Success(posts) => allposts ++= posts +} andThen { + case _ => + clearAll() + for (post <- allposts) render(post) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-10 %} + +```scala +val allPosts = mutable.Set[String]() + +Future { + session.getRecentPosts() +}.andThen { + case Success(posts) => allPosts ++= posts +}.andThen { + case _ => + clearAll() + for post <- allPosts do render(post) +} +``` +{% endtab %} +{% endtabs %} + +综上所述,在 future 上的组合器功能是纯函数式的。 +每种组合器都会返回一个与原future相关的新 future 对象。 +### 投影 + +为了确保for解构(for-comprehensions)能够返回异常, future s也提供了投影(projections)。如果原 future 对象失败了,`failed` 的投影会返回一个带有 `Throwable` 类型返回值的 future 对象。如果原 future 成功了,`failed` 的投影失败并有一个 `NoSuchElementException`。下面就是一个在屏幕上打印出异常的例子: + +{% tabs futures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-11 %} + +```scala +val f = Future { + 2 / 0 +} +for (exc <- f.failed) println(exc) +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-11 %} + +```scala +val f = Future { + 2 / 0 +} +for exc <- f.failed do println(exc) +``` + +{% endtab %} +{% endtabs %} + +本例中的 for-comprehension 翻译成: + +{% tabs for-comp-tran %} +{% tab 'Scala 2 and 3' for=for-comp-tran %} + +```scala +f.failed.foreach(exc => println(exc)) +``` + +{% endtab %} +{% endtabs %} + +因为 `f` 在这没有成功,该闭包被注册到新成功的 `Future[Throwable]` 上的 `foreach`。 下面的例子不会在屏幕上打印出任何东西: - val f = Future { +{% tabs futures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-12 %} + +```scala +val f = Future { + 4 / 2 +} +for (exc <- f.failed) println(exc) +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-12 %} + +```scala +val g = Future { + 4 / 2 +} +for exc <- g.failed do println(exc) +``` + +{% endtab %} +{% endtabs %} + + + + + + ### Future的扩展 -用更多的实用方法来对Futures API进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。 +用更多的实用方法来对 Futures API 进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。 -## Blocking +## 阻塞(Blocking) -正如前面所说的,在future的blocking非常有效地缓解性能和预防死锁。虽然在futures中使用这些功能方面的首选方式是Callbacks和combinators,但在某些处理中也会需要用到blocking,并且它也是被Futures and Promises API所支持的。 +Future 通常是异步的,不会阻塞底层执行线程。 +但是,在某些情况下,有必要阻塞。 +我们区分了两种形式的阻塞执行线程: +调用任意代码,从 future 来内部阻塞线程, +以及从另一个 future 外部阻塞,等待该 future 完成。 -在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到block来确定是否所有的futures已经完成。这有个如何使用block来处理一个future结果的例子: +### Future 内的阻塞 - import scala.concurrent._ - import scala.concurrent.duration._ +正如在全局 `ExecutionContext` 中看到的那样,可以使用 `blocking` 结构通知某个阻塞调用的 `ExecutionContext`。 +但是,实现完全由 `ExecutionContext`。一些 `ExecutionContext`,如 `ExecutionContext.global`,用 `MangedBlocker` 的办法实现 `blocking`,而另外一样通过执行上下文,如固定线程池来实现 `blocking`: +通过“ManagedBlocker”实现“阻塞”,一些执行上下文,如固定线程池: - def main(args: Array[String]) { - val rateQuote = Future { - connection.getCurrentValue(USD) - } +{% tabs fixed-thread-pool %} +{% tab 'Scala 2 and 3' for=fixed-thread-pool %} - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } +```scala +ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x)) +``` - Await.result(purchase, 0 nanos) - } +{% endtab %} +{% endtabs %} -在这种情况下这个future是不成功的,这个调用者转发出了该future对象不成功的异常。它包含了失败的投影(projection)-- 阻塞(blocking)该结果将会造成一个NoSuchElementException异常在原future对象被成功计算的情况下被抛出。 +将不作任何事,就像下面演示的那样: -相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。 +{% tabs futures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-13 %} -The Future trait实现了Awaitable trait还有其`ready()`和`result()`方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。 +```scala +implicit val ec = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) -为了允许程序调用可能是阻塞式的第三方代码,而又不必实现Awaitable特质,原函数可以用如下的方式来调用: +Future { + blocking { blockingStuff() } +} +``` - blocking { - potentiallyBlockingCall() - } +{% endtab %} +{% tab 'Scala 3' for=futures-13 %} -这段blocking代码也可以抛出一个异常。在这种情况下,这个异常会转发给调用者。 +```scala +given ExecutionContext = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) -## 异常(Exceptions) +Future { + blocking { blockingStuff() } +} +``` -当异步计算抛出未处理的异常时,与那些计算相关的futures就失败了。失败的futures存储了一个Throwable的实例,而不是返回值。Futures提供onFailure回调方法,它用一个PartialFunction去表示一个Throwable。下列特殊异常的处理方式不同: +{% endtab %} +{% endtabs %} -`scala.runtime.NonLocalReturnControl[_]` --此异常保存了一个与返回相关联的值。通常情况下,在方法体中的返回结构被调用去抛出这个异常。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。 +和以下代码有一样的副作用: -ExecutionException-当因为一个未处理的中断异常、错误或者`scala.util.control.ControlThrowable`导致计算失败时会被存储起来。这种情况下,ExecutionException会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的future对象是计算失败的。 +{% tabs alternative %} +{% tab 'Scala 2 and 3' for=alternative %} -更精确的语义描述请参见 [NonFatal]。 +```scala +Future { blockingStuff() } +``` -## Promises +{% endtab %} +{% endtabs %} -到目前为止,我们仅考虑了通过异步计算的方式创建future对象来使用future的方法。尽管如此,futures也可以使用promises来创建。 +阻塞代码也可能抛出异常。这种情况下,异常被转发给调用者。 -如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种success方法可以成功去实现一个带有值的future。相反的,因为一个失败的promise通过failure方法就会实现一个带有异常的future。 +### Future 外部的阻塞 -一个promise p通过p.future方式返回future。 这个futrue对象被指定到promise p。根据这种实现方式,可能就会出现p.future与p相同的情况。 +正如前面所说的,为了性能和防止死锁,强烈建议不要在future上用阻塞。 +虽然在futures中使用这些功能方面的首选方式是回调和组合器,但在某些处理中也会需要用到阻塞,并且 Futures API 和 Promises API也支持它。 -考虑下面的生产者 - 消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个promise来完成。 +在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到阻塞来确定是否所有的 futures已经完成。这有个如何使用阻塞来处理一个 future 结果的例子: - import scala.concurrent.{ Future, Promise } - import scala.concurrent.ExecutionContext.Implicits.global +{% tabs futures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-14 %} - val p = Promise[T]() - val f = p.future +```scala +import scala.concurrent._ +import scala.concurrent.duration._ - val producer = Future { - val r = produceSomething() - p success r - continueDoingSomethingUnrelated() +object awaitPurchase { + def main(args: Array[String]): Unit = { + val rateQuote = Future { + connection.getCurrentValue(USD) } - val consumer = Future { - startDoingSomething() - f onSuccess { - case r => doSomethingWithResult() - } + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") } -在这里,我们创建了一个promise并利用它的future方法获得由它实现的Future。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise p,这个值被用来完成future对象f。第二种做了某些计算,然后读取实现了future f的计算结果值r。需要注意的是,在生产者完成执行`continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。 + Await.result(purchase, 0.nanos) + } +} +``` -正如前面提到的,promises具有单赋值语义。因此,它们仅能被实现一次。在一个已经计算完成的promise或者failed的promise上调用success方法将会抛出一个IllegalStateException异常。 +{% endtab %} +{% tab 'Scala 3' for=futures-14 %} -下面的这个例子显示了如何fail a promise。 +```scala +import scala.concurrent.* +import scala.concurrent.duration.* - val p = promise[T] - val f = p.future +@main def awaitPurchase = + val rateQuote = Future { + connection.getCurrentValue(USD) + } - val producer = Future { - val r = someComputation - if (isInvalid(r)) - p failure (new IllegalStateException) - else { - val q = doSomeMoreComputation(r) - p success q - } - } + val purchase = rateQuote.map { quote => + if isProfitable(quote) then connection.buy(amount, quote) + else throw Exception("not profitable") + } -如上,生产者计算出一个中间结果值r,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise p的方式fails the promise,关联的future f是failed。否则,生产者会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。 + Await.result(purchase, 0.nanos) +``` -Promises也能通过一个complete方法来实现,这个方法采用了一个`potential value Try[T]`,这个值要么是一个类型为`Failure[Throwable]`的失败的结果值,要么是一个类型为`Success[T]`的成功的结果值。 +{% endtab %} +{% endtabs %} -类似success方法,在一个已经完成(completed)的promise对象上调用failure方法和complete方法同样会抛出一个IllegalStateException异常。 +在这种情况下这个 future 是不成功的,这个调用者转发出了该 future 对象不成功的异常。它包含了 `failed` 的投影(projection)-- 阻塞(blocking)该结果将会造成一个 `NoSuchElementException` 异常在原 future 对象被成功计算的情况下被抛出。 -应用前面所述的promises和futures方法的一个优点是,这些方法是单一操作的并且是没有副作用(side-effects)的,因此程序是具有确定性的(deterministic)。确定性意味着,如果该程序没有抛出异常(future的计算值被获得),无论并行的程序如何调度,那么程序的结果将会永远是一样的。 +相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。 -在一些情况下,客户端也许希望能够只在promise没有完成的情况下完成该promise的计算(例如,如果有多个HTTP请求被多个不同的futures对象来执行,并且客户端只关心第一个HTTP应答(response),该应答对应于第一个完成该promise的future)。因为这个原因,future提供了tryComplete,trySuccess和tryFailure方法。客户端需要意识到调用这些的结果是不确定的,调用的结果将以来从程序执行的调度。 +The `Future` trait实现了 `Awaitable` trait 还有其 `ready()` 和 `result()` 方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。 -completeWith方法将用另外一个future完成promise计算。当该future结束的时候,该promise对象得到那个future对象同样的值,如下的程序将打印1: +## 异常(Exceptions) - val f = Future { 1 } - val p = promise[Int] +当异步计算抛出未处理的异常时,与那些计算相关的 futures 就失败了。失败的 futures 存储了一个 `Throwable` 的实例,而不是返回值。`Futures` 提供 `failed` 投影方法,它允许这个 `Throwable` 被当作另一个 `Future` 的成功值来处理。下列特殊异常的处理方式不同: +1. `scala.runtime.NonLocalReturnControl[_]` -- 此异常保存了一个与返回相关联的值。通常情况下,方法体中的 `return` 结构被翻译成带有异常的 `throw` 。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。 - p completeWith f +2. `ExecutionException` -- 当因为一个未处理的 `InterruptedException`, `Error` 或者 `scala.util.control.ControlThrowable` 导致计算失败时会被存储起来。这种情况下, `ExecutionException` 会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的 future 对象是计算失败的。 - p.future onSuccess { - case x => println(x) - } +致命异常(由 `NonFatal` 确定)在执行失败的异步计算的线程中重新引发。这会通知管理问题执行线程的代码,并允许它在必要时快速失败。更精确的语义描述请参见[`NonFatal`](https://www.scala-lang.org/api/current/scala/util/control/NonFatal$.html)。 + +## Promise + +到目前为止,我们仅考虑了通过异步计算的方式创建 `Future` 对象来使用 `Future` 的方法。尽管如此,futures也可以使用 *promises* 来创建。 + +如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种 `success` 方法可以成功去实现一个带有值(通过 “实现” 该 promise)的future。相反的,用 `failure` 方法。 +一个 promise 也能用来实现带有异常的 future,通过这个失败的promise。 + +一个promise `p` 通过 `p.future` 方式返回future。 这个futrue对象被指定到promise `p`。根据这种实现方式,可能就会出现 `p.future eq p` 的情况。 + +考虑下面的生产者-消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个 promise 来完成。 + +{% tabs promises %} +{% tab 'Scala 2 and 3' for=promises %} + +```scala +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.foreach { r => + doSomethingWithResult() + } +} +``` + +{% endtab %} +{% endtabs %} + +在这里,我们创建了一个promise并利用它的 `future` 方法获得由它实现的 `Future`。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise `p`,这个值被用来完成future对象 `f`。第二种做了某些计算,然后读取实现了 future `f` 的计算结果值 `r`。需要注意的是,在 `producer` 完成执行 `continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。 + +正如前面提到的,promises 具有单赋值语义。因此,它们仅能被完成一次。在一个已经计算完成的 promise 或者 failed 的promise上调用 `success` 方法将会抛出一个 `IllegalStateException` 异常。 + +下面的这个例子显示了如何使 promise 失败。 + +{% tabs futures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-15 %} + +```scala +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) + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-15 %} + +```scala +val p = Promise[T]() +val f = p.future + +val producer = Future { + val r = someComputation + if isInvalid(r) then + p.failure(new IllegalStateException) + else + val q = doSomeMoreComputation(r) + p.success(q) +} +``` + +{% endtab %} +{% endtabs %} + +如上, `producer` 计算出一个中间结果值 `r`,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise `p` 的方式fails the promise,关联的future `f` 是失败的。否则, `producer` 会继续它的计算,最终使用一个有效的结果值实现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`: -当让一个promise以异常失败的时候,三总子类型的Throwable异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException`或者`scala.util.control.ControlThrowable`,那么该可抛出(Throwable)异常将会封装一个ExecutionException异常,该ExectionException将会让该promise以失败结束。 +{% tabs promises-2 %} +{% tab 'Scala 2 and 3' for=promises-2 %} -通过使用promises,futures的onComplete方法和future的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。让我们来假设一下你想实现一个新的组合起,该组合器首先使用两个future对象f和,产生第三个future,该future能够用f或者g来完成,但是只在它能够成功完成的情况下。 +```scala +val f = Future { 1 } +val p = promise[Int]() + +p.completeWith(f) + +p.future.foreach { x => + println(x) +} +``` + +{% endtab %} +{% endtabs %} + +当让一个promise以异常失败的时候,三个子类型的 `Throwable` 异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException` 或者 `scala.util.control.ControlThrowable`,那么该 `Throwable` 异常将会封装一个 `ExecutionException` 异常,该 `ExectionException` 将会让该 promise 以失败结束。 + +通过使用 promises,futures 的 `onComplete` 方法和 `future` 的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。 +让我们来假设一下你想实现一个新的组合器 `first`,该组合器使用两个future `f` 和 `g`,然后生产出第三个 future,该future能够用 `f` 或者 `g` 来完成(看哪一个先到),但前提是它能够成功。 这里有个关于如何去做的实例: - def first[T](f: Future[T], g: Future[T]): Future[T] = { - val p = promise[T] +{% tabs futures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-16 %} - f onSuccess { - case x => p.trySuccess(x) - } +```scala +def first[T](f: Future[T], g: Future[T]): Future[T] = { + val p = Promise[T] - g onSuccess { - case x => p.trySuccess(x) - } + f.foreach { x => + p.trySuccess(x) + } - p.future - } + g.foreach { x => + p.trySuccess(x) + } + + p.future +} +``` + +{% endtab %} +{% tab 'Scala 3' for=futures-16 %} + +```scala +def first[T](f: Future[T], g: Future[T]): Future[T] = + val p = Promise[T] + + f.foreach { x => + p.trySuccess(x) + } + + g.foreach { x => + p.trySuccess(x) + } + + p.future +``` -注意,在这种实现方式中,如果f与g都不是成功的,那么`first(f, g)`将不会实现(即返回一个值或者返回一个异常)。 +{% endtab %} +{% endtabs %} + +注意,在这种实现方式中,如果 `f` 和 `g` 都不成功,那么 `first(f, g)` 将不会完成(即返回一个值或者返回一个异常)。 + + ## 工具(Utilities) -为了简化在并发应用中处理时序(time)的问题,`scala.concurrent`引入了Duration抽象。Duration不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,Duration位于`scala.concurrent`包中。 +为了简化在并发应用中处理时序(time)的问题, `scala.concurrent` 引入了 `Duration` 抽象。`Duration` 不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,`Duration` 位于 `scala.concurrent` 包中。 + +`Duration` 是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用 `FiniteDuration` 类来表示,并通过 `Long` 长度和 `java.util.concurrent.TimeUnit` 来构造。无限的 durations,同样扩展自 `Duration`,只在两种情况下存在,`Duration.Inf` 和 `Duration.MinusInf`。库中同样提供了一些 `Duration` 的子类用来做隐式的转换,这些子类不应被直接使用。 + +抽象的 `Duration` 类包含了如下方法: + +1. 到不同时间单位的转换(`toNanos`,`toMicros`,`toMillis`,`toSeconds`,`toMinutes`,`toHours`,`toDays` 和 `toUnit(unit: TimeUnit)`)。 +2. durations的比较(`<`,`<=`,`>`和`>=`)。 +3. 算术运算符(`+`,`-`,`*`,`/`和 `unary_-`) +4. `this` duration 与提供的参数之间的最大和最小的方法(`min`,`max`)。 +5. 检查 duration是否有限(`isFinite`)。 + +`Duration` 能够用如下方法实例化(`instantiated`): + +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` 方法,因此可以被用于模式匹配中,例如: + +{% tabs futures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=futures-17 %} + +```scala +import scala.concurrent.duration._ +import java.util.concurrent.TimeUnit._ -Duration是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用FiniteDuration类来表示,并通过时间长度`(length)`和`java.util.concurrent.TimeUnit`来构造。无限的durations,同样扩展了Duration,只在两种情况下存在,`Duration.Inf`和`Duration.MinusInf`。库中同样提供了一些Durations的子类用来做隐式的转换,这些子类不应被直接使用。 +// 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 -抽象的Duration类包含了如下方法: +// pattern matching +val Duration(length, unit) = 5 millis +``` -到不同时间单位的转换`(toNanos, toMicros, toMillis, toSeconds, toMinutes, toHours, toDays and toUnit(unit: TimeUnit))`。 -durations的比较`(<,<=,>和>=)`。 -算术运算符`(+, -, *, / 和单值运算_-)` -duration的最大最小方法`(min,max)`。 -测试duration是否是无限的方法`(isFinite)`。 -Duration能够用如下方法实例化`(instantiated)`: +{% endtab %} +{% tab 'Scala 3' for=futures-17 %} -隐式的通过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被用于模式匹配中,例如: +```scala +import scala.concurrent.duration.* +import java.util.concurrent.TimeUnit.* - 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 - // 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 +``` - // pattern matching - val Duration(length, unit) = 5 millis +{% endtab %} +{% endtabs %} diff --git a/_zh-cn/overviews/core/implicit-classes.md b/_zh-cn/overviews/core/implicit-classes.md index 6999f419aa..e7ec62cad1 100644 --- a/_zh-cn/overviews/core/implicit-classes.md +++ b/_zh-cn/overviews/core/implicit-classes.md @@ -61,7 +61,7 @@ Scala 2.10引入了一种叫做隐式类的新特性。隐式类指的是用impl 2. 构造函数只能携带一个非隐式参数。 ```` - implicit class RichDate(date: java.util.Date) // 正确! + implicit class RichDate(date: java.time.LocalDate) // 正确! implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确! ```` diff --git a/_zh-cn/overviews/core/string-interpolation.md b/_zh-cn/overviews/core/string-interpolation.md deleted file mode 100644 index eab9e4a742..0000000000 --- a/_zh-cn/overviews/core/string-interpolation.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -layout: singlepage-overview -title: 字符串插值 - -partof: string-interpolation - -language: zh-cn ---- - -**Josh Suereth 著** - -## 简介 - -自2.10.0版本开始,Scala提供了一种新的机制来根据数据生成字符串:字符串插值。字符串插值允许使用者将变量引用直接插入处理过的字面字符中。如下例: - - val name="James" - println(s"Hello,$name")//Hello,James - -在上例中, s"Hello,$name" 是待处理字符串字面,编译器会对它做额外的工作。待处理字符串字面通过“号前的字符来标示(例如:上例中是s)。字符串插值的实现细节在 [SIP-11](https://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 StringBuilder(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 index a04b3a958b..9b9446a94b 100644 --- a/_zh-cn/overviews/core/value-classes.md +++ b/_zh-cn/overviews/core/value-classes.md @@ -43,7 +43,7 @@ Value class只能继承universal traits,但其自身不能再被继承。所 def toHexString: String = java.lang.Integer.toHexString(self) } -在运行时,表达式3.toHexString 被优化并等价于静态对象的方法调用 (RichInt$.MODULE$.extension$toHexString(3)),而不是创建一个新实例对象,再调用其方法。 +在运行时,表达式3.toHexString 被优化并等价于静态对象的方法调用 (RichInt$.MODULE$.toHexString$extension(3)),而不是创建一个新实例对象,再调用其方法。 ## 正确性 diff --git a/_zh-cn/overviews/index.md b/_zh-cn/overviews/index.md index 38b8dc4ba2..a051fef550 100644 --- a/_zh-cn/overviews/index.md +++ b/_zh-cn/overviews/index.md @@ -3,6 +3,8 @@ layout: overviews language: zh-cn partof: overviews title: 目录 +redirect_from: + - /zh-cn/scala3/guides.html --- diff --git a/_zh-cn/overviews/reflection/overview.md b/_zh-cn/overviews/reflection/overview.md index 02adf8ecc2..25f54810e9 100644 --- a/_zh-cn/overviews/reflection/overview.md +++ b/_zh-cn/overviews/reflection/overview.md @@ -85,7 +85,7 @@ decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isE #### 1.1.2 运行时实例化一个类型 -通过反射获得的类型,可以通过使用适当的“调用器”镜像调用它们的构造函数来实例化(镜像`mirros`的概念在[后续文档中说明](https://docs.scala-lang.org/overviews/reflection/overview.html#mirrors))。 +通过反射获得的类型,可以通过使用适当的“调用器”镜像调用它们的构造函数来实例化(镜像`mirrors`的概念在[后续文档中说明](https://docs.scala-lang.org/overviews/reflection/overview.html#mirrors))。 让我们通过一个REPL的示例说明: @@ -269,7 +269,7 @@ Scala反射实现了允许在编译阶段就对程序进行修改的一种元编 这个反射环境根据是在运行时环境完成反射任务还是在编译时环境完成反射任务而有所区别。 这个区别被封装于所谓的`universe`中。 反射环境的另一个重要方面就是我们可以访问想反射的那组实体, -这组实体由所谓的镜像`mirros`去确定。 +这组实体由所谓的镜像`mirrors`去确定。 镜像不仅决定反射化操作有哪些实体要被访问到,而且它还提供了反射操作去执行那些实体。 比如在运行时反射过程中可以调用镜像去操作类中一个方法或构造器。 @@ -279,7 +279,7 @@ Scala反射实现了允许在编译阶段就对程序进行修改的一种元编 `Universe`是Scala反射的切入点。 `universe`提供了使用反射所关联的很多核心概念,比如`Types`,`Trees`,以及`Annotations`。 -更多细节请参阅指南中[Universes](https://docs.scala-lang.org/overviews/reflection/environment-universes-mirrors.html)部分,或者看`scala.reflect.api`包的[Universes API文档](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Universe.html)。 +更多细节请参阅指南中[Universes](https://docs.scala-lang.org/overviews/reflection/environment-universes-mirrors.html)部分,或者看`scala.reflect.api`包的[Universes API文档](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Universe.html)。 本指南中提供了大多数情况下Scala反射要用到的部分,一般在使用运行时反射的场景下,直接导入所有`universe`成员去用即可: @@ -293,4 +293,4 @@ import scala.reflect.runtime.universe._ 反射所能提供的信息都是通过镜像去访问的。 根据不同的类型信息或不同的反射操作,必须要使用不同类型的镜像。 -更多细节请参阅指南中[Mirros](https://docs.scala-lang.org/overviews/reflection/environment-universes-mirrors.html)部分,或者看`scala.reflect.api`包的[Mirrors API文档](https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/api/Mirrors.html)。 +更多细节请参阅指南中[Mirrors](https://docs.scala-lang.org/overviews/reflection/environment-universes-mirrors.html)部分,或者看`scala.reflect.api`包的[Mirrors API文档](https://www.scala-lang.org/api/2.x/scala-reflect/scala/reflect/api/Mirrors.html)。 diff --git a/_zh-cn/overviews/scala3-book/ca-context-bounds.md b/_zh-cn/overviews/scala3-book/ca-context-bounds.md new file mode 100644 index 0000000000..9e9c84238c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-context-bounds.md @@ -0,0 +1,114 @@ +--- +title: 上下文绑定 +type: section +description: This page demonstrates Context Bounds in Scala 3. +language: zh-cn +num: 62 +previous-page: ca-context-parameters +next-page: ca-given-imports + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在许多情况下,[上下文参数]({% link _overviews/scala3-book/ca-context-parameters.md %}#context-parameters) 的名称不必显式提及,因为它仅在编译器为其他上下文参数合成实参的时候用到。 +在这种情况下,您不必定义参数名称,只需提供参数类型即可。 + +## 背景 + +例如,假设一个 `maxElement` 方法返回一个集合里的最大值: + +{% tabs context-bounds-max-named-param class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)(ord)) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def maxElement[A](as: List[A])(using ord: Ord[A]): A = + as.reduceLeft(max(_, _)(using ord)) +``` +{% endtab %} +{% endtabs %} + +上面这个 `maxElement` 方法只接受一个类型为 `Ord[A]` 的 _上下文参数_ 并将其作为实参传给 `max` 方法。 + +完整起见,以下是 `max` 和 `Ord` 的定义(注意,在实践中我们会使用 `List` 中已有的 `max` 方法 , +但我们为了说明目的而编造了这个例子): + +{% tabs context-bounds-max-ord class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +/** Defines how to compare values of type `A` */ +trait Ord[A] { + def greaterThan(a1: A, a2: A): Boolean +} + +/** Returns the maximum of two values */ +def max[A](a1: A, a2: A)(implicit ord: Ord[A]): A = + if (ord.greaterThan(a1, a2)) a1 else a2 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +/** Defines how to compare values of type `A` */ +trait Ord[A]: + def greaterThan(a1: A, a2: A): Boolean + +/** Returns the maximum of two values */ +def max[A](a1: A, a2: A)(using ord: Ord[A]): A = + if ord.greaterThan(a1, a2) then a1 else a2 +``` +{% endtab %} +{% endtabs %} + +`max` 方法用了类型为 `Ord[A]` 的上下文参数, 就像 `maxElement` 方法一样。 + +## 省略上下文参数 + +因为 `ord` 是 `max` 方法的上下文参数,当我们调用方法 `max` 时, 编译器可以在 `maxElement` 的实现中为我们提供它: + +{% tabs context-bounds-context class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def maxElement[A](as: List[A])(implicit ord: Ord[A]): A = + as.reduceLeft(max(_, _)) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def maxElement[A](as: List[A])(using Ord[A]): A = + as.reduceLeft(max(_, _)) +``` + +注意,因为我们不用显示传递给 `max` 方法,我们可以在 `maxElement` 定义里不命名。 +这是 _匿名上下文参数_ 。 +{% endtab %} +{% endtabs %} + +## 上下文绑定 + +鉴于此背景,_上下文绑定_ 是一种简写语法,用于表达“依赖于类型参数的上下文参数”模式。 + +使用上下文绑定,`maxElement` 方法可以这样写: + +{% tabs context-bounds-max-rewritten %} +{% tab 'Scala 2 and 3' %} +```scala +def maxElement[A: Ord](as: List[A]): A = + as.reduceLeft(max(_, _)) +``` +{% endtab %} +{% endtabs %} + +方法或类的类型参数 `A`,有类似 `:Ord` 的绑定,它表示有 `Ord[A]` 的上下文参数。 +在后台,编译器将此语法转换为“背景”部分中显示的语法。 + +有关上下文绑定的更多信息,请参阅 Scala 常见问题解答的 [“什么是上下文绑定?”](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html) 部分。 diff --git a/_zh-cn/overviews/scala3-book/ca-context-parameters.md b/_zh-cn/overviews/scala3-book/ca-context-parameters.md new file mode 100644 index 0000000000..3742d36de1 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-context-parameters.md @@ -0,0 +1,152 @@ +--- +title: Given 实例和 Using 语句 +type: section +description: This page demonstrates how to use 'given' instances and 'using' clauses in Scala 3. +language: zh-cn +num: 61 +previous-page: ca-extension-methods +next-page: ca-context-bounds + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +
使用上下文抽象仅限Scala 3
+ +Scala 3 提供了两个重要的上下文抽象特性: + +- **Using 语句** 允许你指定参数,这些参数程序员可以在调用时省略,这些参数由上下文自动提供。 +- **Given 实例** 让您定义 Scala 编译器可以用来填充缺失参数的术语。 + +## Using 语句 + +在设计系统时,通常需要向系统的不同组件提供上下文信息,如_配置_或设置。 +实现此目的的一种常见方法是将配置作为附加参数传递给您的方法。 + +在下面的示例中,我们定义了一个样例类 `Config` 来模拟一些网站配置并在不同的方法中传递它。 + +{% tabs nonusing %} +{% tab 'Scala 2 and 3' %} + +```scala +case class Config(port: Int, baseUrl: String) + +def renderWebsite(path: String, c: Config): String = + "" + renderWidget(List("cart"), c) + "" + +def renderWidget(items: List[String], c: Config): String = ??? + +val config = Config(8080, "docs.scala-lang.org") +renderWebsite("/home", config) +``` + +{% endtab %} +{% endtabs %} + +让我们假设配置在我们的大部分代码库中都没有改变。 +将 `c` 传递给每个方法调用(如 `renderWidget`)变得非常乏味并且使我们的程序更难阅读,因为我们需要忽略 `c` 参数。 + +#### 使用 `using` 将参数标记为上下文 + +在 Scala 3 中,我们可以将方法的一些参数标记为_上下文_。 + +{% tabs using1 %} +{% tab 'Scala 3 Only' %} + +```scala +def renderWebsite(path: String)(using c: Config): String = + "" + renderWidget(List("cart")) + "" + // ^^^ + // no argument c required anymore + +def renderWidget(items: List[String])(using c: Config): String = ??? +``` + +{% endtab %} +{% endtabs %} + +通过使用关键字 `using` 开始参数部分,我们告诉 Scala 编译器它应该在调用处自动找到具有正确类型的参数。 +因此,Scala 编译器执行**术语推断**。 + +在我们对 `renderWidget(List("cart"))` 的调用中,Scala 编译器将看到作用域(`c`)中有一个类型为 `Config` 的术语,并自动将其提供给 `renderWidget`。 +所以程序等同于上面的程序。 + +事实上,由于我们不再需要在 `renderWebsite` 的实现中引用 `c`,我们甚至可以在签名中省略它的名字: + +{% tabs using2 %} +{% tab 'Scala 3 Only' %} + +```scala +// no need to come up with a parameter name +// vvvvvvvvvvvvv +def renderWebsite(path: String)(using Config): String = + "" + renderWidget(List("cart")) + "" +``` + +{% endtab %} +{% endtabs %} + +#### 明确提供上下文参数 + +我们已经了解了如何_抽象_上下文参数,并且 Scala 编译器可以自动为我们提供参数。 +但是我们如何指定调用 `renderWebsite` 时使用的配置呢? + +就像我们使用 `using` 指定参数部分一样,我们也可以使用 `using` 显式提供上下文参数: + +{% tabs using3 %} +{% tab 'Scala 3 Only' %} + +```scala +renderWebsite("/home")(using config) +``` + +{% endtab %} +{% endtabs %} + +如果我们在范围内有多个有意义的不同值,并且我们希望确保将正确的值传递给函数,则显式提供上下文参数可能很有用。 + +对于所有其他情况,正如我们将在下一节中看到的,还有另一种方法可以将上下文值引入范围。 + +## Give 实例 + +我们已经看到,我们可以通过使用 `using` 标记 _调用_的参数部分来显式地将参数作为上下文参数传递。 +但是,如果某个特定类型有一个_单一的规范值_,则还有另一种首选方法可以使其对 Scala 编译器可用:将其标记为 `given`。 + +{% tabs given1 %} +{% tab 'Scala 3 Only' %} + +```scala +val config = Config(8080, "docs.scala-lang.org") +// this is the type that we want to provide the +// canonical value for +// vvvvvv +given Config = config +// ^^^^^^ +// this is the value the Scala compiler will infer +// as argument to contextual parameters of type Config +``` + +{% endtab %} +{% endtabs %} + +在上面的示例中,我们指定每当在当前范围内省略 `Config` 类型的上下文参数时,编译器应该将 `config` 推断为参数。 + +为 `Config` 定义了 given,我们可以简单地调用 `renderWebsite`: + +{% tabs given2 %} +{% tab 'Scala 3 Only' %} + +```scala +renderWebsite("/home") +// ^^^^^ +// again no argument +``` + +{% endtab %} +{% endtabs %} + +[reference]: {{ site.scala3ref }}/overview.html +[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md new file mode 100644 index 0000000000..4b6a82db3b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -0,0 +1,88 @@ +--- +title: 上下文抽象 +type: chapter +description: This chapter provides an introduction to the Scala 3 concept of Contextual Abstractions. +language: zh-cn +num: 59 +previous-page: types-others +next-page: ca-extension-methods + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +## 背景 + +隐式是Scala 2中的一个主要的设计特色。隐式是进行上下文抽象的基本方式。它们代表了具有多种用例的统一范式,其中包括: + +- 实现类型类 +- 建立上下文 +- 依赖注入 +- 表达能力 +- 计算新类型,并证明它们之间的关系 + +此后,其他语言也纷纷效仿,例如 Rust 的 traits 或 Swift 的协议扩展。对于编译期的依赖解析方案,Kotlin 的设计方案也被提上日程,而对 C# 而言是 Shapes 和 Extensions 或对 F# 来说是 Traits。Implicits 也是 Coq 或 Agda 等定理证明器的共同特色。 + +尽管这些设计使用不同的术语,但它们都是*术语推理*核心思想的变体: +给定一个类型,编译器会合成一个具有该类型的“canonical”术语。 + +## 重新设计 + +Scala 3 重新设计了 Scala 中的上下文抽象。 +虽然这些概念是在 Scala 2 中逐渐“发现”的,但它们现在已广为人知和理解,并且重新设计利用了这些知识。 + +Scala 3 的设计侧重于 **意图** 而不是 **机制**。 +Scala 3 没有提供一个非常强大的隐式特性,而是提供了几个面向用例的特性: + +- **抽象上下文信息**。 + [使用子句][givens] 允许程序员对调用上下文中可用的信息进行抽象,并且应该隐式传递。 + 作为对 Scala 2 隐式的改进,可以按类型指定 using 子句,从而将函数签名从从未显式引用的术语变量名称中释放出来。 + +- **提供类型类实例**。 + [给定实例][type-classes]允许程序员定义某种类型的_规范值(canonical value)_。 + 这使得使用类型类的编程更加简单,而不会泄露实现细节。 + +- **追溯扩展类**。 + 在 Scala 2 中,扩展方法必须使用隐式转换或隐式类进行编码。 + 相比之下,在 Scala 3 [扩展方法][extension-methods] 现在直接内置到语言中,产生了更好的错误消息和改进的类型推断。 + +- **将一种类型视为另一种类型**。 + 隐式转换已从头开始[重新设计][implicit-conversions],作为类型类 `Conversion` 的实例。 + +- **高阶上下文抽象**。 + [上下文函数][contextual-functions] 的_全新_特性使上下文抽象成为一等公民。 + 它们是库作者的重要工具,可以表达简洁的领域特定语言。 + +- **来自编译器的可操作反馈**。 + 如果编译器无法解析隐式参数,它现在会为您提供 [导入建议](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) 来解决问题。 + +## 好处 + +Scala 3 中的这些更改实现了术语推理与语言其余部分的更好分离: + +- 仅有一种定义 given 的方法 +- 仅有一种方法可以引入隐式参数和自变量 +- [导入 givens][given-imports] 仅有一种单独的方法,不允许它们隐藏在正常导入的海洋中 +- 定义 [隐式转换][implicit-conversions] 的方法仅有一种,它被清楚地标记为这样,并且不需要特殊的语法 + +这些变化的好处包括: + +- 新设计避免了特性交织,从而使语言更加一致 +- 它使隐式更容易学习和更难滥用 +- 它极大地提高了 95% 使用隐式的 Scala 程序的清晰度 +- 它有可能以一种易于理解和友好的原则方式进行术语推理 + +本章将在以下各节中介绍其中的许多新功能。 + + +[givens]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %} +[given-imports]: {% link _zh-cn/overviews/scala3-book/ca-given-imports.md %} +[implicit-conversions]: {% link _zh-cn/overviews/scala3-book/ca-implicit-conversions.md %} +[extension-methods]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[context-bounds]: {% link _zh-cn/overviews/scala3-book/ca-context-bounds.md %} +[type-classes]: {% link _zh-cn/overviews/scala3-book/ca-type-classes.md %} +[equality]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[contextual-functions]: {% link _zh-cn/overviews/scala3-book/types-dependent-function.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-extension-methods.md b/_zh-cn/overviews/scala3-book/ca-extension-methods.md new file mode 100644 index 0000000000..be1237733b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-extension-methods.md @@ -0,0 +1,127 @@ +--- +title: 扩展方法 +type: section +description: This page demonstrates how Extension Methods work in Scala 3. +language: zh-cn +num: 60 +previous-page: ca-contextual-abstractions-intro +next-page: ca-context-parameters + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +在 Scala 2 中,用 [implicit classes]({% link _overviews/core/implicit-classes.md %}) 可以得到类似的结果。 + +--- + +扩展方法允许您在定义类型后向类型添加方法,即它们允许您向封闭类添加新方法。 +例如,假设其他人创建了一个 `Circle` 类: + +{% tabs ext1 %} +{% tab 'Scala 2 and 3' %} +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` +{% endtab %} +{% endtabs %} + +现在想象一下,你需要一个 `circumference` 方法,但是你不能修改它们的源代码。 +在将术语推理的概念引入编程语言之前,您唯一能做的就是在单独的类或对象中编写一个方法,如下所示: + +{% tabs ext2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +object CircleHelpers { + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object CircleHelpers: + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +``` +{% endtab %} +{% endtabs %} + +你可以像这样用该方法: + +{% tabs ext3 %} +{% tab 'Scala 2 and 3' %} +```scala +val aCircle = Circle(2, 3, 5) + +// without extension methods +CircleHelpers.circumference(aCircle) +``` +{% endtab %} +{% endtabs %} + +但是使用扩展方法,您可以创建一个 `circumference` 方法来处理 `Circle` 实例: + +{% tabs ext4 %} +{% tab 'Scala 3 Only' %} +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 +``` +{% endtab %} +{% endtabs %} + +在这段代码中: + +- 扩展方法 `circumference` 将添加到 `Circle` 类型里 +- `c: Circle` 语法允许您在扩展方法中引用变量 `c` + +然后在您的代码中使用 `circumference`,就像它最初是在 `Circle` 类中定义的一样: + +{% tabs ext5 %} +{% tab 'Scala 3 Only' %} +```scala +aCircle.circumference +``` +{% endtab %} +{% endtabs %} + +### 导入扩展方法 + +想象一下,`circumference` 定义在`lib` 包中,你可以通过以下方式导入它 + +{% tabs ext6 %} +{% tab 'Scala 3 Only' %} +```scala +import lib.circumference + +aCircle.circumference +``` +{% endtab %} +{% endtabs %} + +如果缺少导入,编译器还会通过显示详细的编译错误消息来支持您,如下所示: + +```text +value circumference is not a member of Circle, but could be made available as an extension method. + +The following import might fix the problem: + + import lib.circumference +``` + +## 讨论 + +`extension` 关键字声明您将要在括号中的类型上定义一个或多个扩展方法。 +要在一个类型上定义多个扩展方法,请使用以下语法: + +{% tabs ext7 %} +{% tab 'Scala 3 Only' %} +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` +{% endtab %} +{% endtabs %} diff --git a/_zh-cn/overviews/scala3-book/ca-given-imports.md b/_zh-cn/overviews/scala3-book/ca-given-imports.md new file mode 100644 index 0000000000..ba56af52f0 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-given-imports.md @@ -0,0 +1,54 @@ +--- +title: Given 导入 +type: section +description: This page demonstrates how 'given' import statements work in Scala 3. +language: zh-cn +num: 63 +previous-page: ca-context-bounds +next-page: ca-type-classes + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +为了更清楚地说明当前作用域中的 given 来自何处,我们使用一种特殊形式的 `import` 语句来导入 `given` 实例。 +此示例中显示了基本形式: + +```scala +object A: + class TC + given tc: TC = ??? + def f(using TC) = ??? + +object B: + import A.* // import all non-given members + import A.given // import the given instance +``` + +在此代码中,对象 `B` 的 `import A.*` 子句导入 `A` 的所有成员*除了* `given` 实例 `tc`。 +相反,第二个导入 `import A.given` *仅*导入 `given` 实例。 +两个 `import` 子句也可以合并为一个: + +```scala +object B: + import A.{given, *} +``` + +## 讨论 + +通配符选择器 `*` 将除 given 或扩展之外的所有定义都导入作用域,而 `given` 选择器将所有 *given* 定义---包括由扩展而来的定义---导入作用域。 + +这些规则有两个主要优点: + +- 更清楚当前作用域内 given 的来源。 + 特别是,在一长串其他通配符导入中无法隐藏导入的 given 。 +- 它可以导入所有 given ,而无需导入任何其他内容。 + 这很重要,因为 given 是可以匿名的,因此通常使用命名导入是不切实际的。 + +“导入 given”语法的更多示例见 [package 和 import 章节][imports]。 + + +[imports]: {% link _zh-cn/overviews/scala3-book/packaging-imports.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md new file mode 100644 index 0000000000..5ab40c8064 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md @@ -0,0 +1,53 @@ +--- +title: 隐式转换 +type: section +description: This page demonstrates how to implement Implicit Conversions in Scala 3. +language: zh-cn +num: 66 +previous-page: ca-multiversal-equality +next-page: ca-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +隐式转换由 `scala.Conversion` 类的 `given` 实例定义。 +例如,不考虑可能的转换错误,这段代码定义了从 `String` 到 `Int` 的隐式转换: + +```scala +given Conversion[String, Int] with + def apply(s: String): Int = Integer.parseInt(s) +``` + +使用别名可以更简洁地表示为: + +```scala +given Conversion[String, Int] = Integer.parseInt(_) +``` + +使用这些转换中的任何一种,您现在可以在需要 `Int` 的地方使用 `String`: + +```scala +import scala.language.implicitConversions + +// a method that expects an Int +def plus1(i: Int) = i + 1 + +// pass it a String that converts to an Int +plus1("1") +``` + +> 注意开头的子句 `import scala.language.implicitConversions`, +> 在文件中启用隐式转换。 + +## 讨论 + +Predef 包包含“自动装箱”转换,将基本数字类型映射到 `java.lang.Number` 的子类。 +例如,从 `Int` 到 `java.lang.Integer` 的转换可以定义如下: + +```scala +given int2Integer: Conversion[Int, java.lang.Integer] = + java.lang.Integer.valueOf(_) +``` diff --git a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md new file mode 100644 index 0000000000..9c6d2f422c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md @@ -0,0 +1,204 @@ +--- +title: 多元相等性 +type: section +description: This page demonstrates how to implement Multiversal Equality in Scala 3. +language: zh-cn +num: 65 +previous-page: ca-type-classes +next-page: ca-implicit-conversions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +以前,Scala 具有*通用相等性*:任何类型的两个值都可以使用 `==` 和 `!=` 相互比较。 +这是因为 `==` 和 `!=` 是根据 Java 的 `equals` 方法实现的,该方法还可以比较任何两种引用类型的值。 + +通用相等性很方便,但也很危险,因为它破坏了类型安全。 +例如,假设在一些重构之后,你会得到一个错误的程序,其中值 `y` 的类型为 `S` 而不是正确的类型 `T`: + +```scala +val x = ... // of type T +val y = ... // of type S, but should be T +x == y // typechecks, will always yield false + +``` + +如果 `y` 与其他类型为 `T` 的值进行比较,程序仍然会进行类型检查,因为所有类型的值都可以相互比较。 +但它可能会产生意想不到的结果并在运行时失败。 + +类型安全的编程语言可以做得更好,多元等价是使普遍平等更安全的一种选择方式。 +它使用二元类型类“CanEqual”来指示两个给定类型的值可以相互比较。 + +## 允许比较类实例 + +默认情况下,在 Scala 3 中,您仍然可以像这样创建相等比较: + +```scala +case class Cat(name: String) +case class Dog(name: String) +val d = Dog("Fido") +val c = Cat("Morris") + +d == c // false, but it compiles +``` + +但是使用 Scala 3,您可以禁用此类比较。 +通过 (a) 导入 `scala.language.strictEquality` 或 (b) 使用 `-language:strictEquality` 编译器标志,此比较不再编译: + +```scala +import scala.language.strictEquality + +val rover = Dog("Rover") +val fido = Dog("Fido") +println(rover == fido) // compiler error + +// compiler error message: +// Values of types Dog and Dog cannot be compared with == or != +``` + +## 启用比较 + +有两种方法可以使用 Scala 3 `CanEqual` 类型类来启用这种比较。 +对于像这样的简单情况,您的类可以*派生* `CanEqual` 类: + +```scala +// Option 1 +case class Dog(name: String) derives CanEqual +``` + +稍后您将看到,当您需要更多的灵活性时,您还可以使用以下语法: + +```scala +// Option 2 +case class Dog(name: String) +given CanEqual[Dog, Dog] = CanEqual.derived +``` + +现在,这两种方法中的任何一种都可以让 `Dog` 实例相互比较。 + +## 一个更真实的例子 + +在一个更真实的示例中,假设您有一家在线书店,并且想要允许或禁止比较实体书、打印的书和有声读物。 +在 Scala 3 中,您首先启用多元平等性,如前面的示例所示: + +```scala +// [1] add this import, or this command line flag: -language:strictEquality +import scala.language.strictEquality +``` + +然后像往常一样创建你的领域对象: + +```scala +// [2] create your class hierarchy +trait Book: + def author: String + def title: String + def year: Int + +case class PrintedBook( + author: String, + title: String, + year: Int, + pages: Int +) extends Book + +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book +``` + +最后,使用 `CanEqual` 定义您想要允许的比较: + +```scala +// [3] create type class instances to define the allowed comparisons. +// allow `PrintedBook == PrintedBook` +// allow `AudioBook == AudioBook` +given CanEqual[PrintedBook, PrintedBook] = CanEqual.derived +given CanEqual[AudioBook, AudioBook] = CanEqual.derived + +// [4a] comparing two printed books works as desired +val p1 = PrintedBook("1984", "George Orwell", 1961, 328) +val p2 = PrintedBook("1984", "George Orwell", 1961, 328) +println(p1 == p2) // true + +// [4b] you can’t compare a printed book and an audiobook +val pBook = PrintedBook("1984", "George Orwell", 1961, 328) +val aBook = AudioBook("1984", "George Orwell", 2006, 682) +println(pBook == aBook) // compiler error +``` + +最后一行代码导致此编译器错误消息: + +```` +Values of types PrintedBook and AudioBook cannot be compared with == or != +```` + +这就是多元相等性在编译时捕获非法类型比较的方式。 + +### 启用“PrintedBook == AudioBook” + +这可以按需要工作,但在某些情况下,您可能希望允许将实体书与有声读物进行比较。 +如果需要,请创建以下两个额外的相等比较: + +```scala +// allow `PrintedBook == AudioBook`, and `AudioBook == PrintedBook` +given CanEqual[PrintedBook, AudioBook] = CanEqual.derived +given CanEqual[AudioBook, PrintedBook] = CanEqual.derived +``` + +现在,您可以将实体书与有声书进行比较,而不会出现编译错误: + +```scala +println(pBook == aBook) // false +println(aBook == pBook) // false +``` + +#### 实现 “equals” 以使它们真正起作用 + +虽然现在允许进行这些比较,但它们将始终为 `false`,因为它们的 `equals` 方法不知道如何进行这些比较。 +因此,解决方案是覆盖每个类的 `equals` 方法。 +例如,当您覆盖 `AudioBook` 的 `equals` 方法时: + +```scala +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book: + // override to allow AudioBook to be compared to PrintedBook + override def equals(that: Any): Boolean = that match + case a: AudioBook => + if this.author == a.author + && this.title == a.title + && this.year == a.year + && this.lengthInMinutes == a.lengthInMinutes + then true else false + case p: PrintedBook => + if this.author == p.author && this.title == p.title + then true else false + case _ => + false +``` + +您现在可以将 `AudioBook` 与 `PrintedBook` 进行比较: + +```scala +println(aBook == pBook) // true (works because of `equals` in `AudioBook`) +println(pBook == aBook) // false +``` + +目前 `PrintedBook` 书没有 `equals` 方法,所以第二个比较返回 `false`。 +要启用该比较,只需覆盖 `PrintedBook` 中的 `equals` 方法。 + +您可以在参考文档中找到有关[多元相等性][ref-equal] 的更多信息。 + + +[ref-equal]: {{ site.scala3ref }}/contextual/multiversal-equality.html diff --git a/_zh-cn/overviews/scala3-book/ca-summary.md b/_zh-cn/overviews/scala3-book/ca-summary.md new file mode 100644 index 0000000000..60b1fd01a5 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-summary.md @@ -0,0 +1,37 @@ +--- +title: 总结 +type: section +description: This page provides a summary of the Contextual Abstractions lessons. +language: zh-cn +num: 67 +previous-page: ca-implicit-conversions +next-page: concurrency + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章介绍了大多数上下文抽象主题,包括: + +- 给定实例和使用子句 +- 上下文绑定 +- 给定导入 +- 扩展方法 +- 实现类型类 +- 多元相等性 +- 隐式转换 + +这里没有涉及一些更高级的主题,包括: + +- 类型类派生 +- 上下文函数 +- 传名上下文参数 +- 与 Scala 2 隐式转换 的关系 + +这些主题在 [参考文档][ref] 中有详细讨论。 + + +[ref]: {{ site.scala3ref }}/contextual diff --git a/_zh-cn/overviews/scala3-book/ca-type-classes.md b/_zh-cn/overviews/scala3-book/ca-type-classes.md new file mode 100644 index 0000000000..7adffa0594 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-type-classes.md @@ -0,0 +1,193 @@ +--- +title: 实现类型类 +type: section +description: This page demonstrates how to create and use type classes in Scala 3. +language: zh-cn +num: 64 +previous-page: ca-given-imports +next-page: ca-multiversal-equality + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +_类型类_ 是一种抽象的参数化类型,它允许您在不使用子类型的情况下向任何封闭数据类型添加新行为。 +如果你从 Java 那边过来,你可以将类型类视为类似于 [`java.util.Comparator[T]`][comparator] 的东西。 + +> Oliveira 等人写的论文 [“Type Classes as Objects and Implicits”][typeclasses-paper] (2010) 讨论了在 Scala 中类型类背后的基本观点。 +> 虽然论文用了旧的 Scala 版本,但其中的观点至今依然有用。 + +这在多用例中很有用,例如: + +- 表达你不拥有的类型——来自标准库或第三方库——如何符合这种行为 +- 为多种类型表达这种行为,而不涉及这些类型之间的子类型关系 + +类型类只是具有一个或多个参数的 traits,其实现在 Scala 3 中,由 `given` 实例提供,在 Scala 2 中用 `implicit` 值。 + +## 例子 + +例如,`Show` 是 Haskell 中众所周知的类型类,下面的代码显示了在 Scala 中实现它的一种方法。 +如果您认为 Scala 类没有 `toString` 方法,您可以定义一个 `Show` 类型类,然后把此行为添加到任意的类,这个类是能够转换为自定义字符串。 + +### 类型类 + +创建类型类的第一步是声明具有一个或多个抽象方法的参数化 trait。 +因为 `Showable` 只有一个名为 `show` 的方法,所以写成这样: + +{% tabs 'definition' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +// a type class +trait Showable[A] { + def show(a: A): String +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +// a type class +trait Showable[A]: + extension(a: A) def show: String +``` +{% endtab %} +{% endtabs %} + +请注意,通常当你要定义 `Show` trait时,下面这样的办法接近普通的面向对象的办法: + +{% tabs 'trait' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +// a trait +trait Show { + def show: String +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +// a trait +trait Show: + def show: String +``` +{% endtab %} +{% endtabs %} + +有几件重要的事情需要指出: + +1. 像 `Showable` 这样的类型类有一个类型参数 `A` 来说明我们为哪种类型提供了 `show` 的实现;相反,像 `Show` 这样的传统的 trait 不会。 +2. 要将 show 功能添加到特定类型 `A`,传统的 trait 需要 `A extends Show`,而对于类型类,我们需要实现 `Showable[A]`。 +3. 在 Scala 3 中,为了在两个 `Showable` 中允许相同的方法调用语法来模仿 `Show`,我们将 `Showable.show` 定义为扩展方法。 + +### 实现具体实例 + +下一步是确定在应用程序中,`Showable` 适用于哪些类,然后为它们实现该行为。 +例如,为这个 `Person` 类实现 `Showable`: + +{% tabs 'person' %} +{% tab 'Scala 2 and 3' %} +```scala +case class Person(firstName: String, lastName: String) +``` +{% endtab %} +{% endtabs %} + +你将为 `Showable[Person]` 定义一个 _规范值_ ,例如下面的代码为 `Person` 类提供了一个 `Showable` 的实例: + +{% tabs 'instance' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +implicit val showablePerson: Showable[Person] = new Showable[Person] { + def show(p: Person): String = + s"${p.firstName} ${p.lastName}" +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +given Showable[Person] with + extension(p: Person) def show: String = + s"${p.firstName} ${p.lastName}" +``` +{% endtab %} +{% endtabs %} + +### 使用类型类 + +现在你可以像这样使用这个类型类: + +{% tabs 'usage' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val person = Person("John", "Doe") +println(showablePerson.show(person)) +``` + +注意,在实践中,类型类一般与类型未知的值一起使用,而不像下面章节展示的 `Person` 类。 +{% endtab %} +{% tab 'Scala 3' %} +```scala +val person = Person("John", "Doe") +println(person.show) +``` +{% endtab %} +{% endtabs %} + +同样,如果 Scala 没有可用于每个类的 `toString` 方法,您可以使用此技术将 `Showable` 行为添加到您希望能够转换为 `String` 的任何类。 + +### 编写使用类型类的方法 + +与继承一样,您可以定义使用 `Showable` 作为类型参数的方法: + +{% tabs 'method' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit = + as.foreach(a => println(showable.show(a))) + +showAll(List(Person("Jane"), Person("Mary"))) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def showAll[A: Showable](as: List[A]): Unit = + as.foreach(x => println(a.show)) + +showAll(List(Person("Jane"), Person("Mary"))) +``` +{% endtab %} +{% endtabs %} + +### 具有多种方法的类型类 + +请注意,如果要创建具有多个方法的类型类,则初始语法如下所示: + +{% tabs 'multiple-methods' class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait HasLegs[A] { + def walk(a: A): Unit + def run(a: A): Unit +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +trait HasLegs[A]: + extension (a: A) + def walk(): Unit + def run(): Unit +``` +{% endtab %} +{% endtabs %} + +### 一个真实的例子 + +有关如何在 Scala 3 中使用类型类的真实示例,请参阅[多元相等性部分][multiversal]中的 `CanEqual` 讨论。 + +[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf +[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} +[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html +[multiversal]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} diff --git a/_zh-cn/overviews/scala3-book/collections-classes.md b/_zh-cn/overviews/scala3-book/collections-classes.md new file mode 100644 index 0000000000..47ccd58bbe --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-classes.md @@ -0,0 +1,861 @@ +--- +title: 集合类型 +type: section +description: This page introduces the common Scala 3 collections types and some of their methods. +language: zh-cn +num: 38 +previous-page: collections-intro +next-page: collections-methods + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% comment %} +TODO: mention Array, ArrayDeque, ListBuffer, Queue, Stack, StringBuilder? +LATER: note that methods like `+`, `++`, etc., are aliases for other methods +LATER: add links to the Scaladoc for the major types shown here +{% endcomment %} + +本页演示了常见的 Scala 3 集合及其附带的方法。 +Scala 提供了丰富的集合类型,但您可以从其中的几个开始,然后根据需要使用其他的。 +同样,每种集合类型都有数十种方法可以让您的生活更轻松,但您可以从其中的少数几个开始使用,就可以有很多收获。 + +因此,本节介绍并演示了在开始时,需要使用的最常见的类型和方法。 +当您需要更大的灵活性时,请参阅本节末尾的这些页面以获取更多细节。 + +## 三大类集合 + +从高层次看 Scala 集合,有三个主要类别可供选择: + +- **序列**是元素的顺序集合,可以是_有索引的_(如数组)或_线性的_(如链表) +- **映射** 包含键/值对的集合,例如 Java `Map`、Python 字典或 Ruby `Hash` +- **集合** 是无重复元素的无序集合 + +所有这些都是基本类型,并且具有用于特定目的的子类型,例如并发、缓存和流式传输。 +除了这三个主要类别之外,还有其他有用的集合类型,包括范围、堆栈和队列。 + +### 集合层次结构 + +作为简要概述,接下来的三个图显示了 Scala 集合中类和 trait 的层次结构。 + +第一张图显示了_scala.collection_包中的集合类型。 +这些都是高级抽象类或 traits,它们通常有_不可变_和_可变_的实现。 + +![一般集合层次结构][collections1] + +此图显示包 _scala.collection.immutable_ 中的所有集合: + +![不可变集合层次结构][collections2] + +此图显示包 _scala.collection.mutable_ 中的所有集合: + +![可变集合层次结构][collections3] + +在查看了所有集合类型的详细视图后,以下部分将介绍一些经常使用的常见类型。 + +{% comment %} +NOTE: those images come from this page: https://docs.scala-lang.org/overviews/collections-2.13/overview.html +{% endcomment %} + +## 常用集合 + +您经常使用的主要集合是: + +| 集合类型 | 不可变 | 可变 | 说明 | +| ------------- | --------- | -------- | ------------ | +| `List` | ✓ | | 线性(链表)、不可变序列 | +| `Vector` | ✓ | | 一个索引的、不可变的序列 | +| `LazyList` | ✓ | | 一个惰性不可变链表,它的元素仅在需要时才计算;适用于大型或无限序列。 | +| `ArrayBuffer` | | ✓ | 可变索引序列的首选类型 | +| `ListBuffer` | | ✓ | 当你想要一个可变的 `List` 时使用;通常转换为“列表” | +| `Map` | ✓ | ✓ | 由键和值对组成的可迭代集合。 | +| `Set` | ✓ | ✓ | 没有重复元素的可迭代集合 | + +如图所示,`Map` 和 `Set` 有不可变和可变版本。 + +以下部分演示了每种类型的基础知识。 + +> 在 Scala 中,_缓冲_——例如 `ArrayBuffer` 和 `ListBuffer`——是一个可以增长和缩小的序列。 + +### 关于不可变集合的说明 + +在接下来的部分中,无论何时使用_不可变_这个词,都可以安全地假设该类型旨在用于_函数式编程_(FP) 风格。 +使用这些类型,您无需修改​​集合;您将函数式方法应用于该集合以创建新的结果。 + +## 选择序列 + +选择_序列_ -- 一个顺序集合元素时 -- 您有两个主要决定: + +- 是否应该对序列进行索引(如数组),允许快速访问任何元素,还是应该将其实现为线性链表? +- 你想要一个可变的还是不可变的集合? + +此处显示了推荐的通用顺序集合,用于可变/不可变和索引/线性组合: + +| 类型/类别 | 不可变 | 可变 | +| --------------------- | --------- | ------------ | +|索引 | `Vector` |`ArrayBuffer` | +|线性(链表) | `List` |`ListBuffer` | + +例如,如果您需要一个不可变的索引集合,通常您应该使用 `Vector`。 +相反,如果您需要一个可变的索引集合,请使用 `ArrayBuffer`。 + +> `List` 和 `Vector` 在以函数式风格编写代码时经常使用。 +> `ArrayBuffer` 通常在以命令式风格编写代码时使用。 +> `ListBuffer` 用于混合样式时,例如构建列表。 + +接下来的几节简要介绍了 `List`、`Vector` 和 `ArrayBuffer` 类型。 + +## `List` + +[列表类型](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) 是一个线性的、不可变的序列。 +这只是意味着它是一个您无法修改的链表。 +任何时候你想添加或删除 `List` 元素,你都可以从现有的 `List` 中创建一个新的 `List`。 + +### 创建列表 + +这是创建初始“列表”的方式: + +{% tabs list-creation %} +{% tab 'Scala 2 and 3' %} +```scala +val ints = List(1, 2, 3) +val names = List("Joel", "Chris", "Ed") + +// another way to construct a List +val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil +``` +{% endtab %} +{% endtabs %} + +如果您愿意,也可以声明 `List` 的类型,但通常不是必需的: + +{% tabs list-type %} +{% tab 'Scala 2 and 3' %} +```scala +val ints: List[Int] = List(1, 2, 3) +val names: List[String] = List("Joel", "Chris", "Ed") +``` +{% endtab %} +{% endtabs %} + +一个例外是集合中有混合类型时。在这种情况下,您可能需要明确指定其类型: + +{% tabs list-mixed-types class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val things: List[Any] = List(1, "two", 3.0) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val things: List[String | Int | Double] = List(1, "two", 3.0) // with union types +val thingsAny: List[Any] = List(1, "two", 3.0) // with any +``` +{% endtab %} +{% endtabs %} + +### 将元素添加到列表 + +因为 `List` 是不可变的,所以你不能向它添加新元素。 +相反,您可以通过将元素添加到现有 `List` 来创建新列表。 +例如,给定这个 `List`: + +{% tabs adding-elements-init %} +{% tab 'Scala 2 and 3' %} +```scala +val a = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +使用 `List` 时,用 `::` 来_附加_一个元素,用 `:::` 把另一个 `List` 插在这个 `List` 之前,如下所示: + +{% tabs adding-elements-example %} +{% tab 'Scala 2 and 3' %} +```scala +val b = 0 :: a // List(0, 1, 2, 3) +val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +你也可以在 `List` 中添加元素,但是因为 `List` 是一个单链表,你通常应该只在它前面添加元素; +在它的后面添加元素是一个相对较慢的操作,尤其是在处理大型序列时。 + +> 提示:如果您想将元素添加到不可变序列的前面或者后面时,请改用 `Vector`。 + +因为 `List` 是一个链表,你不应该尝试通过索引值来访问大列表的元素。 +例如,如果您有一个包含一百万个元素的 `List` ,则访问像 `myList(999_999)` 这样的元素将花费相对较长的时间,因为该请求必须遍历所有这些元素。 +如果您有一个大型集合并希望通过索引访问元素,请改用 `Vector` 或 `ArrayBuffer`。 + +### 如何记住方法名 + +现在 IDE 为我们提供了极大的帮助,但是记住这些方法名称的一种方法是,认为 `:` 字符代表序列所在的一侧,因此当您使用 `+:` 时,您知道列表需要在右边,像这样: + +{% tabs list-prepending %} +{% tab 'Scala 2 and 3' %} +```scala +0 +: a +``` +{% endtab %} +{% endtabs %} + +同样,当您使用 `:+` 时,您知道列表需要在左侧: + +{% tabs list-appending %} +{% tab 'Scala 2 and 3' %} +```scala +a :+ 4 +``` +{% endtab %} +{% endtabs %} + +有更多的技术方法可以考虑这一点,但这可能是记住方法名称的有用方法。 + +{% comment %} +LATER: Add a discussion of `:` on method names, right-associativity, and infix operators. +{% endcomment %} + +此外,这些符号方法名称的一个好处是它们是一致的。 +相同的方法名称用于其他不可变序列,例如 `Seq` 和 `Vector`。 +如果您愿意,还可以使用非符号方法名称来附加元素和在头部插入元素。 + +### 如何遍历列表 + +给定一个名称 `List`: + +{% tabs list-loop-init %} +{% tab 'Scala 2 and 3' %} +```scala +val names = List("Joel", "Chris", "Ed") +``` +{% endtab %} +{% endtabs %} + +您可以像这样打印每个字符串: + +{% tabs list-loop-example class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +for (name <- names) println(name) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for name <- names do println(name) +``` +{% endtab %} +{% endtabs %} + +这是它在 REPL 中的样子: + +{% tabs list-loop-repl class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for name <-names do println(name) +Joel +Chris +Ed +``` +{% endtab %} +{% endtabs %} + +将 `for` 循环与集合一起使用的一个好处是 Scala 是一致的,并且相同的方法适用于所有序列,包括 `Array`、`ArrayBuffer`、`List`、`Seq`、`Vector`、`Map` ,`Set` 等。 + +### 一点历史 + +对于那些对历史感兴趣的人,Scala `List` 类似于 [Lisp 编程语言](https://en.wikipedia.org/wiki/Lisp_(programming_language)) 中的 `List`,它是最初于 1958 年确定的。 +实际上,除了像这样创建一个 `List` 之外: + +{% tabs list-history-init %} +{% tab 'Scala 2 and 3' %} +```scala +val ints = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +您也可以通过这种方式创建完全相同的列表: + +{% tabs list-history-init2 %} +{% tab 'Scala 2 and 3' %} +```scala +val list = 1 :: 2 :: 3 :: Nil +``` +{% endtab %} +{% endtabs %} + +REPL 展示了它是如何工作的: + +{% tabs list-history-repl %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val list = 1 :: 2 :: 3 :: Nil +list: List[Int] = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +这是因为 `List` 是一个以 `Nil` 元素结尾的单链表,而 `::` 是一个 `List` 方法,其工作方式类似于 Lisp 的“cons”运算符。 + +### 旁白:LazyList + +Scala 集合还包括一个 [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html),它是一个 _惰性_不可变链表。 +它被称为“惰性”——或非严格——因为它仅在需要时计算其元素。 + +你可以看到 REPL 中的 `LazyList` 有多懒惰: + +{% tabs lazylist-example %} +{% tab 'Scala 2 and 3' %} +```scala +val x = LazyList.range(1, Int.MaxValue) +x.take(1) // LazyList() +x.take(5) // LazyList() +x.map(_ + 1) // LazyList() +``` +{% endtab %} +{% endtabs %} + +在所有这些例子中,什么都没有发生。 +事实上,除非你强迫它发生,否则什么都不会发生,例如通过调用它的 `foreach` 方法: + +{% tabs lazylist-evaluation-example %} +{% tab 'Scala 2 and 3' %} +```scala +scala> x.take(1).foreach(println) +1 +``` +{% endtab %} +{% endtabs %} + +有关严格和非严格的用途、好处和缺点的更多信息严格(惰性)集合,请参阅 [Scala 2.13集合的架构][strict] 页面上的“严格”和“非严格”讨论。 + + + +## 向量 + +[向量](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) 是一个索引的、不可变的序列。 +描述的“索引”部分意味着它提供了在有效恒定时间内随机访问和更新向量,因此您可以通过索引值快速访问 `Vector` 元素,例如访问 `listOfPeople(123_456_789)` 。 + +一般来说,除了 (a) `Vector` 有索引而 `List` 没有索引,以及 (b) `List` 有 `::` 方法这两个不同外,这两种类型的工作方式相同,所以我们将快速过一下示例。 + +以下是创建“向量”的几种方法: + +{% tabs vector-creation %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = Vector(1, 2, 3, 4, 5) + +val strings = Vector("one", "two") + +case class Person(name: String) +val people = Vector( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} +{% endtabs %} + +因为 `Vector` 是不可变的,所以你不能向它添加新元素。 +相反,您通过将元素附加或插入头部到现有的 `Vector`,从而创建新序列。 +这些示例展示了如何将元素_附加_到 `Vector`: + +{% tabs vector-appending %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = a :+ 4 // Vector(1, 2, 3, 4) +val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5) +``` +{% endtab %} +{% endtabs %} + +这就是你_插入头部_元素的方式: + +{% tabs vector-prepending %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = 0 +: a // Vector(0, 1, 2, 3) +val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +除了快速的随机访问和更新之外,`Vector` 还提供了快速的追加和前置时间,因此您可以根据需要使用这些功能。 + +> 请参阅 [集合性能特性](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html) 了解有关 `Vector` 和其他集合的性能详细信息。 + +最后,您可以在 `for` 循环中使用 `Vector`,就像 `List`、`ArrayBuffer` 或任何其他序列一样: + +{% tabs vector-loop class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for name <- names do println(name) +Joel +Chris +Ed +``` +{% endtab %} +{% endtabs %} + +## 数组缓冲区 + +当您在 Scala 应用程序中需要一个通用的、可变的索引序列时,请使用 `ArrayBuffer`。 +它是可变的,所以你可以改变它的元素,也可以调整它的大小。 +因为它是索引的,所以元素的随机访问很快。 + +### 创建一个数组缓冲区 + +要使用 `ArrayBuffer`,首先导入它: + +{% tabs arraybuffer-import %} +{% tab 'Scala 2 and 3' %} +```scala +import scala.collection.mutable.ArrayBuffer +``` +{% endtab %} +{% endtabs %} + +如果您需要从一个空的 `ArrayBuffer` 开始,只需指定其类型: + +{% tabs arraybuffer-creation %} +{% tab 'Scala 2 and 3' %} +```scala +var strings = ArrayBuffer[String]() +var ints = ArrayBuffer[Int]() +var people = ArrayBuffer[Person]() +``` +{% endtab %} +{% endtabs %} + +如果您知道 `ArrayBuffer` 最终需要的大致大小,则可以使用初始大小创建它: + +{% tabs list-creation-with-size %} +{% tab 'Scala 2 and 3' %} +```scala +// ready to hold 100,000 ints +val buf = new ArrayBuffer[Int](100_000) +``` +{% endtab %} +{% endtabs %} + +要创建具有初始元素的新 `ArrayBuffer`,只需指定其初始元素,就像 `List` 或 `Vector` 一样: + +{% tabs arraybuffer-init %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) +val people = ArrayBuffer( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} +{% endtabs %} + +### 将元素添加到数组缓冲区 + +使用 `+=` 和 `++=` 方法将新元素附加到 `ArrayBuffer`。 +或者,如果您更喜欢具有文本名称的方法,您也可以使用 `append`、`appendAll`、`insert`、`insertAll`、`prepend` 和 `prependAll`。 + +以下是 `+=` 和 `++=` 的一些示例: + +{% tabs arraybuffer-add %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) +nums += 4 // ArrayBuffer(1, 2, 3, 4) +nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) +``` +{% endtab %} +{% endtabs %} + +### 从数组缓冲区中移除元素 + +`ArrayBuffer` 是可变的,所以它有 `-=`、`--=`、`clear`、`remove` 等方法。 +这些示例演示了 `-=` 和 `--=` 方法: + +{% tabs arraybuffer-remove %} +{% tab 'Scala 2 and 3' %} +```scala +val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) +a -= 'a' // ArrayBuffer(b, c, d, e, f, g) +a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g) +a --= Set('d', 'e') // ArrayBuffer(f, g) +``` +{% endtab %} +{% endtabs %} + +### 更新数组缓冲区元素 + +通过重新分配所需元素或使用 `update` 方法来更新 `ArrayBuffer` 中的元素: + +{% tabs arraybuffer-update %} +{% tab 'Scala 2 and 3' %} +```scala +val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4) +a(2) = 50 // ArrayBuffer(1, 2, 50, 4) +a.update(0, 10) // ArrayBuffer(10, 2, 50, 4) +``` +{% endtab %} +{% endtabs %} + +## 映射 + +`Map` 是由键值对组成的可迭代集合。 +Scala 有可变和不可变的 `Map` 类型,本节演示如何使用_不可变_ `Map`。 + +### 创建不可变映射 + +像这样创建一个不可变的`Map`: + +{% tabs map-init %} +{% tab 'Scala 2 and 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) +``` +{% endtab %} +{% endtabs %} + +一旦你有了一个`Map`,你可以像这样在`for`循环中遍历它的元素: + +{% tabs map-loop class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} +{% endtabs %} + +REPL 展示了它是如何工作的: + +{% tabs map-repl class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +scala> for ((k, v) <- states) println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for (k, v) <- states do println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} +{% endtabs %} + +### 访问映射元素 + +通过在括号中指定所需的键值来访问映射元素: + +{% tabs map-access-element %} +{% tab 'Scala 2 and 3' %} +```scala +val ak = states("AK") // ak: String = Alaska +val al = states("AL") // al: String = Alabama +``` +{% endtab %} +{% endtabs %} + +在实践中,您还将使用诸如 `keys`、`keySet`、`keysIterator`、`for` 循环之类的方法以及 `map` 之类的高阶函数来处理 `Map` 键和值。 + +### 向映射添加元素 + +使用 `+` 和 `++` 将元素添加到不可变映射中,记住将结果分配给新变量: + +{% tabs map-add-element %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Map(1 -> "one") // a: Map(1 -> one) +val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) +val c = b ++ Seq( + 3 -> "three", + 4 -> "four" +) +// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) +``` +{% endtab %} +{% endtabs %} + +### 从映射中删除元素 + +使用 `-` 或 `--` 和要删除的键值从不可变映射中删除元素,记住将结果分配给新变量: + +{% tabs map-remove-element %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three", + 4 -> "four" +) + +val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three) +val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two) +``` +{% endtab %} +{% endtabs %} + +### 更新映射元素 + +要更新不可变映射中的元素,请在将结果分配给新变量时使用 `updated` 方法(或 `+` 运算符): + +{% tabs map-update-element %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three" +) + +val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!) +val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three) +``` +{% endtab %} +{% endtabs %} + +### 遍历映射 + +如前所述,这是使用 `for` 循环手动遍历映射中元素的常用方法: + +{% tabs map-traverse class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} +{% endtabs %} + +话虽如此,有_许多_方法可以使用映射中的键和值。 +常见的 `Map` 方法包括 `foreach`、`map`、`keys` 和 `values`。 + +Scala 有许多更专业的`Map` 类型,包括`CollisionProofHashMap`、`HashMap`、`LinkedHashMap`、`ListMap`、`SortedMap`、`TreeMap`、`WeakHashMap` 等等。 + +## 使用集合 + +Scala [集合]({{site.baseurl}}/overviews/collections-2.13/sets.html) 是一个没有重复元素的可迭代集合。 + +Scala 有可变和不可变的 `Set` 类型。 +本节演示_不可变_ `Set`。 + +### 创建一个集合 + +像这样创建新的空集: + +{% tabs set-creation %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = Set[Int]() +val letters = Set[Char]() +``` +{% endtab %} +{% endtabs %} + +使用初始数据创建集合,如下: + +{% tabs set-init %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) +val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') +``` +{% endtab %} +{% endtabs %} + +### 向集合中添加元素 + +使用 `+` 和 `++` 将元素添加到不可变的 `Set`,记住将结果分配给一个新变量: + +{% tabs set-add-element %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Set(1, 2) // Set(1, 2) +val b = a + 3 // Set(1, 2, 3) +val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4) +``` +{% endtab %} +{% endtabs %} + +请注意,当您尝试添加重复元素时,它们会被悄悄删除。 + +另请注意,元素的迭代顺序是任意的。 + +### 从集合中删除元素 + +使用 `-` 和 `--` 从不可变集合中删除元素,再次将结果分配给新变量: + +{% tabs set-remove-element %} +{% tab 'Scala 2 and 3' %} +```scala +val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4) +val b = a - 5 // HashSet(1, 2, 3, 4) +val c = b -- Seq(3, 4) // HashSet(1, 2) +``` +{% endtab %} +{% endtabs %} + +## 范围 + +Scala `Range` 通常用于填充数据结构和迭代 `for` 循环。 +这些 REPL 示例演示了如何创建范围: + +{% comment %} +LATER: the dotty repl currently shows results differently +{% endcomment %} + +{% tabs range-init %} +{% tab 'Scala 2 and 3' %} +```scala +1 to 5 // Range(1, 2, 3, 4, 5) +1 until 5 // Range(1, 2, 3, 4) +1 to 10 by 2 // Range(1, 3, 5, 7, 9) +'a' to 'c' // NumericRange(a, b, c) +``` +{% endtab %} +{% endtabs %} + +您可以使用范围来填充集合: + +{% tabs range-conversion %} +{% tab 'Scala 2 and 3' %} +```scala +val x = (1 to 5).toList // List(1, 2, 3, 4, 5) +val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5) +``` +{% endtab %} +{% endtabs %} + +它们也用于 `for` 循环: + +{% tabs range-iteration class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +scala> for (i <- 1 to 3) println(i) +1 +2 +3 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for i <- 1 to 3 do println(i) +1 +2 +3 +``` +{% endtab %} +{% endtabs %} + +还有 `range` 方法: + +{% tabs range-methods %} +{% tab 'Scala 2 and 3' %} +```scala +Vector.range(1, 5) // Vector(1, 2, 3, 4) +List.range(1, 10, 2) // List(1, 3, 5, 7, 9) +Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4) +``` +{% endtab %} +{% endtabs %} + +当您运行测试时,范围对于生成​​测试集合也很有用: + +{% tabs range-tests %} +{% tab 'Scala 2 and 3' %} +```scala +val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10) +val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0) + +// create a Map +val map = (1 to 3).map(e => (e,s"$e")).toMap + // map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3") +``` +{% endtab %} +{% endtabs %} + +## 更多细节 + +当您需要特定集合更多的信息,请参阅以下资源: + +- [具体的不可变集合类](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html) +- [具体的可变集合类](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html) +- [集合是如何构造的? 我应该选择哪一个?](https://docs.scala-lang.org/tutorials/FAQ/collections.html) + + +[strict]: {% link _overviews/core/architecture-of-scala-213-collections.md %} +[collections1]: /resources/images/tour/collections-diagram-213.svg +[collections2]: /resources/images/tour/collections-immutable-diagram-213.svg +[collections3]: /resources/images/tour/collections-mutable-diagram-213.svg diff --git a/_zh-cn/overviews/scala3-book/collections-intro.md b/_zh-cn/overviews/scala3-book/collections-intro.md new file mode 100644 index 0000000000..0b2e1b77ff --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-intro.md @@ -0,0 +1,30 @@ +--- +title: Scala 集合 +type: chapter +description: This page provides and introduction to the common collections classes and their methods in Scala 3. +language: zh-cn +num: 37 +previous-page: packaging-imports +next-page: collections-classes + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章介绍了最常见的 Scala 3 集合及其附带的方法。 +Scala 提供了丰富的集合类型,但您可以从其中的几个开始,然后根据需要使用其他的。 +同样,每种类型都有数十种方法可以让您的生活更轻松,但您可以从少数几种方法开始使用,就可以有很多收获。 + +因此,本节介绍并演示您开始时,需要使用的最常见的集合类型和方法。 + +{% comment %} +LATER: Use more of the content from this page: + https://docs.scala-lang.org/overviews/index.html +{% endcomment %} + + + + diff --git a/_zh-cn/overviews/scala3-book/collections-methods.md b/_zh-cn/overviews/scala3-book/collections-methods.md new file mode 100644 index 0000000000..898619d144 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-methods.md @@ -0,0 +1,558 @@ +--- +title: 集合方法 +type: section +description: This page demonstrates the common methods on the Scala 3 collections classes. +language: zh-cn +num: 39 +previous-page: collections-classes +next-page: collections-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 集合的一大优势在于它们提供了许多开箱即用的方法,并且这些方法在不可变和可变集合类型中始终可用。 +这样做的好处是,您不用在每次需要使用集合时编写自定义的 `for` 循环,并且当您从一个项目转到另一个项目时,您会发现更多地使用这些相同的方法,而不是使用自定义 `for` 循环。 + +有*几十*种方法可供您使用,因此此处并未全部显示。 +相反,只显示了一些最常用的方法,包括: + +- `map` +- `filter` +- `foreach` +- `head` +- `tail` +- `take`, `takeWhile` +- `drop`, `dropWhile` +- `reduce` + +以下方法适用于所有序列类型,包括 `List`、`Vector`、`ArrayBuffer` 等,但除非另有说明,否则这些示例使用 `List`。 + +> 作为一个非常重要的说明,`List` 上的任何方法都不会改变列表。 +> 它们都以函数式风格工作,这意味着它们返回带有修改结果的新集合。 + +## 常用方法示例 + +为了让您大致了解在后面章节中将看到的内容,这些示例展示了一些最常用的集合方法。 +首先,这里有一些不使用 lambda 的方法: + +{% tabs common-method-examples %} +{% tab 'Scala 2 and 3' %} +```scala +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +``` +{% endtab %} +{% endtabs %} + +### 高阶函数和 lambda + +接下来,我们将展示一些常用的接受 lambda(匿名函数)的高阶函数 (HOF)。 +首先,这里有几个 lambda 语法的变体,从最长的形式开始,逐步过渡最简洁的形式: + +{% tabs higher-order-functions-example %} +{% tab 'Scala 2 and 3' %} +```scala +// these functions are all equivalent and return +// the same data: List(10, 20, 10) + +a.filter((i: Int) => i < 25) // 1. most explicit form +a.filter((i) => i < 25) // 2. `Int` is not required +a.filter(i => i < 25) // 3. the parens are not required +a.filter(_ < 25) // 4. `i` is not required +``` +{% endtab %} +{% endtabs %} + +在那些编号的例子中: + +1. 第一个例子显示了最长的形式。 + _很少_需要这么多的冗长,并且只在最复杂的用法中需要。 +2. 编译器知道 `a` 包含 `Int`,所以这里没有必要重述。 +3. 只有一个参数时不需要括号,例如`i`。 +4. 当你有一个参数并且它在你的匿名函数中只出现一次时,你可以用 `_` 替换参数。 + +[匿名函数][lambdas] 提供了与缩短 lambda 表达式相关的规则的更多详细信息和示例。 + +现在您已经看到了简洁的形式,下面是使用短形式 lambda 语法的其他 HOF 的示例: + +{% tabs anonymous-functions-example %} +{% tab 'Scala 2 and 3' %} +```scala +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ > 100) // List() +a.filterNot(_ < 25) // List(30, 40) +a.find(_ > 20) // Some(30) +a.takeWhile(_ < 30) // List(10, 20) +``` +{% endtab %} +{% endtabs %} + +值得注意的是,HOF 也接受方法和函数作为参数——不仅仅是 lambda 表达式。 +下面是一些使用名为 `double` 的方法的`map` HOF 示例。 +再次显示了 lambda 语法的几种变体: + +{% tabs method-as-parameter-example %} +{% tab 'Scala 2 and 3' %} +```scala +def double(i: Int) = i * 2 + +// these all return `List(20, 40, 60, 80, 20)` +a.map(i => double(i)) +a.map(double(_)) +a.map(double) +``` +{% endtab %} +{% endtabs %} + +在最后一个示例中,当匿名函数由一个接受单个参数的函数调用组成时,您不必命名参数,因此甚至不需要 `_`。 + +最后,您可以根据需要组合 HOF 来解决问题: + +{% tabs higher-order-functions-combination-example %} +{% tab 'Scala 2 and 3' %} +```scala +// yields `List(100, 200)` +a.filter(_ < 40) + .takeWhile(_ < 30) + .map(_ * 10) +``` +{% endtab %} +{% endtabs %} + +## 例子数据 + +以下部分中的示例使用这些列表: + +{% tabs sample-data %} +{% tab 'Scala 2 and 3' %} +```scala +val oneToTen = (1 to 10).toList +val names = List("adam", "brandy", "chris", "david") +``` +{% endtab %} +{% endtabs %} + +## `map` + +`map` 方法遍历现有列表中的每个元素,将您提供的函数应用于每个元素,一次一个; +然后它返回一个包含所有修改元素的新列表。 + +这是一个将 `map` 方法应用于 `oneToTen` 列表的示例: + +{% tabs map-example %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val doubles = oneToTen.map(_ * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} +{% endtabs %} + +您还可以使用长格式编写匿名函数,如下所示: + +{% tabs map-example-anonymous %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val doubles = oneToTen.map(i => i * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} +{% endtabs %} + +但是,在本课中,我们将始终使用第一种较短的形式。 + +以下是更多应用于 `oneToTen` 和 `names` 列表的 `map` 方法的示例: + +{% tabs few-more-examples %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val capNames = names.map(_.capitalize) +capNames: List[String] = List(Adam, Brandy, Chris, David) + +scala> val nameLengthsMap = names.map(s => (s, s.length)).toMap +nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david -> 5) + +scala> val isLessThanFive = oneToTen.map(_ < 5) +isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) +``` +{% endtab %} +{% endtabs %} + +如最后两个示例所示,使用 `map` 返回与原始类型不同类型的集合是完全合法的(并且很常见)。 + +## `filter` + +`filter` 方法创建一个新列表,其中包含满足所提供谓词的元素。 +谓词或条件是返回 `Boolean`(`true` 或 `false`)的函数。 +这里有一些例子: + +{% tabs filter-example %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val lessThanFive = oneToTen.filter(_ < 5) +lessThanFive: List[Int] = List(1, 2, 3, 4) + +scala> val evens = oneToTen.filter(_ % 2 == 0) +evens: List[Int] = List(2, 4, 6, 8, 10) + +scala> val shortNames = names.filter(_.length <= 4) +shortNames: List[String] = List(adam) +``` +{% endtab %} +{% endtabs %} + +集合上的函数式方法的一个优点是您可以将它们链接在一起以解决问题。 +例如,这个例子展示了如何链接 `filter` 和 `map`: + +{% tabs filter-example-anonymous %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.filter(_ < 4).map(_ * 10) +``` +{% endtab %} +{% endtabs %} + +REPL 显示结果: + +{% tabs filter-example-anonymous-repl %} +{% tab 'Scala 2 and 3' %} +```scala +scala> oneToTen.filter(_ < 4).map(_ * 10) +val res1: List[Int] = List(10, 20, 30) +``` +{% endtab %} +{% endtabs %} + +## `foreach` + +`foreach` 方法用于遍历集合中的所有元素。 +请注意,`foreach` 用于副作用,例如打印信息。 +这是一个带有 `names` 列表的示例: + +{% tabs foreach-example %} +{% tab 'Scala 2 and 3' %} +```scala +scala> names.foreach(println) +adam +brandy +chris +david +``` +{% endtab %} +{% endtabs %} + +## `head` + +`head` 方法来自 Lisp 和其他早期的函数式编程语言。 +它用于访问列表的第一个元素(头元素): + +{% tabs head-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.head // 1 +names.head // adam +``` +{% endtab %} +{% endtabs %} + +因为 `String` 可以看作是一个字符序列,所以你也可以把它当作一个列表。 +这就是 `head` 在这些字符串上的工作方式: + +{% tabs string-head-example %} +{% tab 'Scala 2 and 3' %} +```scala +"foo".head // 'f' +"bar".head // 'b' +``` +{% endtab %} +{% endtabs %} + +`head` 是一个很好的方法,但需要注意的是,在空集合上调用它时也会抛出异常: + +{% tabs head-error-example %} +{% tab 'Scala 2 and 3' %} +```scala +val emptyList = List[Int]() // emptyList: List[Int] = List() +emptyList.head // java.util.NoSuchElementException: head of empty list +``` +{% endtab %} +{% endtabs %} + +因此,您可能希望使用 `headOption` 而不是 `head`,尤其是在以函数式编程时: + +{% tabs head-option-example %} +{% tab 'Scala 2 and 3' %} +```scala +emptyList.headOption // None +``` +{% endtab %} +{% endtabs %} + +如图所示,它不会抛出异常,它只是返回值为 `None` 的类型 `Option`。 +您可以在 [函数式编程][fp-intro] 章节中了解有关这种编程风格的更多信息。 + +## `tail` + +`tail` 方法也来自 Lisp,它用于打印列表头元素之后的每个元素。 +几个例子展示了这一点: + +{% tabs tail-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.head // 1 +oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) + +names.head // adam +names.tail // List(brandy, chris, david) +``` +{% endtab %} +{% endtabs %} + +就像 `head` 一样,`tail` 也适用于字符串: + +{% tabs string-tail-example %} +{% tab 'Scala 2 and 3' %} +```scala +"foo".tail // "oo" +"bar".tail // "ar" +``` +{% endtab %} +{% endtabs %} + +如果列表为空,`tail` 会抛出 _java.lang.UnsupportedOperationException_,所以就像 `head` 和 `headOption` 一样,还有一个 `tailOption` 方法,这是函数式编程的首选方法。 + +也可以匹配一个列表,因此您可以编写如下表达式: + +{% tabs tail-match-example %} +{% tab 'Scala 2 and 3' %} +```scala +val x :: xs = names +``` +{% endtab %} +{% endtabs %} + +将该代码放在 REPL 中显示 `x` 分配给列表的头部,而 `xs` 分配给列表尾部: + +{% tabs tail-match-example-repl %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val x :: xs = names +val x: String = adam +val xs: List[String] = List(brandy, chris, david) +``` +{% endtab %} +{% endtabs %} + +像这样的模式匹配在许多情况下都很有用,例如使用递归编写一个 `sum` 方法: + +{% tabs tail-match-sum-example class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def sum(list: List[Int]): Int = list match { + case Nil => 0 + case x :: xs => x + sum(xs) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(list: List[Int]): Int = list match + case Nil => 0 + case x :: xs => x + sum(xs) +``` +{% endtab %} +{% endtabs %} + +## `take`、`takeRight`、`takeWhile` + +`take`、`takeRight` 和 `takeWhile` 方法为您提供了一种从列表中“获取”要用于创建新列表的元素的好方法。 +这是 `take` 和 `takeRight`: + +{% tabs take-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.take(1) // List(1) +oneToTen.take(2) // List(1, 2) + +oneToTen.takeRight(1) // List(10) +oneToTen.takeRight(2) // List(9, 10) +``` +{% endtab %} +{% endtabs %} + +注意这些方法是如何处理“临界”情况的,当我们要求比序列中更多的元素,或者要求零元素的时候: + +{% tabs take-edge-cases-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.take(0) // List() +oneToTen.takeRight(0) // List() +``` +{% endtab %} +{% endtabs %} + +这是`takeWhile`,它与谓词函数一起使用: + +{% tabs take-while-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) +names.takeWhile(_.length < 5) // List(adam) +``` +{% endtab %} +{% endtabs %} + +## `drop`、`dropRight`、`dropWhile` + +`drop`、`dropRight` 和 `dropWhile` 本质上与它们对应的“取”相反,从列表中删除元素。 +这里有些例子: + +{% tabs drop-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.drop(5) // List(6, 7, 8, 9, 10) + +oneToTen.dropRight(8) // List(1, 2) +oneToTen.dropRight(7) // List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +再次注意这些方法如何处理临界情况: + +{% tabs drop-edge-cases-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.drop(Int.MaxValue) // List() +oneToTen.dropRight(Int.MaxValue) // List() +oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +``` +{% endtab %} +{% endtabs %} + +这是 `dropWhile`,它与谓词函数一起使用: + +{% tabs drop-while-example %} +{% tab 'Scala 2 and 3' %} +```scala +oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) +names.dropWhile(_ != "chris") // List(chris, david) +``` +{% endtab %} +{% endtabs %} + +## `reduce` + +当您听到 “map reduce” 术语时,“reduce” 部分指的是诸如 `reduce` 之类的方法。 +它接受一个函数(或匿名函数)并将该函数应用于列表中的连续元素。 + +解释 `reduce` 的最好方法是创建一个可以传递给它的小辅助方法。 +例如,这是一个将两个整数相加的 `add` 方法,还为我们提供了一些不错的调试输出: + +{% tabs reduce-example class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def add(x: Int, y: Int): Int = { + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def add(x: Int, y: Int): Int = + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +``` +{% endtab %} +{% endtabs %} + +有上面的方法和下面的列表: + +{% tabs reduce-example-init %} +{% tab 'Scala 2 and 3' %} +```scala +val a = List(1,2,3,4) +``` +{% endtab %} +{% endtabs %} + +这就是将 `add` 方法传递给 `reduce` 时发生的情况: + +{% tabs reduce-example-evaluation %} +{% tab 'Scala 2 and 3' %} +```scala +scala> a.reduce(add) +received 1 and 2, their sum is 3 +received 3 and 3, their sum is 6 +received 6 and 4, their sum is 10 +res0: Int = 10 +``` +{% endtab %} +{% endtabs %} + +如该结果所示,`reduce` 使用`add` 将列表 `a` 归约为单个值,在这种情况下,是列表中整数的总和。 + +一旦你习惯了 `reduce`,你会写一个像这样的“求和”算法: + +{% tabs reduce-example-sum %} +{% tab 'Scala 2 and 3' %} +```scala +scala> a.reduce(_ + _) +res0: Int = 10 +``` +{% endtab %} +{% endtabs %} + +类似地,“连乘”算法如下所示: + +{% tabs reduce-example-multiply %} +{% tab 'Scala 2 and 3' %} +```scala +scala> a.reduce(_ * _) +res1: Int = 24 +``` +{% endtab %} +{% endtabs %} + +> 关于 `reduce` 的一个重要概念是——顾名思义——它用于将集合_归约_为单个值。 + +## 更多 + +在 Scala 集合类型上确实有几十个额外的方法,可以让你不再需要编写另一个 `for` 循环。有关 Scala 集合的更多详细信息,请参阅[可变和不可变集合][mut-immut-colls]和[Scala集合的架构][architecture]。 + +> 最后一点,如果您在 Scala 项目中使用 Java 代码,您可以将 Java 集合转换为 Scala 集合。 +> 通过这样做,您可以在 `for` 表达式中使用这些集合,还可以利用 Scala 的函数式集合方法。 +> 请参阅 [与 Java 交互][interacting] 部分了解更多详细信息。 + +[interacting]: {% link _zh-cn/overviews/scala3-book/interacting-with-java.md %} +[lambdas]: {% link _zh-cn/overviews/scala3-book/fun-anonymous-functions.md %} +[fp-intro]: {% link _zh-cn/overviews/scala3-book/fp-intro.md %} +[mut-immut-colls]: {% link _overviews/collections-2.13/overview.md %} +[architecture]: {% link _overviews/core/architecture-of-scala-213-collections.md %} + diff --git a/_zh-cn/overviews/scala3-book/collections-summary.md b/_zh-cn/overviews/scala3-book/collections-summary.md new file mode 100644 index 0000000000..8a0c958712 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-summary.md @@ -0,0 +1,37 @@ +--- +title: 总结 +type: section +description: This page provides a summary of the Collections chapter. +language: zh-cn +num: 40 +previous-page: collections-methods +next-page: fp-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章总结了常见的 Scala 3 集合及其附带的方法。 +如图所示,Scala 带有丰富的集合和方法。 + +当您需要查看本章中显示的集合类型的更多详细信息时,请参阅他们的 Scaladoc 页面: + +- [List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +- [Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) +- [ArrayBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ArrayBuffer.html) +- [Range](https://www.scala-lang.org/api/current/scala/collection/immutable/Range.html) + +也提到了不可变的 `Map` 和 `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) + +和可变的 `Map` 和 `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/mutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/mutable/Set.html) + + diff --git a/_zh-cn/overviews/scala3-book/concurrency.md b/_zh-cn/overviews/scala3-book/concurrency.md new file mode 100644 index 0000000000..169e1f1684 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/concurrency.md @@ -0,0 +1,315 @@ +--- +title: 并发 +type: chapter +description: This page discusses how Scala concurrency works, with an emphasis on Scala Futures. +language: zh-cn +num: 68 +previous-page: ca-summary +next-page: scala-tools + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用原生 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 + +## 介绍 + +以下是 Scaladoc 中对 Scala `Future` 的描述: + +> “ `Future` 代表一个值,它可能_当前_可用或不可用,但在某个时候可用,或者如果该值不能可用,则表示为异常。” + +为了演示这意味着什么,让我们首先看一下单线程编程。 +在单线程世界中,您将方法调用的结果绑定到如下变量: + +```scala +def aShortRunningTask(): Int = 42 +val x = aShortRunningTask() +``` + +在此代码中,值 `42` 立即绑定到 `x`。 + +当您使用 `Future` 时,分配过程看起来很相似: + +```scala +def aLongRunningTask(): Future[Int] = ??? +val x = aLongRunningTask() +``` + +但在这种情况下的主要区别在于,因为 `aLongRunningTask` 需要不确定的时间才能返回,所以 `x` 中的值可能_当前_可用也可能不可用,但它会在某个时候可用——在未来. + +另一种看待这个问题的方法是阻塞。 +在这个单线程示例中,在 `aShortRunningTask` 完成之前不会打印 `println` 语句: + +```scala +def aShortRunningTask(): Int = + Thread.sleep(500) + 42 +val x = aShortRunningTask() +println("Here") +``` + +相反,如果 `aShortRunningTask` 被创建为 `Future`,`println` 语句几乎立即被打印,因为 `aShortRunningTask` 是在其他线程上产生的——它不会阻塞。 + +在本章中,您将看到如何使用 futures,包括如何并行运行多个 future 并将它们的结果组合到一个 `for` 表达式中。 +您还将看到一些例子,在这些例子中,有些方法用于处理在返回的 future 中的值。 + +> 当你考虑 future 时,重要的是要知道它们是一次性的,“在其他线程上处理这个相对较慢的计算,完成后给把结果通知我”的结构。 +> 作为对比,[Akka](https://akka.io) Actor 旨在运行很长时间,并在其生命周期内响应许多请求。 +> 虽然 actor可能永远活着,但 future 最终会包含只运行一次的计算结果。 + +## REPL 中的一个例子 + +future 用于创建一个临时的并发包。 +例如,当您需要调用运行不确定时间的算法时---例如调用远程微服务---您使用 future---因此您希望在主线程之外运行它。 + +为了演示它是如何工作的,让我们从 REPL 中的 `Future` 示例开始。 +首先,粘贴这些必需的 `import` 语句: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +``` + +现在您已准备好创造 future 。 +对于这个例子,首先定义一个长时间运行的单线程算法: + +```scala +def longRunningAlgorithm() = + Thread.sleep(10_000) + 42 +``` + +这种奇特的算法在十秒延迟后返回整数值`42`。 +现在通过将其包装到 `Future` 构造函数中来调用该算法,并将结果分配给一个变量: + +```scala +scala> val eventualInt = Future(longRunningAlgorithm()) +eventualInt: scala.concurrent.Future[Int] = Future() +``` + +马上,您的计算——对 `longRunningAlgorithm()` 的调用——开始运行。 +如果你立即检查变量 `eventualInt` 的值,你会看到 future 还没有完成: + +```scala +scala> eventualInt +val res1: scala.concurrent.Future[Int] = Future() +``` + +但是如果你在十秒后再次检查,你会看到它已经成功完成了: + +```scala +scala> eventualInt +val res2: scala.concurrent.Future[Int] = Future(Success(42)) +``` + +虽然这是一个相对简单的示例,但它显示了基本方法:只需使用您的长时间运行的算法构建一个新的 `Future`。 + +需要注意的一点是,您期望的 `42` 被包裹在 `Success` 中,而后者又被包裹在 `Future` 中。 +这是一个需要理解的关键概念:`Future` 中的值始终是`scala.util.Try` 类型之一的实例:`Success` 或 `Failure`。 +因此,当您处理 future 的结果时,您使用通常的 `Try` 处理技术。 + +### 将 `map` 与 future 一起使用 + +`Future` 有一个 `map` 方法,你可以像使用集合中的 `map` 方法一样使用它。 +这是在创建变量 `f` 后立即调用 `map` 时的结果: + +```scala +scala> val a = eventualInt.map(_ * 2) +a: scala.concurrent.Future[Int] = Future() +``` + +如图所示,对于使用 `longRunningAlgorithm` 创建的 future ,初始输出显示 `Future()`。 +但是当你在十秒后检查 `a` 的值时,你会看到它包含 `84` 的预期结果: + +```scala +scala> a +res1: scala.concurrent.Future[Int] = Future(Success(84)) +``` + +再一次,成功的结果被包裹在 `Success` 和 `Future` 中。 + +### 在 future 中使用回调方法 + +除了像`map`这样的高阶函数,你还可以使用回调方法和futures。 +一种常用的回调方法是 `onComplete`,它采用*偏函数*,您可以在其中处理 `Success` 和 `Failure` 情况: + +```scala +eventualInt.onComplete { + case Success(value) => println(s"Got the callback,value = $value") + case Failure(e) => e.printStackTrace +} +``` + +当您将该代码粘贴到 REPL 中时,您最终会看到结果: + +```scala +Got the callback, value = 42 +``` + +## 其他 future 方法 + +`Future` 类还有其他可以使用的方法。 +它具有您在 Scala 集合类中找到的一些方法,包括: + +- `filter` +- `flatMap` +- `map` + +它的回调方法有: + +- `onComplete` +- `andThen` +- `foreach` + +其他转换方法包括: + +- `fallbackTo` +- `recover` +- `recoverWith` + +请参阅 [Futures and Promises][futures] 页面,了解有关 future 可用的其他方法的讨论。 + +## 运行多个 future 并加入他们的结果 + +要并行运行多个计算并在所有 future 完成后加入它们的结果,请使用 “for” 表达式。 + +正确的做法是: + +1. 开始计算返回 `Future` 结果 +2. 将他们的结果合并到一个 `for` 表达式中 +3. 使用 `onComplete` 或类似技术提取合并结果 + +### 一个例子 + +以下示例显示了正确方法的三个步骤。 +一个关键是你首先开始计算返回 future ,然后将它们加入到 `for` 表达式中: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +val startTime = System.currentTimeMillis() +def delta() = System.currentTimeMillis() - startTime +def sleep(millis: Long) = Thread.sleep(millis) + +@main def multipleFutures1 = + + println(s"creating the futures: ${delta()}") + + // (1) start the computations that return futures + val f1 = Future { sleep(800); 1 } // eventually returns 1 + val f2 = Future { sleep(200); 2 } // eventually returns 2 + val f3 = Future { sleep(400); 3 } // eventually returns 3 + + // (2) join the futures in a `for` expression + val result = + for + r1 <- f1 + r2 <- f2 + r3 <- f3 + yield + println(s"in the 'yield': ${delta()}") + (r1 + r2 + r3) + + // (3) process the result + result.onComplete { + case Success(x) => + println(s"in the Success case: ${delta()}") + println(s"result = $x") + case Failure(e) => + e.printStackTrace + } + + println(s"before the 'sleep(3000)': ${delta()}") + + // important for a little parallel demo: keep the jvm alive + sleep(3000) +``` + +当您运行该应用程序时,您会看到如下所示的输出: + +```` +creating the futures: 1 +before the 'sleep(3000)': 2 +in the 'yield': 806 +in the Success case: 806 +result = 6 +```` + +如该输出所示, future 的创建速度非常快,仅在两毫秒内就到达了方法末尾的 `sleep(3000)` 语句之前的打印语句。 +所有这些代码都在 JVM 的主线程上运行。 +然后,在 806 毫秒,三个 future 完成并运行 `yield` 块中的代码。 +然后代码立即转到 `onComplete` 方法中的 `Success` 分支。 + +806 毫秒的输出是看到三个计算并行运行的关键。 +如果它们按顺序运行,总时间约为 1,400 毫秒——三个计算的睡眠时间之和。 +但是因为它们是并行运行的,所以总时间只比运行时间最长的计算:`f1`,即 800 毫秒,稍长。 + +> 请注意,如果计算是在 `for` 表达式中运行的,它们 +> 将按顺序执行,而不是并行执行: +> ~~~ +> // Sequential execution (no parallelism!) +> for +> r1 <- Future { sleep(800); 1 } +> r2 <- Future { sleep(200); 2 } +> r3 <- Future { sleep(400); 3 } +> yield +> r1 + r2 + r3 +> ~~~ +> 因此,如果您希望计算可能并行运行,请记住 +> 在 `for` 表达式之外运行它们。 + +### 一个返回 future 的方法 + +到目前为止,您已经了解了如何将单线程算法传递给 `Future` 构造函数。 +您可以使用相同的技术来创建一个返回 `Future` 的方法: + +```scala +// simulate a slow-running method +def slowlyDouble(x: Int, delay: Long): Future[Int] = Future { + sleep(delay) + x * 2 +} +``` + +与前面的示例一样,只需将方法调用的结果分配给一个新变量。 +然后当你立刻检查结果时,你会看到它没有完成,但是在延迟时间之后,future 会有一个结果: + +```` +scala> val f = slowlyDouble(2, 5_000L) +val f: concurrent.Future[Int] = Future() + +scala> f +val res0: concurrent.Future[Int] = Future() + +scala> f +val res1: concurrent.Future[Int] = Future(Success(4)) +```` + +## 关于 future 的要点 + +希望这些示例能让您了解 Scala future 是如何工作的。 +总而言之,关于 future 的几个关键点是: + +- 您构建 future 以在主线程之外运行任务 +- Futures 用于一次性的、可能长时间运行的并发任务,这些任务*最终*返回一个值;他们创造了一个临时的并发的容器 +- 一旦你构建了 future,它就会开始运行 +- future 相对于线程的一个好处是它们可以使用 `for` 表达式,并带有各种回调方法,可以简化使用并发线程的过程 +- 当您使用 future 时,您不必关心线程管理的低级细节 +- 您可以使用 `onComplete` 和 `andThen` 之类的回调方法或 `filter`、`map` 等转换方法来处理 future 的结果。 +- `Future` 中的值始终是 `Try` 类型之一的实例:`Success` 或 `Failure` +- 如果您使用多个 future 来产生一个结果,请将它们组合在一个 `for` 表达式中 + +此外,正如您在这些示例中看到的 `import` 语句,Scala `Future` 依赖于 `ExecutionContext`。 + +有关 future 的更多详细信息,请参阅[Future 和 Promises][future],这是一篇讨论 future 、promises 和 ExecutionContext 的文章。 +它还讨论了如何将 `for` 表达式转换为 `flatMap` 操作。 + + +[futures]: {% link _overviews/core/futures.md %} diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md new file mode 100644 index 0000000000..fa46f25a2c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -0,0 +1,1120 @@ +--- +title: 控制结构 +type: chapter +description: This page provides an introduction to Scala's control structures, including if/then/else, 'for' loops, 'for' expressions, 'match' expressions, try/catch/finally, and 'while' loops. +language: zh-cn +num: 19 +previous-page: string-interpolation +next-page: domain-modeling-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala具有您希望在编程语言中找到的控制结构,包括: + +- `if`/`then`/`else` +- `for` 循环 +- `while` 循环 +- `try`/`catch`/`finally` + +它还具有另外两个您可能以前从未见过的强大结构,具体取决于您的编程背景: + +- `for` 表达式(也被称作 _`for` comprehensions_) +- `match` 表达式 + +这些都将在以下各节中进行演示。 + +## if/then/else 结构 + +单行 Scala `if` 语句像这样: + +{% tabs control-structures-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-1 %} + +```scala +if (x == 1) println(x) +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-1 %} + +```scala +if x == 1 then println(x) +``` + +{% endtab %} +{% endtabs %} + +如果要在 `if` 比较后运行多行代码,用这个语法: + +{% tabs control-structures-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-2 %} + +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-2 %} + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +``` + +{% endtab %} +{% endtabs %} + +`if`/`else` 语法像这样: + +{% tabs control-structures-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-3 %} + +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} else { + println("x was not 1") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-3 %} + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +else + println("x was not 1") +``` + +{% endtab %} +{% endtabs %} + +这是 `if`/`else if`/`else` 语法: + +{% tabs control-structures-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-4 %} + +```scala +if (x < 0) + println("negative") +else if (x == 0) + println("zero") +else + println("positive") +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-4 %} + +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` + +{% endtab %} +{% endtabs %} + +### `end if` 语句 + +
+  这是 Scala 3 里的新东西,在 Scala 2 里不支持。 +
+ +如果您愿意,可以选择在每个表达式的末尾包含 `end if` 语句: + +{% tabs control-structures-5 %} +{% tab 'Scala 3 Only' %} + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +end if +``` + +{% endtab %} +{% endtabs %} + +### `if`/`else` 表达式总是有返回值 + +请注意, `if` / `else` 比较形式为 _表达式_,这意味着它们返回一个可以分配给变量的值。 +因此,不需要特殊的三元运算符: + +{% tabs control-structures-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-6 %} + +```scala +val minValue = if (a < b) a else b +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-6 %} + +```scala +val minValue = if a < b then a else b +``` + +{% endtab %} +{% endtabs %} + +由于它们返回一个值,因此可以使用 `if`/`else` 表达式作为方法的主体: + +{% tabs control-structures-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-7 %} + +```scala +def compare(a: Int, b: Int): Int = + if (a < b) + -1 + else if (a == b) + 0 + else + 1 +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-7 %} + +```scala +def compare(a: Int, b: Int): Int = + if a < b then + -1 + else if a == b then + 0 + else + 1 +``` + +{% endtab %} +{% endtabs %} + +### 题外话:面向表达式的编程 + +作为一般编程的简要说明,当您编写的每个表达式都返回一个值时,该样式称为面向 _面向表达式的编程_ 或 EOP。 +例如,这是一个 _表达式_: + +{% tabs control-structures-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-8 %} + +```scala +val minValue = if (a < b) a else b +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-8 %} + +```scala +val minValue = if a < b then a else b +``` + +{% endtab %} +{% endtabs %} + +相反,不返回值的代码行称为 _语句_,用它们的 _副作用_。 +例如,这些代码行不返回值,因此用它们的副作用: + +{% tabs control-structures-9 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-9 %} + +```scala +if (a == b) action() +println("Hello") +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-9 %} + +```scala +if a == b then action() +println("Hello") +``` + +{% endtab %} +{% endtabs %} + +第一个示例在 `a` 等于 `b` 时将 `action` 方法作为副作用运行。 +第二个示例用于将字符串打印到 STDOUT 的副作用。 +随着你对Scala的了解越来越多,你会发现自己写的 _表达式_ 越来越多,_语句_ 也越来越少。 + +## `for` 循环 + +在最简单的用法中,Scala `for` 循环可用于迭代集合中的元素。 +例如,给定一个整数序列,您可以循环访问其元素并打印其值,如下所示: + +{% tabs control-structures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-10 %} + +```scala +val ints = Seq(1, 2, 3) +for (i <- ints) println(i) +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-10 %} + +```scala +val ints = Seq(1, 2, 3) +for i <- ints do println(i) +``` + +{% endtab %} +{% endtabs %} + +代码 `i <- ints` 被称为 _生成器_。 + +这是在 Scala REPL 中的结果: + +{% tabs control-structures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for (i <- ints) println(i) +1 +2 +3 +```` +{% endtab %} +{% tab 'Scala 3' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for i <- ints do println(i) +1 +2 +3 +```` + +{% endtab %} +{% endtabs %} + +如果需要在 `for` 生成器后面显示多行代码块,请使用以下语法:ddu + +{% tabs control-structures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-12 %} + +```scala +for (i <- ints) { + val x = i * 2 + println(s"i = $i, x = $x") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-12 %} + +```scala +for + i <- ints +do + val x = i * 2 + println(s"i = $i, x = $x") +``` + +{% endtab %} +{% endtabs %} + +### 多生成器 + +`for` 循环可以有多个生成器,如以下示例所示: + +{% tabs control-structures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-13 %} + +```scala +for { + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +} { + println(s"i = $i, j = $j, k = $k") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-13 %} + +```scala +for + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +do + println(s"i = $i, j = $j, k = $k") +``` + +{% endtab %} +{% endtabs %} + +那个表达式的输出: + +```` +i = 1, j = a, k = 1 +i = 1, j = a, k = 6 +i = 1, j = b, k = 1 +i = 1, j = b, k = 6 +i = 2, j = a, k = 1 +i = 2, j = a, k = 6 +i = 2, j = b, k = 1 +i = 2, j = b, k = 6 +```` + +### 守卫 + +`for` 循环也可以包含 `if` 语句,这些语句称为 _守卫_: + +{% tabs control-structures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-14 %} + +```scala +for { + i <- 1 to 5 + if i % 2 == 0 +} { + println(i) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-14 %} + +```scala +for + i <- 1 to 5 + if i % 2 == 0 +do + println(i) +``` + +{% endtab %} +{% endtabs %} + +以上循环的输出是: + +```` +2 +4 +```` + +`for` 循环可以根据需要有任意数量的守卫。 +此示例显示了打印数字`4`的一种方法: + +{% tabs control-structures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-15 %} + +```scala +for { + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +} { + println(i) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-15 %} + +```scala +for + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +do + println(i) +``` + +{% endtab %} +{% endtabs %} + +### 把 `for` 用在 `Map` 上 + +您还可以将 `for` 循环与 `Map` 一起使用。 +例如,给定州缩写及其全名的 `Map`: + +{% tabs map %} +{% tab 'Scala 2 and 3' for=map %} + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AR" -> "Arizona" +) +``` + +{% endtab %} +{% endtabs %} + +您可以使用 `for` 打印键和值,如下所示: + +{% tabs control-structures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-16 %} + +```scala +for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-16 %} + +```scala +for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +``` + +{% endtab %} +{% endtabs %} + +以下是 REPL 中的样子: + +{% tabs control-structures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-17 %} + +```scala +scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-17 %} + +```scala +scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` + +{% endtab %} +{% endtabs %} + +当 `for` 循环遍历映射时,每个键/值对都绑定到变量 `abbrev` 和 `fullName` ,它们位于元组中: + +{% tabs tuple %} +{% tab 'Scala 2 and 3' for=tuple %} + +```scala +(abbrev, fullName) <- states +``` + +{% endtab %} +{% endtabs %} + +当循环运行时,变量 `abbrev` 被分配给映射中的当前 _键_,变量 `fullName` 被分配给当前map 的 _值_。 + +## `for` 表达式 + +在前面的 `for` 循环示例中,这些循环都用于 _副作用_,特别是使用 `println` 将这些值打印到STDOUT。 + +重要的是要知道,您还可以创建有返回值的 `for` _表达式_。 +您可以通过添加 `yield` 关键字和要返回的表达式来创建 `for` 表达式,如下所示: + +{% tabs control-structures-18 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-18 %} + +```scala +val list = + for (i <- 10 to 12) + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-18 %} + +```scala +val list = + for i <- 10 to 12 + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` + +{% endtab %} +{% endtabs %} + +在 `for` 表达式运行后,变量 `list` 是包含所示值的 `Vector` 。 +这是表达式的工作原理: + +1. `for` 表达式开始循环访问范围 `(10, 11, 12)` 中的值。 + 它首先处理值`10`,将其乘以`2`,然后 _产生_ 结果为`20`的值。 +2. 接下来,它处理`11`---该范围中的第二个值。 + 它乘以`2`,然后产生值`22`。 + 您可以将这些产生的值看作它们累积在某个临时位置。 +3. 最后,循环从范围中获取数字 `12`,将其乘以 `2`,得到数字 `24`。 + 循环此时完成并产生最终结果 `Vector(20, 22, 24)`。 + +{% comment %} +NOTE: This is a place where it would be great to have a TIP or NOTE block: +{% endcomment %} + +虽然本节的目的是演示 `for` 表达式,但它可以帮助知道显示的 `for` 表达式等效于以下 `map` 方法调用: + +{% tabs map-call %} +{% tab 'Scala 2 and 3' for=map-call %} + +```scala +val list = (10 to 12).map(i => i * 2) +``` + +{% endtab %} +{% endtabs %} + +只要您需要遍历集合中的所有元素,并将算法应用于这些元素以创建新列表,就可以使用 `for` 表达式。 + +下面是一个示例,演示如何在 `yield` 之后使用代码块: + +{% tabs control-structures-19 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-19 %} + +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for (name <- names) yield { + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName +} + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-19 %} + +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` + +{% endtab %} +{% endtabs %} + +### 使用 `for` 表达式作为方法的主体 + +由于 `for` 表达式产生结果,因此可以将其用作返回有用值的方法的主体。 +此方法返回给定整数列表中介于`3`和`10`之间的所有值: + +{% tabs control-structures-20 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-20 %} + +```scala +def between3and10(xs: List[Int]): List[Int] = + for { + x <- xs + if x >= 3 + if x <= 10 + } yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-20 %} + +```scala +def between3and10(xs: List[Int]): List[Int] = + for + x <- xs + if x >= 3 + if x <= 10 + yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` + +{% endtab %} +{% endtabs %} + +## `while` 循环 + +Scala `while` 循环语法如下: + +{% tabs control-structures-21 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-21 %} + +```scala +var i = 0 + +while (i < 3) { + println(i) + i += 1 +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-21 %} + +```scala +var i = 0 + +while i < 3 do + println(i) + i += 1 +``` + +{% endtab %} +{% endtabs %} + +## `match` 表达式 + +模式匹配是函数式编程语言的一个主要特征,Scala包含一个具有许多功能的 `match` 表达式。 + +在最简单的情况下,您可以使用 `match` 表达式,象Java `switch` 语句,根据整数值匹配。 +请注意,这实际上是一个表达式,因为它计算出一个结果: + +{% tabs control-structures-22 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-22 %} + +```scala +// `i` is an integer +val day = i match { + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // the default, catch-all +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-22 %} + +```scala +import scala.annotation.switch + +// `i` is an integer +val day = i match + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // the default, catch-all +``` + +{% endtab %} +{% endtabs %} + +在此示例中,变量 `i` 根据所示情况进行测试。 +如果它介于`0`和`6`之间,则 `day` 绑定到一个字符串,该字符串表示一周中的某一天。 +否则,捕获所有情况,这些情况用 `_` 字符表示,这样 `day` 绑定到字符串 `"invalid day"`。 + +> 在编写像这样的简单 `match` 表达式时,建议在变量 `i` 上使用 `@switch` 注释。 +> 如果开关无法编译为 `tableswitch` 或 `lookupswitch`,则此注释会提供编译时警告,这个开关对性能更好。 + +### 使用缺省值 + +当您需要访问 `match` 表达式中匹配所有情况,也就是默认值时,只需在 `case` 语句的左侧提供一个变量名而不是 `_`,然后根据需要在语句的右侧使用该变量名称: + +{% tabs control-structures-23 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-23 %} + +```scala +i match { + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-23 %} + +```scala +i match + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what" ) +``` + +{% endtab %} +{% endtabs %} + +在模式中使用的名称必须以小写字母开头。 +以大写字母开头的名称并不引入变量,而是匹配该范围内的一个值: + +{% tabs control-structures-24 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-24 %} + +```scala +val N = 42 +i match { + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-24 %} + +```scala +val N = 42 +i match + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +``` + +{% endtab %} +{% endtabs %} + +如果 `i` 等于`42`,则 `case N` 将匹配,然后打印字符串`"42"`。它不会到达默认分支。 + +### 在一行上处理多个可能的匹配项 + +如前所述,`match` 表达式具有许多功能。 +此示例演示如何在每个 `case` 语句中使用多个可能的模式匹配: + +{% tabs control-structures-25 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-25 %} + +```scala +val evenOrOdd = i match { + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-25 %} + +```scala +val evenOrOdd = i match + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +``` + +{% endtab %} +{% endtabs %} + +### 在 `case` 子句中使用 `if` 守卫 + +您还可以在匹配表达式的 `case` 中使用守卫装置。 +在此示例中,第二个和第三个 `case` 都使用守卫来匹配多个整数值: + +{% tabs control-structures-26 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-26 %} + +```scala +i match { + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-26 %} + +```scala +i match + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +``` + +{% endtab %} +{% endtabs %} + +下面是另一个示例,它显示了如何将给定值与数字范围进行匹配: + +{% tabs control-structures-27 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-27 %} + +```scala +i match { + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-27 %} + +```scala +i match + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +``` + +{% endtab %} +{% endtabs %} + +#### 样例类和 match 表达式 + +您还可以从 `case` 类中提取字段 —— 以及正确编写了 `apply`/`unapply` 方法的类 —— 并在守卫条件下使用这些字段。 +下面是一个使用简单 `Person` 案例类的示例: + +{% tabs control-structures-28 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-28 %} + +```scala +case class Person(name: String) + +def speak(p: Person) = p match { + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") +} + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-28 %} + +```scala +case class Person(name: String) + +def speak(p: Person) = p match + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` + +{% endtab %} +{% endtabs %} + +### 使用 `match` 表达式作为方法的主体 + +由于 `match` 表达式返回一个值,因此它们可以用作方法的主体。 +此方法采用 `Matchable` 值作为输入参数,并根据 `match` 表达式的结果返回 `Boolean`: + +{% tabs control-structures-29 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-29 %} + +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" | false => false + case _ => true +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-29 %} + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` + +{% endtab %} +{% endtabs %} + +输入参数 `a` 被定义为 [`Matchable`类型][matchable]---这是可以对其执行模式匹配的所有Scala类型的根。 +该方法通过在输入上进行匹配来实现,提供两种情况: +第一个检查给定值是整数`0`,空字符串还是 `false`,在这种情况下返回 `false`。 +在默认情况下,我们为任何其他值返回 `true`。 +以下示例演示此方法的工作原理: + +{% tabs is-truthy-call %} +{% tab 'Scala 2 and 3' for=is-truthy-call %} + +```scala +isTruthy(0) // false +isTruthy(false) // false +isTruthy("") // false +isTruthy(1) // true +isTruthy(" ") // true +isTruthy(2F) // true +``` + +{% endtab %} +{% endtabs %} + +使用 `match` 表达式作为方法的主体是一种非常常见的用法。 + +#### 匹配表达式支持许多不同类型的模式 + +有许多不同形式的模式可用于编写 `match` 表达式。 +示例包括: + +- 常量模式(如 `case 3 => `) +- 序列模式(如 `case List(els : _*) =>`) +- 元组模式(如 `case (x, y) =>`) +- 构造函数模式(如 `case Person(first, last) =>`) +- 类型测试模式(如 `case p: Person =>`) + +所有这些类型的模式匹配都展示在以下 `pattern` 方法中,该方法采用类型为 `Matchable` 的输入参数并返回 `String` : + +{% tabs control-structures-30 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-30 %} + +```scala +def pattern(x: Matchable): String = x match { + + // constant patterns + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // sequence patterns + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // tuple patterns + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // constructor patterns + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // type test patterns + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // the default wildcard pattern + case _ => "Unknown" +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-30 %} + +```scala +def pattern(x: Matchable): String = x match + + // constant patterns + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // sequence patterns + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // tuple patterns + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // constructor patterns + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // type test patterns + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // the default wildcard pattern + case _ => "Unknown" +``` + +{% endtab %} +{% endtabs %} + +{% comment %} +TODO: Add in the new Scala 3 syntax shown on this page: +http://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html +{% endcomment %} + +## try/catch/finally + +与Java一样,Scala也有一个 `try`/`catch`/`finally` 结构,让你可以捕获和管理异常。 +为了保持一致性,Scala使用与 `match` 表达式相同的语法,并支持在可能发生的不同可能的异常上进行模式匹配。 + +在下面的示例中,`openAndReadAFile` 是一个执行其名称含义的方法:它打开一个文件并读取其中的文本,将结果分配给可变变量 `text` : + +{% tabs control-structures-31 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-31 %} + +```scala +var text = "" +try { + text = openAndReadAFile(filename) +} catch { + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +} finally { + // close your resources here + println("Came to the 'finally' clause.") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=control-structures-31 %} + +```scala +var text = "" +try + text = openAndReadAFile(filename) +catch + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +finally + // close your resources here + println("Came to the 'finally' clause.") +``` + +{% endtab %} +{% endtabs %} + +假设 `openAndReadAFile` 方法使用 Java `java.io.*` 类来读取文件并且不捕获其异常,则尝试打开和读取文件可能会导致 `FileNotFoundException` 和 `IOException` 异常,本例中,这两个异常在 `catch` 块中被捕获。 + +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md new file mode 100644 index 0000000000..46797dfc7e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -0,0 +1,817 @@ +--- +title: 函数式领域建模 +type: section +description: This chapter provides an introduction to FP domain modeling with Scala 3. +language: zh-cn +num: 23 +previous-page: domain-modeling-oop +next-page: methods-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章介绍了在 Scala 3 中使用函数式编程 (FP) 进行领域建模。 +当使用 FP 对我们周围的世界进行建模时,您通常会使用以下 Scala 构造: + +- 枚举 +- 样例类 +- Traits + +> 如果您不熟悉代数数据类型 (ADT) 及其泛型版本 (GADT),您可能需要先阅读 [代数数据类型][adts] 部分,然后再阅读本节。 + +## 介绍 + +在 FP 中,*数据*和*对该数据的操作*是两个独立的东西;您不必像使用 OOP 那样将它们封装在一起。 + +这个概念类似于数值代数。 +当您考虑值大于或等于零的整数时,您有一*组*可能的值,如下所示: + +```` +0, 1, 2 ... Int.MaxValue +```` + +忽略整数的除法,对这些值可能的*操作*是: + +```` ++, -, * +```` + +FP设计以类似的方式实现: + +- 你描述你的值的集合(你的数据) +- 您描述了对这些值起作用的操作(您的函数) + +> 正如我们将看到的,这种风格的程序推理与面向对象的编程完全不同。 +> FP 中的数据只**是**: +> 将功能与数据分离,让您无需担心行为即可检查数据。 + +在本章中,我们将为披萨店中的“披萨”建模数据和操作。 +您将看到如何实现 Scala/FP 模型的“数据”部分,然后您将看到几种不同的方式来组织对该数据的操作。 + +## 数据建模 + +在 Scala 中,描述编程问题的数据模型很简单: + +- 如果您想使用不同的替代方案对数据进行建模,请使用 `enum` 结构,(或者在 Scala 2 中用 `case object`)。 +- 如果您只想对事物进行分组(或需要更细粒度的控制),请使用 样例类 + +### 描述替代方案 + +简单地由不同的选择组成的数据,如面饼大小、面饼类型和馅料,在 Scala 中使用枚举进行简洁的建模: + +{% tabs data_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_1 %} + +在 Scala 2 中,一个 `sealed class` 和若干个继承自该类的 `case object` 组合在一起来表示枚举: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=data_1 %} + +在 Scala 3 中,用 `enum` 结构简洁地表示: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +> 描述不同选择的数据类型(如 `CrustSize`)有时也称为_归纳类型_。 + +### 描述复合数据 + +可以将披萨饼视为上述不同属性的_组件_容器。 +我们可以使用 样例类来描述 `Pizza` 由 `crustSize`、`crustType` 和可能的多个 `Topping` 组成: + +{% tabs data_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_2 %} + +```scala +import CrustSize._ +import CrustType._ +import Topping._ + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +{% endtab %} +{% tab 'Scala 3' for=data_2 %} + +```scala +import CrustSize.* +import CrustType.* +import Topping.* + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +{% endtab %} +{% endtabs %} + +> 聚合多个组件的数据类型(如`Pizza`)有时也称为_乘积类型_。 + +就是这样。 +这就是 FP 式披萨系统的数据模型。 +该解决方案非常简洁,因为它不需要将披萨饼上的操作与数据模型相结合。 +数据模型易于阅读,就像声明关系数据库的设计一样。 +创建数据模型的值并检查它们也很容易: + +{% tabs data_3 %} +{% tab 'Scala 2 and 3' for=data_3 %} + +```scala +val myFavPizza = Pizza(Small, Regular, Seq(Cheese, Pepperoni)) +println(myFavPizza.crustType) // prints Regular +``` + +{% endtab %} +{% endtabs %} + +#### 更多数据模型 + +我们可能会以同样的方式对整个披萨订购系统进行建模。 +下面是一些用于对此类系统建模的其他 样例类: + +{% tabs data_4 %} +{% tab 'Scala 2 and 3' for=data_4 %} + +```scala +case class Address( + street1: String, + street2: Option[String], + city: String, + state: String, + zipCode: String +) + +case class Customer( + name: String, + phone: String, + address: Address +) + +case class Order( + pizzas: Seq[Pizza], + customer: Customer +) +``` + +{% endtab %} +{% endtabs %} + +#### “瘦领域对象(贫血模型)” + +Debasish Ghosh 在他的《*函数式和反应式领域建模*》一书中指出,OOP 从业者将他们的类描述为封装数据和行为的“富领域模型(充血模型)”,而 FP 数据模型可以被认为是“瘦领域对象”。 +这是因为——正如本课所示——数据模型被定义为具有属性但没有行为的样例类,从而产生了简短而简洁的数据结构。 + +## 操作建模 + +这就引出了一个有趣的问题:因为 FP 将数据与对该数据的操作分开,那么如何在 Scala 中实现这些操作? + +答案实际上很简单:您只需编写对我们刚刚建模的数据值进行操作的函数(或方法)。 +例如,我们可以定义一个计算披萨价格的函数。 + +{% tabs data_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_5 %} + +```scala +def pizzaPrice(p: Pizza): Double = p match { + case Pizza(crustSize, crustType, toppings) => { + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=data_5 %} + +```scala +def pizzaPrice(p: Pizza): Double = p match + case Pizza(crustSize, crustType, toppings) => + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops +``` + +{% endtab %} +{% endtabs %} + +您注意到函数的实现如何简单地遵循数据的样式:由于 `Pizza` 是一个样例类,我们使用模式匹配来提取组件并调用辅助函数来计算各个部分单独的价格。 + +{% tabs data_6 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_6 %} + +```scala +def toppingPrice(t: Topping): Double = t match { + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +} +``` + +{% endtab %} +{% tab 'Scala 3' for=data_6 %} + +```scala +def toppingPrice(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` + +{% endtab %} +{% endtabs %} + +同样,由于 `Topping` 是一个枚举,我们使用模式匹配来区分不同的变量。 +奶酪和洋葱的价格为 50ct,其余的价格为 75ct。 + +{% tabs data_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=data_7 %} + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match { + // if the crust size is small or medium, + // the type is not important + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 + } +``` + +{% endtab %} +{% tab 'Scala 3' for=data_7 %} + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match + // if the crust size is small or medium, + // the type is not important + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 +``` + +{% endtab %} +{% endtabs %} + +为了计算面饼的价格,我们同时对面饼的大小和类型进行模式匹配。 + +> 关于上面显示的所有函数的重要一点是它们是*纯函数*:它们不会改变任何数据或有其他副作用(如抛出异常或写入文件)。 +> 他们所做的只是简单地接收值并计算结果。 + +{% comment %} +I’ve added this comment per [this Github comment](https://github.com/scalacenter/docs.scala-lang/pull/3#discussion_r543372428). +To that point, I’ve added these definitions here from our Slack conversation, in case anyone wants to update the “pure function” definition. If not, please delete this comment. + +Sébastien: +---------- +A function `f` is pure if, given the same input `x`, it will always return the same output `f(x)`, and it never modifies any state outside of it (therefore potentially causing other functions to behave differently in the future). + +Jonathan: +--------- +We say a function is 'pure' if it does not depend on or modify the context it is called in. + +Wikipedia +--------- +The function always evaluates to the same result value given the same argument value(s). It cannot depend on any hidden state or value, and it cannot depend on any I/O. +Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices. + +Mine (Alvin, now modified, from fp-pure-functions.md): +------------------------------------------------------ +- A function `f` is pure if, given the same input `x`, it always returns the same output `f(x)` +- The function’s output depends *only* on its input variables and its internal algorithm +- It doesn’t modify its input parameters +- It doesn’t mutate any hidden state +- It doesn’t have any “back doors”: It doesn’t read data from the outside world (including the console, web services, databases, files, etc.), or write data to the outside world +{% endcomment %} + +## 如何组织功能 + +在实现上面的 `pizzaPrice` 函数时,我们没有说我们将在*哪里*定义它。 +在 Scala 3 中,在文件的顶层定义它是完全有效的。 +但是,该语言为我们提供了许多很棒的工具在不同命名空间和模块中组织我们的逻辑。 + +有几种不同的方式来实现和组织行为: + +- 在伴生对象中定义您的函数 +- 使用模块化编程风格 +- 使用“函数式对象”方法 +- 在扩展方法中定义功能 + +在本节的其余部分将展示这些不同的解决方案。 + +### 伴生对象 + +第一种方法是在伴生对象中定义行为——函数。 + +> 正如在领域建模 [工具部分][modeling-tools] 中所讨论的,_伴生对象_ 是一个与类同名的 `object` ,并在与类相同的文件中声明。 + +使用这种方法,除了枚举或样例类之外,您还定义了一个包含该行为的同名伴生对象。 + +{% tabs org_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=org_1 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// the companion object of case class Pizza +object Pizza { + // the implementation of `pizzaPrice` from above + def price(p: Pizza): Double = ... +} + +sealed abstract class Topping + +// the companion object of enumeration Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping + + // the implementation of `toppingPrice` above + def price(t: Topping): Double = ... +} +``` + +{% endtab %} +{% tab 'Scala 3' for=org_1 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// the companion object of case class Pizza +object Pizza: + // the implementation of `pizzaPrice` from above + def price(p: Pizza): Double = ... + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions + +// the companion object of enumeration Topping +object Topping: + // the implementation of `toppingPrice` above + def price(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` + +{% endtab %} +{% endtabs %} + +使用这种方法,您可以创建一个 `Pizza` 并计算其价格,如下所示: + +{% tabs org_2 %} +{% tab 'Scala 2 and 3' for=org_2 %} + +```scala +val pizza1 = Pizza(Small, Thin, Seq(Cheese, Onions)) +Pizza.price(pizza1) +``` + +{% endtab %} +{% endtabs %} + +以这种方式对功能进行分组有几个优点: + +- 它将功能与数据相关联,让程序员(和编译器)更容易找到它。 +- 它创建了一个命名空间,例如让我们使用 `price` 作为方法名称,而不必依赖重载。 +- `Topping.price` 的实现可以访问枚举值,例如 `Cheese` ,而无需导入它们。 + +但是,还应权衡: + +- 它将功能与您的数据模型紧密结合。 + 特别是,伴生对象需要在与您的样例类相同的文件中定义。 +- 可能不清楚在哪里定义像 `crustPrice` 这样同样可以放置在 `CrustSize` 或 `CrustType` 的伴生对象中的函数。 + +## 模块 + +组织行为的第二种方法是使用“模块化”方法。 +这本书,*Programming in Scala*,将 *模块* 定义为“具有良好定义的接口和隐藏实现的‘较小的程序片段’”。 +让我们看看这意味着什么。 + +### 创建一个 `PizzaService` 接口 + +首先要考虑的是 `Pizza` 的“行为”。 +执行此操作时,您可以像这样草拟一个 `PizzaServiceInterface` trait: + +{% tabs module_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_1 %} + +```scala +trait PizzaServiceInterface { + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +} +``` + +{% endtab %} +{% tab 'Scala 3' for=module_1 %} + +```scala +trait PizzaServiceInterface: + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +``` + +{% endtab %} +{% endtabs %} + +如图所示,每个方法都将 `Pizza` 作为输入参数——连同其他参数——然后返回一个 `Pizza` 实例作为结果 + +当你写一个像这样的纯接口时,你可以把它想象成一个约定,“所有扩展这个特性的非抽象类*必须*提供这些服务的实现。” + +此时您还可以做的是想象您是此 API 的使用者。 +当你这样做时,它有助于草拟一些示例“消费者”代码,以确保 API 看起来像你想要的: + +{% tabs module_2 %} +{% tab 'Scala 2 and 3' for=module_2 %} + +```scala +val p = Pizza(Small, Thin, Seq(Cheese)) + +// how you want to use the methods in PizzaServiceInterface +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) +``` + +{% endtab %} +{% endtabs %} + +如果该代码看起来没问题,您通常会开始草拟另一个 API ——例如用于订单的 API ——但由于我们现在只关注披萨饼,我们将停止考虑接口,然后创建这个接口的具体实现。 + +> 请注意,这通常是一个两步过程。 +> 在第一步中,您将 API 的合同草拟为*接口*。 +> 在第二步中,您创建该接口的具体*实现*。 +> 在某些情况下,您最终会创建基本接口的多个具体实现。 + +### 创建一个具体的实现 + +现在您知道了 `PizzaServiceInterface` 的样子,您可以通过为接口中定义的所有方法体来创建它的具体实现: + +{% tabs module_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_3 %} + +```scala +object PizzaService extends PizzaServiceInterface { + + def price(p: Pizza): Double = + ... // implementation from above + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=module_3 %} + +```scala +object PizzaService extends PizzaServiceInterface: + + def price(p: Pizza): Double = + ... // implementation from above + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) + +end PizzaService +``` + +{% endtab %} +{% endtabs %} + +虽然创建接口和实现的两步过程并不总是必要的,但明确考虑 API 及其使用是一种好方法。 + +一切就绪后,您可以使用 `Pizza` 类和 `PizzaService`: + +{% tabs module_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_4 %} + +```scala +import PizzaService._ + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// use the PizzaService methods +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // prints 8.75 +``` + +{% endtab %} +{% tab 'Scala 3' for=module_4 %} + +```scala +import PizzaService.* + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// use the PizzaService methods +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // prints 8.75 +``` + +{% endtab %} +{% endtabs %} + +### 函数对象 + +在 *Programming in Scala* 一书中,作者将术语“函数对象”定义为“不具有任何可变状态的对象”。 +`scala.collection.immutable` 中的类型也是如此。 +例如,`List` 上的方法不会改变内部状态,而是创建 `List` 的副本作为结果。 + +您可以将此方法视为“混合 FP/OOP 设计”,因为您: + +- 使用不可变的 样例类对数据进行建模。 +- 定义_同类型_数据中的行为(方法)。 +- 将行为实现为纯函数:它们不会改变任何内部状态;相反,他们返回一个副本。 + +> 这确实是一种混合方法:就像在 **OOP 设计**中一样,方法与数据一起封装在类中, +> 但作为典型的 **FP 设计**,方法被实现为纯函数,该函数不改变数据 + +#### 例子 + +使用这种方法,您可以在样例类中直接实现披萨上的功能: + +{% tabs module_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_5 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) { + + // the operations on the data model + def price: Double = + pizzaPrice(this) // implementation from above + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +} +``` + +{% endtab %} +{% tab 'Scala 3' for=module_5 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +): + + // the operations on the data model + def price: Double = + pizzaPrice(this) // implementation from above + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +``` + +{% endtab %} +{% endtabs %} + +请注意,与之前的方法不同,因为这些是 `Pizza` 类上的方法,它们不会将 `Pizza` 引用作为输入参数。 +相反,他们用 `this` 作为当前披萨实例的引用。 + +现在你可以像这样使用这个新设计: + +{% tabs module_6 %} +{% tab 'Scala 2 and 3' for=module_6 %} + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +{% endtab %} +{% endtabs %} + +### 扩展方法 + +最后,我们展示了一种介于第一个(在伴生对象中定义函数)和最后一个(将函数定义为类型本身的方法)之间的方法。 + +扩展方法让我们创建一个类似于函数对象的 API,而不必将函数定义为类型本身的方法。 +这可以有多个优点: + +- 我们的数据模型再次_非常简洁_并且没有提及任何行为。 +- 我们可以_追溯性地_为类型配备额外的方法,而无需更改原始定义。 +- 除了伴生对象或类型上的直接方法外,扩展方法可以在_外部_另一个文件中定义。 + +让我们再次回顾一下我们的例子。 + +{% tabs module_7 class=tabs-scala-version %} +{% tab 'Scala 2' for=module_7 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +implicit class PizzaOps(p: Pizza) { + def price: Double = + pizzaPrice(p) // implementation from above + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +} +``` +在上面的代码中,我们将披萨上的不同方法定义为_implicit class_。 +用 `implicit class PizzaOps(p: Pizza)`,不管什么时候导入 `PizzaOps`,它的方法在 `Pizza` 的实例上都是可用的。 +在本例中接收者是 `p`。 + +{% endtab %} +{% tab 'Scala 3' for=module_7 %} + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +extension (p: Pizza) + def price: Double = + pizzaPrice(p) // implementation from above + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +``` +在上面的代码中,我们将披萨上的不同方法定义为_扩展方法_。 +对于 `extension (p: Pizza)`,我们想在 `Pizza` 的实例上让方法可用。在本例中接收者是 `p`。 + +{% endtab %} +{% endtabs %} + +使用扩展方法,我们可以获得和之前一样的 API: + +{% tabs module_8 %} +{% tab 'Scala 2 and 3' for=module_8 %} + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +{% endtab %} +{% endtabs %} + +通常,如果您是数据模型的设计者,您将在伴生对象中定义您的扩展方法。 +这样,它们已经可供所有用户使用。 +否则,扩展方法需要显式导入才能使用。 + +## 这种方法的总结 + +在 Scala/FP 中定义数据模型往往很简单:只需使用枚举对数据的变体进行建模,并使用 样例类对复合数据进行建模。 +然后,为了对行为建模,定义对数据模型的值进行操作的函数。 +我们已经看到了组织函数的不同方法: + +- 你可以把你的方法放在伴生对象中 +- 您可以使用模块化编程风格,分离接口和实现 +- 您可以使用“函数对象”方法并将方法存储在定义的数据类型上 +- 您可以使用扩展方法把函数装配到数据模型上 + +[adts]: {% link _zh-cn/overviews/scala3-book/types-adts-gadts.md %} +[modeling-tools]: {% link _zh-cn/overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-intro.md b/_zh-cn/overviews/scala3-book/domain-modeling-intro.md new file mode 100644 index 0000000000..f2d91f71a3 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-intro.md @@ -0,0 +1,18 @@ +--- +title: 领域建模 +description: This chapter provides an introduction to domain modeling in Scala 3. +num: 20 +previous-page: control-structures +next-page: domain-modeling-tools + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +本章介绍如何使用 Scala 3 对周围的世界进行建模: + +- 工具部分介绍了可供您使用的工具,包括类、traits 、枚举等 +- OOP 建模部分着眼于面向对象编程 (OOP) 风格中的建模属性和行为 +- FP 建模部分以函数式编程 (FP) 风格来看领域建模 diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md new file mode 100644 index 0000000000..0c71a3f65c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -0,0 +1,588 @@ +--- +title: OOP 领域建模 +type: section +description: This chapter provides an introduction to OOP domain modeling with Scala 3. +language: zh-cn +num: 22 +previous-page: domain-modeling-tools +next-page: domain-modeling-fp + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章介绍了在 Scala 3 中使用面向对象编程 (OOP) 进行领域建模。 + +## 介绍 + +Scala 为面向对象设计提供了所有必要的工具: + +- **Traits** 让您指定(抽象)接口以及具体实现。 +- **Mixin Composition** 为您提供了从较小的部分组成组件的工具。 +- **类**可以实现trait指定的接口。 +- 类的**实例**可以有自己的私有状态。 +- **Subtyping** 允许您在需要超类实例的地方使用一个类的实例。 +- **访问修饰符**允许您控制类的哪些成员可以被代码的哪个部分访问。 + +## Traits + +可能与支持 OOP 的其他语言(例如 Java)不同,Scala 中分解的主要工具不是类,而是trait。 +它们可以用来描述抽象接口,例如: + +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait Showable: + def show: String +``` + +{% endtab %} +{% endtabs %} + +并且还可以包含具体的实现: + +{% tabs traits_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Showable { + def show: String + def showHtml = "

" + show + "

" +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait Showable: + def show: String + def showHtml = "

" + show + "

" +``` + +{% endtab %} +{% endtabs %} + +你可以看到我们用抽象方法 `show` 来定义方法 `showHtml`。 + +[Odersky and Zenger][scalable] 展示了 _面向服务的组件模型_ 和视图: + +- **抽象成员**作为_必须_服务:它们仍然需要由子类实现。 +- **具体成员**作为_提供_服务:它们被提供给子类。 + +我们已经可以在 `Showable` 的示例中看到这一点:定义一个扩展 `Showable` 的类 `Document`,我们仍然必须定义 `show`,但我们提供了 `showHtml`: + +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Document(text: String) extends Showable { + def show = text +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Document(text: String) extends Showable: + def show = text +``` + +{% endtab %} +{% endtabs %} + +#### 抽象成员 + +抽象方法并不是trait中唯一可以抽象的东西。 +一个trait可以包含: + +- 抽象方法(`def m(): T`) +- 抽象值定义(`val x: T`) +- 抽象类型成员(`type T`),可能有界限(`type T <: S`) +- 抽象given(`given t: T`) +Scala 3 only + +上述每个特性都可用于指定对 trait 实现者的某种形式的要求。 + +## 混入组合 + +traits 不仅可以包含抽象和具体的定义,Scala 还提供了一种组合多个 trait 的强大方法:这个特性通常被称为 _混入组合_。 + +让我们假设以下两个(可能独立定义的)traits: + +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait GreetingService { + def translate(text: String): String + def sayHello = translate("Hello") +} + +trait TranslationService { + def translate(text: String): String = "..." +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait GreetingService: + def translate(text: String): String + def sayHello = translate("Hello") + +trait TranslationService: + def translate(text: String): String = "..." +``` + +{% endtab %} +{% endtabs %} + +要组合这两个服务,我们可以简单地创建一个扩展它们的新trait: + +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait ComposedService extends GreetingService with TranslationService +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait ComposedService extends GreetingService, TranslationService +``` + +{% endtab %} +{% endtabs %} + +一个 trait 中的抽象成员(例如 `GreetingService` 中的 `translate`)会自动与另一个 trait 中的具体成员匹配。 +这不仅适用于本例中的方法,而且适用于上述所有其他抽象成员(即类型、值定义等)。 + +## 类 + +Traits 非常适合模块化组件和描述接口(必需和提供)。 +但在某些时候,我们会想要创建它们的实例。 +在 Scala 中设计软件时,只考虑在继承模型的叶子中使用类通常很有帮助: + +{% comment %} +NOTE: I think “leaves” may technically be the correct word to use, but I prefer “leafs.” +{% endcomment %} + +{% tabs table-traits-cls-summary class=tabs-scala-version %} +{% tab 'Scala 2' %} +| Traits | `T1`, `T2`, `T3` +| 组合traits | `S1 extends T1 with T2`, `S2 extends T2 with T3` +| 类 | `C extends S1 with T3` +| 实例 | `new C()` +{% endtab %} +{% tab 'Scala 3' %} +| Traits | `T1`, `T2`, `T3` +| 组合 traits | `S extends T1, T2`, `S extends T2, T3` +| 类 | `C extends S, T3` +| 实例 | `C()` +{% endtab %} +{% endtabs %} + +在 Scala 3 中更是如此,trait 现在也可以接受参数,进一步消除了对类的需求。 + +#### 定义类 + +像trait一样,类可以扩展多个trait(但只有一个超类): + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class MyService(name: String) extends ComposedService with Showable { + def show = s"$name says $sayHello" +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class MyService(name: String) extends ComposedService, Showable: + def show = s"$name says $sayHello" +``` + +{% endtab %} +{% endtabs %} + +#### 子类型化 + +我们可以创建一个 `MyService` 的实例,如下所示: + +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1: MyService = new MyService("Service 1") +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val s1: MyService = MyService("Service 1") +``` + +{% endtab %} +{% endtabs %} + +通过子类型化的方式,我们的实例 `s1` 可以在任何扩展了trait的地方使用: + +{% tabs class_3 %} +{% tab 'Scala 2 and 3' %} + +```scala +val s2: GreetingService = s1 +val s3: TranslationService = s1 +val s4: Showable = s1 +// ... and so on ... +``` + +{% endtab %} +{% endtabs %} + +#### 扩展规划 + +如前所述,可以扩展另一个类: + +{% tabs class_4 %} +{% tab 'Scala 2 and 3' %} + +```scala +class Person(name: String) +class SoftwareDeveloper(name: String, favoriteLang: String) + extends Person(name) +``` + +{% endtab %} +{% endtabs %} + +然而,由于 _traits_ 被设计为主要的分解手段,在一个文件中定义的类_不能_在另一个文件中扩展。 +为了允许这样做,需要将基类标记为 `open`: + +{% tabs class_5 %} +{% tab 'Scala 3 Only' %} + +```scala +open class Person(name: String) +``` + +{% endtab %} +{% endtabs %} + +用 [`open`][open] 标记类是 Scala 3 的一个新特性。必须将类显式标记为开放可以避免面向对象设计中的许多常见缺陷。 +特别是,它要求库设计者明确计划扩展,例如用额外的扩展契约来记录那些被标记为开放的类。 + +{% comment %} +NOTE/FWIW: In his book, “Effective Java,” Joshua Bloch describes this as “Item 19: Design and document for inheritance or else prohibit it.” +Unfortunately I can’t find any good links to this on the internet. +I only mention this because I think that book and phrase is pretty well known in the Java world. +{% endcomment %} + +## 实例和私有可变状态 + +与其他支持 OOP 的语言一样,Scala 中的trait和类可以定义可变字段: + +{% tabs instance_6 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Counter { + // can only be observed by the method `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Counter: + // can only be observed by the method `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +``` + +{% endtab %} +{% endtabs %} + +`Counter` 类的每个实例都有自己的私有状态,只能通过方法 `count` 观察到,如下面的交互所示: + +{% tabs instance_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val c1 = new Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val c1 = Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +{% endtab %} +{% endtabs %} + +#### 访问修饰符 + +默认情况下,Scala 中的所有成员定义都是公开可见的。 +要隐藏实现细节,可以将成员(方法、字段、类型等)定义为 `private` 或 `protected`。 +通过这种方式,您可以控制访问或覆盖它们的方式。 +私有成员仅对类/trait本身及其伴生对象可见。 +受保护的成员对类的子类也是可见的。 + +## 高级示例:面向服务的设计 + +在下文中,我们展示了 Scala 的一些高级特性,并展示了如何使用它们来构建更大的软件组件。 +这些示例改编自 Martin Odersky 和 ​​Matthias Zenger 的论文 ["Scalable Component Abstractions"][scalable]。 +如果您不了解示例的所有细节,请不要担心;它的主要目的是演示如何使用多种类型特性来构造更大的组件。 + +我们的目标是定义一个_种类丰富_的软件组件,而对组件的细化,可以放到以后的实现中 +具体来说,以下代码将组件 `SubjectObserver` 定义为具有两个抽象类型成员的trait, `S` (用于主题)和 `O` (用于观察者): + +{% tabs example_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait SubjectObserver { + + type S <: Subject + type O <: Observer + + trait Subject { self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = { + observers = obs :: observers + } + def publish() = { + for ( obs <- observers ) obs.notify(this) + } + } + + trait Observer { + def notify(sub: S): Unit + } +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait SubjectObserver: + + type S <: Subject + type O <: Observer + + trait Subject: + self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = + observers = obs :: observers + def publish() = + for obs <- observers do obs.notify(this) + + trait Observer: + def notify(sub: S): Unit +``` + +{% endtab %} +{% endtabs %} + +有几件事需要解释。 + +#### 抽象类型成员 + +声明 `type S <: Subject` 表示在 trait `SubjectObserver` 中我们可以引用一些我们称为 `S` 的_未知_(即抽象)类型。 +然而,该类型并不是完全未知的:我们至少知道它是trait `Subject` 的_某个子类型_。 +只要选择的类型是 `Subject` 的子类型,所有扩展自 `SubjectObserver` 的trait和类都可以自由地用于 `S`的类型。 +声明的 `<: Subject` 部分也称为 _`S` 的上界_。 + +#### 嵌套trait + +在 trait `SubjectObserver` _内_,我们定义了另外两个traits。 +让我们从 trait `Observer` 开始,它只定义了一个抽象方法 `notify`,它接受一个类型为 `S` 的参数。 +正如我们稍后将看到的,重要的是参数的类型为 `S` 而不是 `Subject` 类型。 + +第二个trait,`Subject`,定义了一个私有字段`observers`来存储所有订阅这个特定主题的观察者。 +订阅主题只是将对象存储到此列表中。 +同样,参数 `obs` 的类型是 `O`,而不是 `Observer`。 + +#### 自类型注解 + +最后,你可能想知道 trait `Subject` 上的 `self: S =>` 应该是什么意思。 +这称为 _自类型注解_。 +它要求 `Subject` 的子类型也是 `S` 的子类型。 +这对于能够使用 `this` 作为参数调用 `obs.notify` 是必要的,因为它需要 `S` 类型的值。 +如果 `S` 是一个_具体_类型,自类型注解可以被 `trait Subject extends S` 代替。 + +### 实现组件 + +我们现在可以实现上述组件并将抽象类型成员定义为具体类型: + +{% tabs example_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object SensorReader extends SubjectObserver { + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject { + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = { + currentValue = v + publish() + } + } + + class Display extends Observer { + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") + } +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object SensorReader extends SubjectObserver: + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject: + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = + currentValue = v + publish() + + class Display extends Observer: + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") +``` + +{% endtab %} +{% endtabs %} + +具体来说,我们定义了一个扩展 `SubjectObserver` 的_单例_对象 `SensorReader`。 +在 `SensorReader` 的实现中,我们说 `S` 类型现在被定义为 `Sensor` 类型,`O` 类型被定义为等于 `Display` 类型。 +`Sensor` 和 `Display` 都被定义为 `SensorReader` 中的嵌套类,相应地实现了 `Subject` 和 `Observer` 特性。 + +除了作为面向服务设计的示例之外,这段代码还突出了面向对象编程的许多方面: + +- `Sensor` 类引入了它自己的私有状态(`currentValue`),并在方法`changeValue` 后面封装了对状态的修改。 +- `changeValue` 的实现使用扩展trait中定义的方法 `publish`。 +- 类 `Display` 扩展了 `Observer` 特性,并实现了缺失的方法 `notify`。 + +{% comment %} +NOTE: You might say “the abstract method `notify`” in that last sentence, but I like “missing.” +{% endcomment %} + +有一点很重要,需要指出,`notify` 的实现只能安全地访问 `sub` 的标签和值,因为我们最初将参数声明为 `S` 类型。 + +### 使用组件 + +最后,下面的代码说明了如何使用我们的 `SensorReader` 组件: + +{% tabs example_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import SensorReader._ + +// setting up a network +val s1 = new Sensor("sensor1") +val s2 = new Sensor("sensor2") +val d1 = new Display() +val d2 = new Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// propagating updates through the network +s1.changeValue(2) +s2.changeValue(3) + +// prints: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 + +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import SensorReader.* + +// setting up a network +val s1 = Sensor("sensor1") +val s2 = Sensor("sensor2") +val d1 = Display() +val d2 = Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// propagating updates through the network +s1.changeValue(2) +s2.changeValue(3) + +// prints: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 +``` + +{% endtab %} +{% endtabs %} + +借助我们掌握的所有面向对象的编程工具,在下一节中,我们将演示如何以函数式风格设计程序。 + +{% comment %} +NOTE: One thing I occasionally do is flip things like this around, so I first show how to use a component, and then show how to implement that component. I don’t have a rule of thumb about when to do this, but sometimes it’s motivational to see the use first, and then see how to create the code to make that work. +{% endcomment %} + +[scalable]: https://doi.org/10.1145/1094811.1094815 +[open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md new file mode 100644 index 0000000000..08aa972f0a --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md @@ -0,0 +1,1345 @@ +--- +title: 工具 +type: section +description: This chapter provides an introduction to the available domain modeling tools in Scala 3, including classes, traits, enums, and more. +language: zh-cn +num: 21 +previous-page: domain-modeling-intro +next-page: domain-modeling-oop + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +Scala 3提供了许多不同的结构,因此我们可以对周围的世界进行建模: + +- 类 +- 对象 +- 伴生对象 +- Traits +- 抽象类 +- 枚举 +Scala 3 独有 +- 样例类 +- 样例对象 + +本节简要介绍其中的每种语言功能。 + +## 类 + +与其他语言一样,Scala中的_类_是用于创建对象实例的模板。 +下面是一些类的示例: + +{% tabs class_1 %} +{% tab 'Scala 2 and 3' %} + +```scala +class Person(var name: String, var vocation: String) +class Book(var title: String, var author: String, var year: Int) +class Movie(var name: String, var director: String, var year: Int) +``` + +{% endtab %} +{% endtabs %} + +这些例子表明,Scala有一种非常轻量级的方式来声明类。 + +我们的示例类的所有参数都定义为 `var` 字段,这意味着它们是可变的:您可以读取它们,也可以修改它们。 +如果您希望它们是不可变的---仅读取---请改为将它们创建为 `val` 字段,或者使用样例类。 + +在Scala 3之前,您使用 `new` 关键字来创建类的新实例: + +{% tabs class_2 %} +{% tab 'Scala 2 Only' %} + +```scala +val p = new Person("Robert Allen Zimmerman", "Harmonica Player") +// --- +``` + +{% endtab %} +{% endtabs %} + +然而,通过[通用 apply 方法][creator],在 Scala 3 里面不要求使用 `new`: +Scala 3 独有 + +{% tabs class_3 %} +{% tab 'Scala 3 Only' %} + +```scala +val p = Person("Robert Allen Zimmerman", "Harmonica Player") +``` + +{% endtab %} +{% endtabs %} + +一旦你有了一个类的实例,比如 `p`,你就可以访问它的字段,在此示例中,这些字段都是构造函数的参数: + +{% tabs class_4 %} +{% tab 'Scala 2 and 3' %} + +```scala +p.name // "Robert Allen Zimmerman" +p.vocation // "Harmonica Player" +``` + +{% endtab %} +{% endtabs %} + +如前所述,所有这些参数都是作为 `var` 字段创建的,因此您也可以更改它们: + +{% tabs class_5 %} +{% tab 'Scala 2 and 3' %} + +```scala +p.name = "Bob Dylan" +p.vocation = "Musician" +``` + +{% endtab %} +{% endtabs %} + +### 字段和方法 + +类还可以具有不属于构造函数的方法和其他字段。 +它们在类的主体中定义。 +主体初始化为默认构造函数的一部分: + +{% tabs method class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person(var firstName: String, var lastName: String) { + + println("initialization begins") + val fullName = firstName + " " + lastName + + // a class method + def printFullName: Unit = + // access the `fullName` field, which is created above + println(fullName) + + printFullName + println("initialization ends") +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Person(var firstName: String, var lastName: String): + + println("initialization begins") + val fullName = firstName + " " + lastName + + // a class method + def printFullName: Unit = + // access the `fullName` field, which is created above + println(fullName) + + printFullName + println("initialization ends") +``` + +{% endtab %} +{% endtabs %} + +以下 REPL 会话演示如何使用这个类创建新的 `Person` 实例: + +{% tabs demo-person class=tabs-scala-version %} +{% tab 'Scala 2' %} +```` +scala> val john = new Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` +{% endtab %} +{% tab 'Scala 3' %} + +```` +scala> val john = Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` + +{% endtab %} +{% endtabs %} + +类还可以扩展 traits和抽象类,我们将在下面专门部分中介绍这些内容。 + +### 默认参数值 + +快速浏览一下其他功能,类构造函数参数也可以具有默认值: + +{% tabs default-values_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000) { + override def toString = s"timeout: $timeout, linger: $linger" +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000): + override def toString = s"timeout: $timeout, linger: $linger" +``` + +{% endtab %} +{% endtabs %} + +此功能的一大优点是,它允许代码的使用者以各种不同的方式创建类,就好像该类有别的构造函数一样: + +{% tabs default-values_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s = new Socket() // timeout: 5000, linger: 5000 +val s = new Socket(2_500) // timeout: 2500, linger: 5000 +val s = new Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = new Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = new Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val s = Socket() // timeout: 5000, linger: 5000 +val s = Socket(2_500) // timeout: 2500, linger: 5000 +val s = Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +{% endtab %} +{% endtabs %} + +创建类的新实例时,还可以使用命名参数。 +当许多参数具有相同的类型时,这特别有用,如以下比较所示: + +{% tabs default-values_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// option 1 +val s = new Socket(10_000, 10_000) + +// option 2 +val s = new Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// option 1 +val s = Socket(10_000, 10_000) + +// option 2 +val s = Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +{% endtab %} +{% endtabs %} + +### 辅助构造函数 + +可以为类定义多个构造函数,以便类的使用者用不同的方式来生成这个类。 +例如,假设您需要编写一些代码给大学招生系统中的学生进行建模。 +在分析需求时,您已经看到您需要能够以三种方式构建 `Student` 实例: + +- 当他们第一次开始招生过程时,带有姓名和政府 ID, +- 当他们提交申请时,带有姓名,政府 ID 和额外的申请日期 +- 在他们被录取后,带有姓名,政府 ID 和学生证 + +在 OOP 风格中处理这种情况的一种方法是使用以下代码: + +{% tabs structor_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import java.time._ + +// [1] the primary constructor +class Student( + var name: String, + var govtId: String +) { + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] a constructor for when the student has completed + // their application + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = { + this(name, govtId) + _applicationDate = Some(applicationDate) + } + + // [3] a constructor for when the student is approved + // and now has a student id + def this( + name: String, + govtId: String, + studentId: Int + ) = { + this(name, govtId) + _studentId = studentId + } +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import java.time.* + +// [1] the primary constructor +class Student( + var name: String, + var govtId: String +): + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] a constructor for when the student has completed + // their application + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = + this(name, govtId) + _applicationDate = Some(applicationDate) + + // [3] a constructor for when the student is approved + // and now has a student id + def this( + name: String, + govtId: String, + studentId: Int + ) = + this(name, govtId) + _studentId = studentId +``` + +{% endtab %} +{% endtabs %} + +{% comment %} +// for testing that code +override def toString = s""" +|Name: $name +|GovtId: $govtId +|StudentId: $_studentId +|Date Applied: $_applicationDate +""".trim.stripMargin +{% endcomment %} + +该类有三个构造函数,由代码中编号的注释给出: + +1. 主构造函数,由类定义中的 `name` 和 `govtId` 给出 +2. 具有参数 `name` 、 `govtId` 和 `applicationDate` 的辅助构造函数 +3. 另一个带有参数 `name` 、 `govtId` 和 `studentId` 的辅助构造函数 + +这些构造函数可以这样调用: + +{% tabs structor_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s1 = new Student("Mary", "123") +val s2 = new Student("Mary", "123", LocalDate.now) +val s3 = new Student("Mary", "123", 456) +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +val s1 = Student("Mary", "123") +val s2 = Student("Mary", "123", LocalDate.now) +val s3 = Student("Mary", "123", 456) +``` + +{% endtab %} +{% endtabs %} + +虽然可以使用此技术,但请记住,构造函数参数也可以具有默认值,这使得一个类看起来具有多个构造函数。 +这在前面的 `Socket` 示例中所示。 + +## 对象 + +对象是一个正好有一个实例的类。 +当其成员是引用类时,它会延迟初始化,类似于 `lazy val` 。 +Scala 中的对象允许在一个命名空间下对方法和字段进行分组,类似于我们在 Java,Javascript(ES6)中使用 `static` 方法或在 Python 中使用 `@staticmethod` 方法。 + +声明 `object` 类似于声明 `class` 。 +下面是一个“字符串实用程序”对象的示例,其中包含一组用于处理字符串的方法: + +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object StringUtils { + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object StringUtils: + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +``` + +{% endtab %} +{% endtabs %} + +我们可以这样使用对象: + +{% tabs object_2 %} +{% tab 'Scala 2 and 3' %} + +```scala +StringUtils.truncate("Chuck Bartowski", 5) // "Chuck" +``` + +{% endtab %} +{% endtabs %} + +在 Scala 中导入非常灵活,并允许我们导入对象的_所有_ 成员: + +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import StringUtils._ +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import StringUtils.* +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +{% endtab %} +{% endtabs %} + +或者只是 _部分_ 成员: + +{% tabs object_4 %} +{% tab 'Scala 2 and 3' %} + +```scala +import StringUtils.{truncate, containsWhitespace} +truncate("Charles Carmichael", 7) // "Charles" +containsWhitespace("Captain Awesome") // true +isNullOrEmpty("Morgan Grimes") // Not found: isNullOrEmpty (error) +``` + +{% endtab %} +{% endtabs %} + +对象还可以包含字段,这些字段也可以像静态成员一样访问: + +{% tabs object_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +object MathConstants { + val PI = 3.14159 + val E = 2.71828 +} + +println(MathConstants.PI) // 3.14159 +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object MathConstants: + val PI = 3.14159 + val E = 2.71828 + +println(MathConstants.PI) // 3.14159 +``` + +{% endtab %} +{% endtabs %} + +## 伴生对象 + +与类同名且在与类在相同的文件中声明的 `object` 称为_“伴生对象”_。 +同样,相应的类称为对象的伴生类。 +伴生类或对象可以访问其伴生的私有成员。 + +伴生对象用于不特定于伴生类实例的方法和值。 +例如,在下面的示例中,类 `Circle` 具有一个名为 `area` 的成员,该成员特定于每个实例,其伴生对象具有一个名为 `calculateArea` 的方法,该方法(a)不特定于实例,并且(b)可用于每个实例: + +{% tabs companion class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.math._ + +class Circle(val radius: Double) { + def area: Double = Circle.calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import scala.math.* + +case class Circle(radius: Double): + def area: Double = Circle.calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area +``` + +{% endtab %} +{% endtabs %} + +在此示例中,每个实例可用的 `area` 方法使用伴生对象中定义的 `calculateArea` 方法。 +再一次, `calculateArea` 类似于Java中的静态方法。 +此外,由于 `calculateArea` 是私有的,因此其他代码无法访问它,但如图所示,它可以被 `Circle` 类的实例看到。 + +### 其他用途 + +伴生对象可用于多种用途: + +- 如图所示,它们可用于将“静态”方法分组到命名空间下 + - 这些方法可以是公共的,也可以是私有的 + - 如果 `calculateArea` 是公开的,它将被访问为 `Circle.calculateArea` +- 它们可以包含 `apply` 方法,这些方法---感谢一些语法糖---作为工厂方法来构建新实例 +- 它们可以包含 `unapply` 方法,用于解构对象,例如模式匹配 + +下面快速了解如何将 `apply` 方法当作工厂方法来创建新对象: + +{% tabs companion-use class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person { + var name = "" + var age = 0 + override def toString = s"$name is $age years old" +} + +object Person { + // a one-arg factory method + def apply(name: String): Person = { + var p = new Person + p.name = name + p + } + + // a two-arg factory method + def apply(name: String, age: Int): Person = { + var p = new Person + p.name = name + p.age = age + p + } +} + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +此处不涉及 `unapply` 方法,但在[语言规范](https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns)中对此进行了介绍。 + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Person: + var name = "" + var age = 0 + override def toString = s"$name is $age years old" + +object Person: + + // a one-arg factory method + def apply(name: String): Person = + var p = new Person + p.name = name + p + + // a two-arg factory method + def apply(name: String, age: Int): Person = + var p = new Person + p.name = name + p.age = age + p + +end Person + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +{% endtab %} +{% endtabs %} + +此处不涉及 `unapply` 方法,但在 [参考文档][unapply] 中对此进行了介绍。 + +## Traits + +如果你熟悉Java,Scala trait 类似于Java 8+中的接口。Traits 可以包含: + +- 抽象方法和成员 +- 具体方法和成员 + +在基本用法中,trait 可以用作接口,仅定义将由其他类实现的抽象成员: + +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Employee { + def id: Int + def firstName: String + def lastName: String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait Employee: + def id: Int + def firstName: String + def lastName: String +``` + +{% endtab %} +{% endtabs %} + +但是,traits 也可以包含具体成员。 +例如,以下 traits定义了两个抽象成员---`numLegs` 和 `walk()`---并且还具有`stop()`方法的具体实现: + +{% tabs traits_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Employee { + def id: Int + def firstName: String + def lastName: String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +``` + +{% endtab %} +{% endtabs %} + +下面是另一个具有抽象成员和两个具体实现的 trait: + +{% tabs traits_3 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait HasTail { + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait HasTail: + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +``` + +{% endtab %} +{% endtabs %} + +请注意,每个 trait 只处理非常特定的属性和行为:`HasLegs` 只处理腿,而 `HasTail` 只处理与尾部相关的功能。 +Traits可以让你构建这样的小模块。 + +在代码的后面部分,类可以混合多个 traits 来构建更大的组件: + +{% tabs traits_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class IrishSetter(name: String) extends HasLegs with HasTail { + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class IrishSetter(name: String) extends HasLegs, HasTail: + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +``` + +{% endtab %} +{% endtabs %} + +请注意,`IrishSetter` 类实现了在 `HasLegs` 和 `HasTail` 中定义的抽象成员。 +现在,您可以创建新的 `IrishSetter` 实例: + +{% tabs traits_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val d = new IrishSetter("Big Red") // "Big Red is a Dog" +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val d = IrishSetter(“Big Red”) // “Big Red is a Dog” +``` + +{% endtab %} +{% endtabs %} + +这只是你对 trait 可以完成的事情的一种体验。 +有关更多详细信息,请参阅这些建模课程的其余部分。 + +## 抽象类 + +{% comment %} +LATER: If anyone wants to update this section, our comments about abstract classes and traits are on Slack. The biggest points seem to be: +- The `super` of a trait is dynamic +- At the use site, people can mix in traits but not classes +- It remains easier to extend a class than a trait from Java, if the trait has at least a field +- Similarly, in Scala.js, a class can be imported from or exported to JavaScript. A trait cannot +- There are also some point that unrelated classes can’t be mixed together, and this can be a modeling advantage +{% endcomment %} + +当你想写一个类,但你知道它将有抽象成员时,你可以创建一个 trait 或一个抽象类。 +在大多数情况下,你会使用 trait,但从历史上看,有两种情况,使用抽象类比使用 trait 更好: + +- 您想要创建一个使用构造函数参数的基类 +- 代码将从 Java 代码调用 + +### 使用构造函数参数的基类 + +在 Scala 3 之前,当基类需要使用构造函数参数时,你可以将其声明为 `abstract class`: + +{% tabs abstract_1 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +abstract class Pet(name: String) { + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" +} + +class Dog(name: String, var age: Int) extends Pet(name) { + val greeting = "Woof" +} + +val d = new Dog("Fido", 1) +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +abstract class Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +{% endtab %} +{% endtabs %} + +

Trait 参数 Scala 3 独有

+ +但是,在 Scala 3 中,trait 现在可以具有[参数][trait-params],因此您现在可以在相同情况下使用 trait: + +{% tabs abstract_2 %} +{% tab 'Scala 3 Only' %} + +```scala +trait Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, var age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +{% endtab %} +{% endtabs %} + +trait 的组成更加灵活---您可以混合多个 trait,但只能扩展一个类---并且大多数时候应该优先于类和抽象类。 +经验法则是,每当要创建特定类型的实例时,就使用类;如果要分解和重用行为时,应使用trait。 + +

枚举Scala 3 独有

+ +枚举可用于定义由一组有限的命名值组成的类型(在[FP建模][fp-modeling]一节中,我们将看到枚举比这更灵活)。 +基本枚举用于定义常量集,如一年中的月份、一周中的天数、北/南/东/西方向等。 + +例如,这些枚举定义了与披萨饼相关的属性集: + +{% tabs enum_1 %} +{% tab 'Scala 3 Only' %} + + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +若要在其他代码中使用它们,请先导入它们,然后使用它们: + +{% tabs enum_2 %} +{% tab 'Scala 3 Only' %} + +```scala +import CrustSize.* +val currentCrustSize = Small +``` + +{% endtab %} +{% endtabs %} + +枚举值可以使用等于 (`==`) 进行比较,也可以用匹配的方式: + +{% tabs enum_3 %} +{% tab 'Scala 3 Only' %} + +```scala +// if/then +if (currentCrustSize == Large) + println("You get a prize!") + +// match +currentCrustSize match + case Small => println("small") + case Medium => println("medium") + case Large => println("large") +``` + +{% endtab %} +{% endtabs %} + +### 更多枚举特性 + +枚举也可以参数化: + +{% tabs enum_4 %} +{% tab 'Scala 3 Only' %} + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +{% endtab %} +{% endtabs %} + +它们还可以具有成员(如字段和方法): + +{% tabs enum_5 %} +{% tab 'Scala 3 Only' %} + +```scala +enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = + otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // more planets here ... +``` + +{% endtab %} +{% endtabs %} + +### 与 Java 枚举的兼容性 + +如果要将 Scala 定义的枚举用作 Java 枚举,可以通过扩展类 `java.lang.Enum`(默认情况下导入)来实现,如下所示: + +{% tabs enum_6 %} +{% tab 'Scala 3 Only' %} + +```scala +enum Color extends Enum[Color] { case Red, Green, Blue } +``` + +{% endtab %} +{% endtabs %} + +类型参数来自 Java `enum` 定义,并且应与枚举的类型相同。 +在扩展时,无需向`java.lang.Enum`提供构造函数参数(如Java API文档中所定义的那样)---编译器会自动生成它们。 + +像这样定义 `Color` 之后,你可以像使用 Java 枚举一样使用它: + +```` +scala> Color.Red.compareTo(Color.Green) +val res0: Int = -1 +```` + +关于[代数数据类型][adts]和[参考文档][ref-enums]的部分更详细地介绍了枚举。 + +## 样例类 + +样例类用于对不可变数据结构进行建模。 +举个例子: + +{% tabs case-classes_1 %} +{% tab 'Scala 2 and 3' %} + +```scala +case class Person(name: String, relation: String) +``` + +{% endtab %} +{% endtabs %} + +由于我们将 `Person` 声明为样例类,因此默认情况下,字段 `name` 和 `relation` 是公共的和不可变的。 +我们可以创建 样例类的实例,如下所示: + +{% tabs case-classes_2 %} +{% tab 'Scala 2 and 3' %} + +```scala +val christina = Person("Christina", "niece") +``` + +{% endtab %} +{% endtabs %} + +请注意,这些字段不能发生更改: + +{% tabs case-classes_3 %} +{% tab 'Scala 2 and 3' %} + +```scala +christina.name = "Fred" // error: reassignment to val +``` + +{% endtab %} +{% endtabs %} + +由于 样例类的字段被假定为不可变的,因此 Scala 编译器可以为您生成许多有用的方法: + +* 生成一个 `unapply` 方法,该方法允许您对样例类执行模式匹配(即,`case Person(n, r) => ...`)。 +* 在类中生成一个 `copy` 方法,这对于创建实例的修改副本非常有用。 +* 生成使用结构相等的 `equals` 和 `hashCode` 方法,允许您在 `Map` 中使用样例类的实例。 +* 生成默认的 `toString` 方法,对调试很有帮助。 + +以下示例演示了这些附加功能: + +{% tabs case-classes_4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +// Case classes can be used as patterns +christina match { + case Person(n, r) => println("name is " + n) +} + +// `equals` and `hashCode` methods generated for you +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// `toString` method +println(christina) // Person(Christina,niece) + +// built-in `copy` method +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// result: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) + +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +// Case classes can be used as patterns +christina match + case Person(n, r) => println("name is " + n) + +// `equals` and `hashCode` methods generated for you +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// `toString` method +println(christina) // Person(Christina,niece) + +// built-in `copy` method +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// result: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) +``` + +{% endtab %} +{% endtabs %} + +### 支持函数式编程 + +如前所述,样例类支持函数式编程 (FP): + +- 在FP中,您尽量避免改变数据结构。 + 因此,构造函数字段默认为 `val` 是有道理的。 + 由于无法更改样例类的实例,因此可以轻松共享它们,而不必担心突变或争用条件。 +- 您可以使用 `copy` 方法作为模板来创建新的(可能已更改的)实例,而不是改变实例。 + 此过程可称为“复制时更新”。 +- 自动为您生成 `unapply` 方法,还允许以模式匹配的高级方式使用样例类。 + +{% comment %} +NOTE: We can use this following text, if desired. If it’s used, it needs to be updated a little bit. + +### 一种 `unapply` 的方法 + +样例类的一大优点是,它可以为您的类自动生成一个 `unapply` 方法,因此您不必编写一个方法。 + +为了证明这一点,假设你有这个 trait: + +{% tabs case-classes_5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Person { + def name: String +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +trait Person: + def name: String +``` + +{% endtab %} +{% endtabs %} + +然后,创建以下样例类以扩展该 trait: + +{% tabs case-classes_6 %} +{% tab 'Scala 2 and 3' %} + +```scala +case class Student(name: String, year: Int) extends Person +case class Teacher(name: String, specialty: String) extends Person +``` + +{% endtab %} +{% endtabs %} + +由于它们被定义为样例类---并且它们具有内置的 `unapply` 方法---因此您可以编写如下匹配表达式: + +{% tabs case-classes_7 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def getPrintableString(p: Person): String = p match { + case Student(name, year) => + s"$name is a student in Year $year." + case Teacher(name, whatTheyTeach) => + s"$name teaches $whatTheyTeach." +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +def getPrintableString(p: Person): String = p match + case Student(name, year) => + s"$name is a student in Year $year." + case Teacher(name, whatTheyTeach) => + s"$name teaches $whatTheyTeach." +``` + +{% endtab %} +{% endtabs %} + +请注意 `case` 语句中的以下两种模式: + +{% tabs case-classes_8 %} +{% tab 'Scala 2 and 3' %} + +```scala +case Student(name, year) => +case Teacher(name, whatTheyTeach) => +``` + +{% endtab %} +{% endtabs %} + +这些模式之所以有效,是因为 `Student` 和 `Teacher` 被定义为具有 `unapply` 方法的样例类,其类型签名符合特定标准。 +从技术上讲,这些示例中显示的特定类型的模式匹配称为_结构模式匹配_。 + +> Scala 标准是, `unapply` 方法在包装在 `Option` 中的元组中返回 样例类构造函数字段。 +> 解决方案的“元组”部分在上一课中进行了演示。 + +要显示该代码的工作原理,请创建一个 `Student` 和 `Teacher` 的实例: + +{% tabs case-classes_9 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val s = new Student("Al", 1) +val t = new Teacher("Bob Donnan", "Mathematics") +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val s = Student("Al", 1) +val t = Teacher("Bob Donnan", "Mathematics") +``` + +{% endtab %} +{% endtabs %} + +接下来,这是当您使用这两个实例来调用 `getPrintableString` 时,REPL 中的输出如下所示: + +{% tabs case-classes_10 %} +{% tab 'Scala 2 and 3' %} + +```scala +scala> getPrintableString(s) +res0: String = Al is a student in Year 1. + +scala> getPrintableString(t) +res1: String = Bob Donnan teaches Mathematics. +``` + +{% endtab %} +{% endtabs %} + +>所有这些关于 `unapply` 方法和提取器的内容对于这样的入门书来说都有些先进,但是因为样例类是一个重要的FP主题,所以似乎最好涵盖它们,而不是跳过它们。 + +#### 将模式匹配添加到任何具有 unapply 的类型 + +Scala 的一个很好的特性是,你可以通过编写自己的 `unapply` 方法来向任何类型添加模式匹配。 +例如,此类在其伴生对象中定义了一个 `unapply` 方法: + +{% tabs case-classes_11 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +class Person(var name: String, var age: Int) +object Person { + def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +class Person(var name: String, var age: Int) +object Person: + def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) +``` + +{% endtab %} +{% endtabs %} + +因为它定义了一个 `unapply` 方法,并且因为该方法返回一个元组,所以您现在可以将 `Person` 与 `match` 表达式一起使用: + +{% tabs case-classes_12 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +val p = new Person("Astrid", 33) + +p match { + case Person(n,a) => println(s"name: $n, age: $a") + case null => println("No match") +} + +// that code prints: "name: Astrid, age: 33" +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +val p = Person("Astrid", 33) + +p match + case Person(n,a) => println(s"name: $n, age: $a") + case null => println("No match") + +// that code prints: "name: Astrid, age: 33" +``` + +{% endtab %} +{% endtabs %} + +{% endcomment %} + +## 样例对象 + +样例对象之于对象,就像 样例类之于类:它们提供了许多自动生成的方法,以使其更加强大。 +每当您需要需要少量额外功能的单例对象时,它们特别有用,例如在 `match` 表达式中与模式匹配一起使用。 + +当您需要传递不可变消息时,样例对象非常有用。 +例如,如果您正在处理音乐播放器项目,您将创建一组命令或消息,如下所示: + +{% tabs case-objects_1 %} +{% tab 'Scala 2 and 3' %} + +```scala +sealed trait Message +case class PlaySong(name: String) extends Message +case class IncreaseVolume(amount: Int) extends Message +case class DecreaseVolume(amount: Int) extends Message +case object StopPlaying extends Message +``` + +{% endtab %} +{% endtabs %} + +然后在代码的其他部分,你可以编写这样的方法,这些方法使用模式匹配来处理传入的消息(假设方法 `playSong` , `changeVolume` 和 `stopPlayingSong` 在其他地方定义): + +{% tabs case-objects_2 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +def handleMessages(message: Message): Unit = message match { + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +def handleMessages(message: Message): Unit = message match + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +``` + +{% endtab %} +{% endtabs %} + +[ref-enums]: {{ site.scala3ref }}/enums/enums.html +[adts]: {% link _zh-cn/overviews/scala3-book/types-adts-gadts.md %} +[fp-modeling]: {% link _zh-cn/overviews/scala3-book/domain-modeling-fp.md %} +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[unapply]: {{ site.scala3ref }}/changed-features/pattern-matching.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html diff --git a/_zh-cn/overviews/scala3-book/first-look-at-types.md b/_zh-cn/overviews/scala3-book/first-look-at-types.md new file mode 100644 index 0000000000..0c8fb09744 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/first-look-at-types.md @@ -0,0 +1,376 @@ +--- +title: 类型初探 +type: chapter +description: This page provides a brief introduction to Scala's built-in data types, including Int, Double, String, Long, Any, AnyRef, Nothing, and Null. +language: zh-cn +num: 17 +previous-page: taste-summary +next-page: string-interpolation + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +## 所有值都有一个类型 + +在 Scala 中,所有值都有一个类型,包括数值和函数。 +下图展示了类型层次结构的一个子集。 + +Scala 3 Type Hierarchy + +## Scala 类型层次结构 + +`Any` 是所有类型的超类型,也称为 **首类型**。 +它定义了某些通用方法,例如 `equals` , `hashCode` 和 `toString` 。 + +首类型 `Any` 有一个子类型 [`Matchable`][matchable],它用于标记可以执行模式匹配的所有类型。 +保证属性调用 _“参数化”_ 非常重要。 +我们不会在这里详细介绍,但总而言之,这意味着我们不能对类型为 `Any` 的值进行模式匹配,而只能对 `Matchable` 的子类型的值进行模式匹配。 +[参考文档][matchable]包含有关 `Matchable` 的更多信息。 + +`Matchable` 有两个重要的子类型: `AnyVal` 和 `AnyRef` 。 + +*`AnyVal`* 表示值类型。 +有几个预定义的值类型,它们是不可为空的: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, 和 `Boolean`。 +`Unit` 是一种值类型,它不携带有意义的信息。 +`Unit` 只有一个实例,我们可以将其称为:`()`。 + +*`AnyRef`* 表示引用类型。 +所有非值类型都定义为引用类型。 +Scala中的每个用户定义类型都是 `AnyRef` 的子类型。 +如果在Java运行时环境的上下文中使用Scala,则 `AnyRef` 对应于 `java.lang.Object`。 + +在基于语句的编程语言中, `void` 用于没有返回值的方法。 +如果您在Scala中编写没有返回值的方法,例如以下方法,则 `Unit` 用于相同的目的: + +{% tabs unit %} +{% tab 'Scala 2 and 3' for=unit %} + +```scala +def printIt(a: Any): Unit = println(a) +``` + +{% endtab %} +{% endtabs %} + +下面是一个示例,它演示了字符串、整数、字符、布尔值和函数都是 `Any` 的实例,可以像对待其他所有对象一样处理: + +{% tabs any %} +{% tab 'Scala 2 and 3' for=any %} + +```scala +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)) +``` + +{% endtab %} +{% endtabs %} + +该代码定义了一个类型为 `List[Any]` 的值 `list` 。 +该列表使用各种类型的元素进行初始化,但每个元素都是 `scala.Any` 的实例,因此我们可以将它们添加到列表中。 + +下面是程序的输出: + +``` +a string +732 +c +true + +``` + +## Scala的“值类型” + +如上所示,Scala的值类型扩展了 `AnyVal`,它们都是成熟的对象。 +这些示例演示如何声明以下数值类型的变量: + +{% tabs anyval %} +{% tab 'Scala 2 and 3' for=anyval %} + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` + +{% endtab %} +{% endtabs %} + +在前四个示例中,如果未显式指定类型,则数字 `1` 将默认为 `Int` ,因此,如果需要其他数据类型之一 --- `Byte` 、`Long` 或 `Short` --- 则需要显式声明这些类型,如上面代码所示。 +带有小数的数字(如2.0)将默认为 `Double` ,因此,如果您想要 `Float` ,则需要声明 `Float` ,如上一个示例所示。 + +由于 `Int` 和 `Double` 是默认数值类型,因此通常在不显式声明数据类型的情况下创建它们: + +{% tabs anynum %} +{% tab 'Scala 2 and 3' for=anynum %} + +```scala +val i = 123 // defaults to Int +val x = 1.0 // defaults to Double +``` + +{% endtab %} +{% endtabs %} + +在代码中,您还可以将字符 `L` 、 `D` 和 `F` (及其小写等效项)加到数字末尾,以指定它们是 `Long`, `Double`, 或 `Float` 值: + +{% tabs type-post %} +{% tab 'Scala 2 and 3' for=type-post %} + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = 3.3F // val z: Float = 3.3 +``` + +{% endtab %} +{% endtabs %} + +Scala还具有 `String` 和 `Char` 类型,通常可以使用隐式形式声明: + +{% tabs type-string %} +{% tab 'Scala 2 and 3' for=type-string %} + +```scala +val s = "Bill" +val c = 'a' +``` + +{% endtab %} +{% endtabs %} + +如下面表格所示,将字符串括在双引号中 --- 或多行字符串括在三引号中 --- 或字符括在单引号中。 + +这些数据类型及其范围包括: + +| 数据类型 | 可能的值 | +| ---------- | --------------- | +| Boolean | `true` 或 `false` | +| Byte | 8 位有符号二进制补码整数(-2^7 至 2^7-1,含)
-128 至 127 | +| Short | 16 位有符号二进制补码整数(-2^15 至 2^15-1,含)
-32,768 至 32,767 | +| Int | 32 位二进制补码整数(-2^31 至 2^31-1,含)
-2,147,483,648 至 2,147,483,647 | +| Long | 64 位 2 的补码整数(-2^63 到 2^63-1,含)
(-2^63 到 2^63-1,包括) | +| Float | 32 位 IEEE 754 单精度浮点数
1.40129846432481707e-45 到 3.40282346638528860e+38 | +| Double | 64 位 IEEE 754 双精度浮点数
4.94065645841246544e-324 到 1.79769313486231570e+308 | +| Char | 16 位无符号 Unicode 字符(0 到 2^16-1,含)
0 到 65,535 | +| String | 一个 `Char` 序列 | + +## `BigInt` 和 `BigDecimal` + +当您需要非常大的数字时,请使用 `BigInt` 和 `BigDecimal` 类型: + +{% tabs type-bigint %} +{% tab 'Scala 2 and 3' for=type-bigint %} + +```scala +val a = BigInt(1_234_567_890_987_654_321L) +val b = BigDecimal(123_456.789) +``` + +{% endtab %} +{% endtabs %} + +其中 `Double` 和 `Float` 是近似的十进制数, `BigDecimal` 用于精确算术,例如在使用货币时。 + +`BigInt` 和 `BigDecimal` 的一个好处是,它们支持您习惯于用于数字类型的所有运算符: + +{% tabs type-bigint2 %} +{% tab 'Scala 2 and 3' for=type-bigint2 %} + +```scala +val b = BigInt(1234567890) // scala.math.BigInt = 1234567890 +val c = b + b // scala.math.BigInt = 2469135780 +val d = b * b // scala.math.BigInt = 1524157875019052100 +``` + +{% endtab %} +{% endtabs %} + +## 关于字符串的两个注释 + +Scala字符串类似于Java字符串,但它们有两个很棒的附加特性: + +- 它们支持字符串插值 +- 创建多行字符串很容易 + +### 字符串插值 + +字符串插值提供了一种非常可读的方式在字符串中使用变量。 +例如,给定以下三个变量: + +{% tabs string-inside1 %} +{% tab 'Scala 2 and 3' for=string-inside1 %} + +```scala +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +{% endtab %} +{% endtabs %} + +你可以把那些变量组合成这样的字符串: + +{% tabs string-inside2 %} +{% tab 'Scala 2 and 3' for=string-inside2 %} + +```scala +println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" +``` + +{% endtab %} +{% endtabs %} + +只需在字符串前面加上字母 `s` ,然后在字符串内的变量名称之前放置一个 `$` 符号。 + +如果要在字符串中使用可能较大的表达式时,请将它们放在大括号中: + +{% tabs string-inside3 %} +{% tab 'Scala 2 and 3' for=string-inside3 %} + +```scala +println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +``` + +{% endtab %} +{% endtabs %} + +#### 其他插值器 + +放置在字符串前面的 `s` 只是一个可能的插值器。 +如果使用 `f` 而不是 `s` ,则可以在字符串中使用 `printf` 样式的格式语法。 +此外,字符串插值器只是一种特殊方法,可以定义自己的方法。 +例如,一些数据库库定义了非常强大的 `sql` 插值器。 + +### 多行字符串 + +多行字符串是通过将字符串包含在三个双引号内来创建的: + +{% tabs string-mlines1 %} +{% tab 'Scala 2 and 3' for=string-mlines1 %} + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +{% endtab %} +{% endtabs %} + +这种基本方法的一个缺点是,第一行之后的行是缩进的,如下所示: + +{% tabs string-mlines2 %} +{% tab 'Scala 2 and 3' for=string-mlines2 %} + +```scala +"The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting." +``` + +{% endtab %} +{% endtabs %} + +当间距很重要时,在第一行之后的所有行前面放一个 `|` 符号,并在字符串之后调用 `stripMargin` 方法: + +{% tabs string-mlines3 %} +{% tab 'Scala 2 and 3' for=string-mlines3 %} + +```scala +val quote = """The essence of Scala: + |Fusion of functional and object-oriented + |programming in a typed setting.""".stripMargin +``` + +{% endtab %} +{% endtabs %} + +现在字符串里所有行都是左对齐了: + +{% tabs string-mlines4 %} +{% tab 'Scala 2 and 3' for=string-mlines4 %} + +```scala +"The essence of Scala: +Fusion of functional and object-oriented +programming in a typed setting." +``` + +{% endtab %} +{% endtabs %} + +## 类型转换 + +可以通过以下方式强制转换值类型: + +Scala Type Hierarchy + +例如: + +{% tabs cast1 %} +{% tab 'Scala 2 and 3' for=cast1 %} + +```scala +val b: Byte = 127 +val i: Int = b // 127 + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +{% endtab %} +{% endtabs %} + +只有在没有丢失信息的情况下,才能强制转换为类型。否则,您需要明确说明强制转换: + +{% tabs cast2 %} +{% tab 'Scala 2 and 3' for=cast2 %} + +```scala +val x: Long = 987654321 +val y: Float = x.toFloat // 9.8765434E8 (注意 `.toFloat` 是必须的,因为强制类型转换后的精度会损) +val z: Long = y // Error +``` + +{% endtab %} +{% endtabs %} + +还可以将引用类型强制转换为子类型。 +这将在教程的后面部分介绍。 + +## `Nothing` 和 `null` + +`Nothing` 是所有类型的子类型,也称为**底部类型**。 +`Nothing` 类型是没有值的。 +一个常见的用途是发出非终止信号,例如抛出异常,程序退出或无限循环---即,它是不计算为值的那种表达式,或者不正常返回的方法。 + +`Null` 是所有引用类型的子类型(即 `AnyRef` 的任何子类型)。 +它具有由关键字面量 `null` 标识的单个值。 +目前,使用 `null` 被认为是不好的做法。它应该主要用于与其他JVM语言的互操作性。选择加入编译器选项会更改 `Null` 状态,以修复与其用法相关的警告。此选项可能会成为将来版本的 Scala 中的默认值。你可以在[这里][safe-null]了解更多关于它的信息。 + +与此同时, `null` 几乎不应该在Scala代码中使用。 +本书的[函数式编程章节][fp]和 [API文档][option-api]中讨论了 `null` 的替代方法。 + +[reference]: {{ site.scala3ref }}/overview.html +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +[interpolation]: {% link _overviews/scala3-book/string-interpolation.md %} +[fp]: {% link _zh-cn/overviews/scala3-book/fp-intro.md %} +[option-api]: https://scala-lang.org/api/3.x/scala/Option.html +[safe-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html diff --git a/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md b/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md new file mode 100644 index 0000000000..b1cce269af --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md @@ -0,0 +1,489 @@ +--- +title: 函数式错误处理 +type: section +description: This section provides an introduction to functional error handling in Scala 3. +language: zh-cn +num: 46 +previous-page: fp-functions-are-values +next-page: fp-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +函数式编程就像写一系列代数方程,因为代数没有空值或抛出异常,所以你不用在 FP 中使用这些特性。 +这带来了一个有趣的问题:在 OOP 代码中通常可能使用空值或异常的情况下,您会怎么做? + +Scala 的解决方案是使用类似 `Option`/`Some`/`None` 类的结构。 +本课介绍如何使用这些技术。 + +在我们开始之前有两个注意事项: + +- `Some` 和 `None` 类是 `Option` 的子类。 +- 下面的文字一般只指“`Option`”或“`Option`类”,而不是重复说“`Option`/`Some`/`None`”。 + +## 第一个例子 + +虽然第一个示例不处理空值,但它是引入 `Option` 类的好方法,所以我们将从它开始。 + +想象一下,您想编写一个方法,可以轻松地将字符串转换为整数值,并且您想要一种优雅的方法来处理异常,这个是异常是该方法获取类似“Hello”而不是“1”的字符串时引发的。 +对这种方法的初步猜测可能如下所示: + +{% tabs fp-java-try class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Int = + try { + Integer.parseInt(s.trim) + } catch { + case e: Exception => 0 + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Int = + try + Integer.parseInt(s.trim) + catch + case e: Exception => 0 +``` +{% endtab %} +{% endtabs %} + +如果转换成功,则此方法返回正确的 `Int` 值,但如果失败,则该方法返回 `0`。 +出于某些目的,这可能是可以的,但它并不准确。 +例如,该方法可能收到了`"0"`,但它也可能收到了 `"foo"`、`"bar"` 或无数其他将引发异常的字符串。 +这是一个真正的问题:您如何知道该方法何时真正收到 `"0"`,或者何时收到其他内容? +答案是,用这种方法,没有办法知道。 + +## 使用 Option/Some/None + +Scala 中这个问题的一个常见解决方案是使用三个类,称为 `Option`、`Some` 和 `None` 。 +`Some` 和 `None` 类是 `Option` 的子类,因此解决方案的工作原理如下: + +- 你声明 `makeInt` 返回一个 `Option` 类型 +- 如果 `makeInt` 接收到一个字符串,它*可以* 转换为 `Int`,答案将包含在 `Some` 中 +- 如果 `makeInt` 接收到一个它*无法*转换的字符串,它返回一个 `None` + +这是 `makeInt` 的修订版: + +{% tabs fp--try-option class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Option[Int] = + try { + Some(Integer.parseInt(s.trim)) + } catch { + case e: Exception => None + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Option[Int] = + try + Some(Integer.parseInt(s.trim)) + catch + case e: Exception => None +``` +{% endtab %} +{% endtabs %} + +这段代码可以理解为,“当给定的字符串转换为整数时,返回包裹在 `Some` 中的 `Int`,例如 `Some(1)`。 +当字符串无法转换为整数时,会抛出并捕获异常,并且该方法返回一个 `None` 值。” + +这些示例展示了 `makeInt` 的工作原理: + +{% tabs fp-try-option-example %} +{% tab 'Scala 2 and 3' %} +```scala +val a = makeInt("1") // Some(1) +val b = makeInt("one") // None +``` +{% endtab %} +{% endtabs %} + +如图所示,字符串`"1"`产生一个 `Some(1)`,而字符串 `"one"` 产生一个 `None`。 +这是错误处理的 `Option` 方法的本质。 +如图所示,使用了这种技术,因此方法可以返回 *值* 而不是 *异常*。 +在其他情况下,`Option` 值也用于替换 `null` 值。 + +两个注意事项: + +- 你会发现这种方法在整个 Scala 库类和第三方 Scala 库中使用。 +- 这个例子的一个关键点是函数式方法不会抛出异常;相反,它们返回类似 `Option` 的值。 + +## 成为 makeInt 的消费者 + +现在假设您是 `makeInt` 方法的使用者。 +你知道它返回一个 `Option[Int]` 的子类,所以问题就变成了,你如何处理这些返回类型? + +根据您的需要,有两个常见的答案: + +- 使用 `match` 表达式 +- 使用 `for` 表达式 + +## 使用 `match` 表达式 + +一种可能的解决方案是使用 `match` 表达式: + +{% tabs fp-option-match class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn’t work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn’t work.") +``` +{% endtab %} +{% endtabs %} + +在本例中,如果 `x` 可以转换为 `Int`,则计算第一个 `case` 子句右侧的表达式;如果 `x` 不能转换为 `Int`,则计算第二个 `case` 子句右侧的表达式。 + +## 使用 `for` 表达式 + +另一种常见的解决方案是使用 `for` 表达式,即本书前面显示的 `for`/`yield` 组合。 +例如,假设您要将三个字符串转换为整数值,然后将它们相加。 +这就是你使用 `for` 表达式和 `makeInt` 的方法: + +{% tabs fp-for-comprehension class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} +{% endtabs %} + +在该表达式运行后,`y` 将是以下两种情况之一: + +- 如果*所有*三个字符串都转换为 `Int` 值,`y` 将是 `Some[Int]`,即包裹在 `Some` 中的整数 +- 如果三个字符串中*任意一个*字符串不能转换为 `Int`,则 `y` 将是 `None` + +你可以自己测试一下: + +{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} +{% endtabs %} + +使用该样本数据,变量 `y` 的值将是 `Some(6)` 。 + +要查看失败案例,请将这些字符串中的任何一个更改为不会转换为整数的字符串。 +当你这样做时,你会看到 `y` 是 `None`: + +{% tabs fp-for-comprehension-failure-result %} +{% tab 'Scala 2 and 3' %} +```scala +y: Option[Int] = None +``` +{% endtab %} +{% endtabs %} + +## 将 Option 视为容器 + +心智模型通常可以帮助我们理解新情况,因此,如果您不熟悉 `Option` 类,可以将它们视为*容器*: + +- `Some` 是一个容器,里面有一个项目 +- `None` 是一个容器,但里面什么都没有 + +如果您更愿意将 `Option` 类想象成一个盒子,`None` 就像一个空盒子。 +它可能有一些东西,但它没有。 + +{% comment %} +NOTE: I commented-out this subsection because it continues to explain Some and None, and I thought it was probably too much for this book. + +## Using `foreach` with `Option` + +Because `Some` and `None` can be thought of containers, they’re also like collections classes. +They have many of the methods you’d expect from a collection class, including `map`, `filter`, `foreach`, etc. + +This raises an interesting question: What will these two values print, if anything? + +{% tabs fp-option-methods-evaluation %} +{% tab 'Scala 2 and 3' %} +```scala +makeInt("1").foreach(println) +makeInt("x").foreach(println) +``` +{% endtab %} +{% endtabs %} + +Answer: The first example prints the number `1`, and the second example doesn’t print anything. +The first example prints `1` because: + +- `makeInt("1")` evaluates to `Some(1)` +- The expression becomes `Some(1).foreach(println)` +- The `foreach` method on the `Some` class knows how to reach inside the `Some` container and extract the value (`1`) that’s inside it, so it passes that value to `println` + +Similarly, the second example prints nothing because: + +- `makeInt("x")` evaluates to `None` +- The `foreach` method on the `None` class knows that `None` doesn’t contain anything, so it does nothing + +In this regard, `None` is similar to an empty `List`. + +### The happy and unhappy paths + +Somewhere in Scala’s history, someone noted that the first example (the `Some`) represents the “Happy Path” of the `Option` approach, and the second example (the `None`) represents the “Unhappy Path.” +*But* despite having two different possible outcomes, the great thing with `Option` is that there’s really just one path: The code you write to handle the `Some` and `None` possibilities is the same in both cases. +The `foreach` examples look like this: + +{% tabs fp-another-option-method-example %} +{% tab 'Scala 2 and 3' %} +```scala +makeInt(aString).foreach(println) +``` +{% endtab %} +{% endtabs %} + +And the `for` expression looks like this: + +{% tabs fp-another-for-comprehension-example class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} +{% endtabs %} + +With exceptions you have to worry about handling branching logic, but because `makeInt` returns a value, you only have to write one piece of code to handle both the Happy and Unhappy Paths, and that simplifies your code. + +Indeed, the only time you have to think about whether the `Option` is a `Some` or a `None` is when you handle the result value, such as in a `match` expression: + +{% tabs fp-option-match-handle class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn't work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn't work.") +``` +{% endtab %} +{% endtabs %} + +> There are several other ways to handle `Option` values. +> See the reference documentation for more details. +{% endcomment %} + +## 使用 `Option` 替换 `null` + +回到 `null` 值,`null` 值可以悄悄地潜入你的代码的地方是这样的类: + +{% tabs fp=case-class-nulls %} +{% tab 'Scala 2 and 3' %} +```scala +class Address( + var street1: String, + var street2: String, + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} +{% endtabs %} + +虽然地球上的每个地址都有一个 `street1` 值,但 `street2` 值是可选的。 +因此,`street2` 字段可以被分配一个 `null` 值: + +{% tabs fp-case-class-nulls-example class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + null, // <-- D’oh! A null value! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + null, // <-- D’oh! A null value! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} +{% endtabs %} + +从历史上看,开发人员在这种情况下使用了空白字符串和空值,这两种方法都是使用技巧来解决基础性的问题,这个问题是:`street2` 是一个*可选*字段。 +在 Scala 和其他现代语言中,正确的解决方案是预先声明 `street2` 是可选的: + +{% tabs fp-case-class-with-options %} +{% tab 'Scala 2 and 3' %} +```scala +class Address( + var street1: String, + var street2: Option[String], // an optional value + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} +{% endtabs %} + +现在开发人员可以编写更准确的代码,如下所示: + +{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + None, // 'street2' has no value + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + None, // 'street2' has no value + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} +{% endtabs %} + +或这个: + +{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} +{% endtabs %} + +## `Option` 不是唯一的解决方案 + +虽然本节关注的是 `Option` 类,但 Scala 还有一些其他选择。 + +例如,称为 `Try`/`Success`/`Failure` 的三个类以相同的方式工作,但是 (a) 当您的代码可以抛出异常时,您主要使用这些类,并且 (b) 您想要使用`Failure` 类,因为它使您可以访问异常消息。 +例如,在编写与文件、数据库和 Internet 服务交互的方法时,通常会使用这些 `Try` 类,因为这些函数很容易引发异常。 + +## 快速回顾 + +这部分很长,让我们快速回顾一下: + +- 函数式程序员不使用 `null` 值 +- `null` 值的主要替代品是使用 `Option` 类 +- 函数式方法不会抛出异常; 相反,它们返回诸如 `Option` 、 `Try` 或 `Either` 之类的值 +- 使用 `Option` 值的常用方法是 `match` 和 `for` 表达式 +- Option 可以被认为是一个项目(`Some`)和没有项目(`None`)的容器 +- option 也可用于可选的构造函数或方法参数 + diff --git a/_zh-cn/overviews/scala3-book/fp-functions-are-values.md b/_zh-cn/overviews/scala3-book/fp-functions-are-values.md new file mode 100644 index 0000000000..ab802ed02a --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-functions-are-values.md @@ -0,0 +1,133 @@ +--- +title: 函数是值 +type: section +description: This section looks at the use of functions as values in functional programming. +language: zh-cn +num: 45 +previous-page: fp-pure-functions +next-page: fp-functional-error-handling + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +虽然曾经创建的每种编程语言都可能允许您编写纯函数,但 Scala FP 的第二个重要特性是*您可以将函数创建为值*,就像您创建 `String` 和 `Int` 值一样。 + +这个特性有很多好处,其中最常见的是(a)您可以定义方法来接受函数参数,以及(b)您可以将函数作为参数传递给方法。 +你已经在本书的多个地方看到了这一点,每当演示像 `map` 和 `filter` 这样的方法时: + +{% tabs fp-function-as-values-anonymous %} +{% tab 'Scala 2 and 3' %} +```scala +val nums = (1 to 10).toList + +val doubles = nums.map(_ * 2) // double each value +val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4) +``` +{% endtab %} +{% endtabs %} + +在这些示例中,匿名函数被传递到 `map` 和 `filter` 中。 + +> 匿名函数也称为 *lambdas*。 + +除了将匿名函数传递给 `filter` 和 `map` 之外,您还可以为它们提供 *方法*: + +{% tabs fp-function-as-values-defined %} +{% tab 'Scala 2 and 3' %} +```scala +// two methods +def double(i: Int): Int = i * 2 +def underFive(i: Int): Boolean = i < 5 + +// pass those methods into filter and map +val doubles = nums.filter(underFive).map(double) +``` +{% endtab %} +{% endtabs %} + +这种将方法和函数视为值的能力是函数式编程语言提供的强大特性。 + +> 从技术上讲,将另一个函数作为输入参数的函数称为 *高阶函数*。 +> (如果你喜欢幽默,就像有人曾经写过的那样,这就像说一个将另一个类的实例作为构造函数参数的类是一个高阶类。) + +## 函数、匿名函数和方法 + +正如您在这些示例中看到的,这是一个匿名函数: + +{% tabs fp-anonymous-function-short %} +{% tab 'Scala 2 and 3' %} +```scala +_ * 2 +``` +{% endtab %} +{% endtabs %} + +如 [高阶函数][hofs] 讨论中所示,上面的写法是下面语法的简写版本: + +{% tabs fp-anonymous-function-full %} +{% tab 'Scala 2 and 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +像这样的函数被称为“匿名”,因为它们没有名字。 +如果你想给一个名字,只需将它分配给一个变量: + +{% tabs fp-function-assignement %} +{% tab 'Scala 2 and 3' %} +```scala +val double = (i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +现在你有了一个命名函数,一个分配给变量的函数。 +您可以像使用方法一样使用此函数: + +{% tabs fp-function-used-like-method %} +{% tab 'Scala 2 and 3' %} +```scala +double(2) // 4 +``` +{% endtab %} +{% endtabs %} + +在大多数情况下,`double` 是函数还是方法并不重要。 Scala 允许您以同样的方式对待它们。 +在幕后,让您像对待函数一样对待方法的 Scala 技术被称为 [Eta 表达式][eta]。 + +这种将函数作为变量无缝传递的能力是 Scala 等函数式编程语言的一个显着特征。 +正如您在本书中的 `map` 和 `filter` 示例中所见,将函数传递给其他函数的能力有助于您创建简洁且仍然可读的代码---*富有表现力*。 + +如果您对将函数作为参数传递给其他函数的过程不适应,可以尝试以下几个示例: + +{% tabs fp-function-as-values-example %} +{% tab 'Scala 2 and 3' %} +```scala +List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE) +List("bob", "joe").map(_.capitalize) // List(Bob, Joe) +List("plum", "banana").map(_.length) // List(4, 6) + +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(5, 1, 3, 11, 7) +nums.map(_ * 2) // List(10, 2, 6, 22, 14) +nums.filter(_ > 3) // List(5, 11, 7) +nums.takeWhile(_ < 6) // List(5, 1, 3) +nums.sortWith(_ < _) // List(1, 3, 5, 7, 11) +nums.sortWith(_ > _) // List(11, 7, 5, 3, 1) + +nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5) +``` +{% endtab %} +{% endtabs %} + +[hofs]: {% link _zh-cn/overviews/scala3-book/fun-hofs.md %} +[eta]: {% link _zh-cn/overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-immutable-values.md b/_zh-cn/overviews/scala3-book/fp-immutable-values.md new file mode 100644 index 0000000000..1d6b474daf --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-immutable-values.md @@ -0,0 +1,95 @@ +--- +title: 不可变值 +type: section +description: This section looks at the use of immutable values in functional programming. +language: zh-cn +num: 43 +previous-page: fp-what-is-fp +next-page: fp-pure-functions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在纯函数式编程中,只使用不可变的值。 +在 Scala 中,这意味着: + +- 所有变量都创建为 `val` 字段 +- 仅使用不可变的集合类,例如 `List`、`Vector` 和不可变的 `Map` 和 `Set` 类 + +只使用不可变变量会引发一个有趣的问题:如果一切都是不可变的,那么任何东西都如何改变? + +当谈到使用集合时,一个答案是你不会改变现有的集合。相反,您将函数应用于现有集合以创建新集合。 +这就是像 `map` 和 `filter` 这样的高阶函数的用武之地。 + +例如,假设你有一个名字列表——一个 `List[String]`——都是小写的,你想找到所有以字母 `"j"` 开头的名字,并且把找出来的名字大写。 +在 FP 中,您编写以下代码: + +{% tabs fp-list %} +{% tab 'Scala 2 and 3' %} +```scala +val a = List("jane", "jon", "mary", "joe") +val b = a.filter(_.startsWith("j")) + .map(_.capitalize) +``` +{% endtab %} +{% endtabs %} + +如图所示,您不会改变原始列表 `a`。 +相反,您将过滤和转换函数应用于 `a` 以创建一个新集合,并将该结果分配给新的不可变变量 `b` 。 + +同样,在 FP 中,您不会创建具有可变 `var` 构造函数参数的类。 +也就是说,你不要这样写: + +{% tabs fp--class-variables %} +{% tab 'Scala 2 and 3' %} +```scala +// don’t do this in FP +class Person(var firstName: String, var lastName: String) + --- --- +``` +{% endtab %} +{% endtabs %} + +相反,您通常创建 `case` 类,其构造函数参数默认为 `val`: + +{% tabs fp-immutable-case-class %} +{% tab 'Scala 2 and 3' %} +```scala +case class Person(firstName: String, lastName: String) +``` +{% endtab %} +{% endtabs %} + +现在你创建一个 `Person` 实例作为 `val` 字段: + +{% tabs fp-case-class-creation %} +{% tab 'Scala 2 and 3' %} +```scala +val reginald = Person("Reginald", "Dwight") +``` +{% endtab %} +{% endtabs %} + +然后,当您需要对数据进行更改时,您可以使用 `case` 类附带的 `copy` 方法来“在制作副本时更新数据”,如下所示: + +{% tabs fp-case-class-copy %} +{% tab 'Scala 2 and 3' %} +```scala +val elton = reginald.copy( + firstName = "Elton", // update the first name + lastName = "John" // update the last name +) +``` +{% endtab %} +{% endtabs %} + +还有其他处理不可变集合和变量的技术,但希望这些示例能让您尝试一下这些技术。 + +> 根据您的需要,您可以创建枚举、traits 或类,而不是 `case` 类。 +> 有关详细信息,请参阅[数据建模][modeling]一章。 + +[modeling]: {% link _zh-cn/overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-intro.md b/_zh-cn/overviews/scala3-book/fp-intro.md new file mode 100644 index 0000000000..4805401aec --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-intro.md @@ -0,0 +1,30 @@ +--- +title: 函数式编程 +type: chapter +description: This chapter provides an introduction to functional programming in Scala 3. +language: zh-cn +num: 41 +previous-page: collections-summary +next-page: fp-what-is-fp + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 允许您以面向对象编程 (OOP) 风格、函数式编程 (FP) 风格以及混合风格编写代码——结合使用这两种方法。 +正如 Scala 的创建者 Martin Odersky 所说,Scala 的本质是在类型化设置中融合了函数式编程和面向对象编程: + +- 逻辑函数 +- 模块化的对象 + +本章假设您熟悉 OOP 而不太熟悉 FP,因此它简要介绍了几个主要的函数式编程概念: + +- 什么是函数式编程? +- 不可变值 +- 纯函数 +- 函数是值 +- 函数式错误处理 + diff --git a/_zh-cn/overviews/scala3-book/fp-pure-functions.md b/_zh-cn/overviews/scala3-book/fp-pure-functions.md new file mode 100644 index 0000000000..25fa4398ff --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-pure-functions.md @@ -0,0 +1,129 @@ +--- +title: 纯函数 +type: section +description: This section looks at the use of pure functions in functional programming. +language: zh-cn +num: 44 +previous-page: fp-immutable-values +next-page: fp-functions-are-values + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 提供的另一个帮助您编写函数式代码的特性是编写纯函数的能力。 +一个_纯函数_可以这样定义: + +- 函数 `f` 是纯函数,如果给定相同的输入 `x`,它总是返回相同的输出 `f(x)` +- 函数的输出_只_取决于它的输入变量和它的实现 +- 它只计算输出而不修改周围的世界 + +这意味着: +- 它不修改其输入参数 +- 它不会改变任何隐藏状态 +- 没有任何“后门”:不从外界读取数据(包括控制台、Web服务、数据库、文件等),也不向外界写入数据 + +作为这个定义的结果,任何时候你调用一个具有相同输入值的纯函数,你总是会得到相同的结果。 +例如,您可以使用输入值 `2` 无限次调用 `double` 函数,并且始终得到结果 `4`。 + +## 纯函数示例 + +给定这个定义,你可以想象, *scala.math._* 包中的这些方法是纯函数: + +- `abs` +- `ceil` +- `max` + +这些 `String` 方法也是纯函数: + +- `isEmpty` +- `length` +- `substring` + +Scala 集合类上的大多数方法也可以作为纯函数工作,包括 `drop`、`filter`、`map` 等等。 + +> 在 Scala 中,_函数_和_方法_几乎可以完全互换,因此即使我们使用行业通用术语“纯函数”,这个术语也可以用来描述函数和方法。 +> 如果您对如何像函数一样使用方法感兴趣,请参阅 [Eta 表达式][eta] 的讨论。 + +## 不纯函数示例 + +相反,以下函数是_不纯的_,因为它们违反了纯函数的定义。 + +- `println` -- 与控制台、文件、数据库、Web 服务、传感器等交互的方法都是不纯的。 +- `currentTimeMillis ` -- 与日期和时间相关的方法都是不纯的,因为它们的输出取决于输入参数以外的其他东西 +- `sys.error` -- 异常抛出方法是不纯的,因为它们不简单地返回结果 + +不纯函数通常会执行以下一项或多项操作: + +- 从隐藏状态读取,即它们访问的变量和数据不是作为输入参数明确地传递给函数 +- 写入隐藏状态 +- 改变他们给定的参数,或改变隐藏变量,例如类中包涵的字段 +- 与外界执行某种 I/O + +> 通常,您应该注意返回类型为 `Unit` 的函数。 +> 因为这些函数不返回任何东西,逻辑上你调用它的唯一原因是实现一些副作用。 +> 因此,这些功能的使用通常是不纯的。 + +## 但是需要不纯的函数... + +当然,如果一个应用程序不能读取或写入外部世界,它就不是很有用,所以人们提出以下建议: + +> 使用纯函数编写应用程序的核心,然后围绕该核心编写一个不纯的“包装器”以与外部世界交互。 +> 正如有人曾经说过的,这就像在纯蛋糕上加了一层不纯的糖霜。 + +重要的是要注意,有一些方法可以让与外界的不纯粹互动感觉更纯粹。 +例如,你会听说使用 `IO` Monad 来处理输入和输出。 +这些主题超出了本文档的范围,所以为了简单起见,这样认识 FP 会有所帮助:FP 应用程序有一个纯函数核心,还有其他一些函数把这些纯函数包装起来与外部世界交互。 + +## 编写纯函数 + +**注意**:在本节中,常见的行业术语“纯函数”通常用于指代 Scala 方法。 + +要在 Scala 中编写纯函数,只需使用 Scala 的方法语法编写它们(尽管您也可以使用 Scala 的函数语法)。 +例如,这是一个将给定输入值加倍的纯函数: + +{% tabs fp-pure-function %} +{% tab 'Scala 2 and 3' %} +```scala +def double(i: Int): Int = i * 2 +``` +{% endtab %} +{% endtabs %} + +如果您对递归感到满意,这是一个计算整数列表之和的纯函数: + +{% tabs fp-pure-recursive-function class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def sum(xs: List[Int]): Int = xs match { + case Nil => 0 + case head :: tail => head + sum(tail) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(xs: List[Int]): Int = xs match + case Nil => 0 + case head :: tail => head + sum(tail) +``` +{% endtab %} +{% endtabs %} + +如果您理解该代码,您会发现它符合纯函数定义。 + +## 要点 + +本节的第一个要点是纯函数的定义: + +> _纯函数_是仅依赖于其声明的输入及其实现来产生其输出的函数。 +> 它只计算其输出,不依赖或修改外部世界。 + +第二个要点是每个现实世界的应用程序都与外部世界交互。 +因此,考虑函数式程序的一种简化方法是,它们由一个纯函数核心组成,其他一些函数把这些纯函数包装起来与外部世界交互。 + +[eta]: {% link _zh-cn/overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-summary.md b/_zh-cn/overviews/scala3-book/fp-summary.md new file mode 100644 index 0000000000..459b2d6f82 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-summary.md @@ -0,0 +1,30 @@ +--- +title: 总结 +type: section +description: This section summarizes the previous functional programming sections. +language: zh-cn +num: 47 +previous-page: fp-functional-error-handling +next-page: types-introduction + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章在比较高的层次上,对 Scala 中的函数式编程进行了介绍。 +涵盖的主题是: + +- 什么是函数式编程? +- 不可变值 +- 纯函数 +- 函数是值 +- 函数式错误处理 + +如前所述,函数式编程是一个很大的话题,所以我们在本书中所能做的就是触及这些介绍性概念。 +有关详细信息,请参阅 [参考文档][reference]。 + +[reference]: {{ site.scala3ref }}/overview.html + diff --git a/_zh-cn/overviews/scala3-book/fp-what-is-fp.md b/_zh-cn/overviews/scala3-book/fp-what-is-fp.md new file mode 100644 index 0000000000..79370d450b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-what-is-fp.md @@ -0,0 +1,33 @@ +--- +title: 什么是函数式编程? +type: section +description: This section provides an answer to the question, what is functional programming? +language: zh-cn +num: 42 +previous-page: fp-intro +next-page: fp-immutable-values + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +[维基百科定义_函数式编程_](https://en.wikipedia.org/wiki/Functional_programming)如下: + +
+

函数式编程是一种编程范式,通过应用和组合函数来构建程序。 +它是一种声明式编程范例,其中函数定义是表达式树,每个表达式都返回一个值,而不是改变程序状态的命令式语句序列。

+

 

+

在函数式编程中,函数被视为一等公民,这意味着它们可以绑定到名称(包括本地标识符)、作为参数传递以及从其他函数返回,就像任何其他数据类型一样。 +这允许以声明式和可组合的方式编写程序,其中小函数以模块化方式组合。

+
+ +知道这样的情况对你很有帮助:有经验的函数式程序员非常倾向于将他们的代码视为数学,将纯函数组合在一起就像组合一系列代数方程一样。 + +当你编写函数式代码时,你会感觉自己像个数学家,一旦你理解了范式,你就会想编写总是返回_值_---而不是异常或空值---的纯函数,这样你就可以将它们组合(结合)在一起以创建解决方案。 +编写类似数学的方程式(表达式)的感觉是驱使您_只_使用纯函数和不可变值的动力,因为这就是您在代数和其他形式的数学中使用的东西。 + +函数式编程是一个很大的主题,没有简单的方法可以将整个主题浓缩为一章,但希望以下部分能够概述主要主题,并展示 Scala 为编写函数式代码提供的一些工具。 + diff --git a/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md b/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md new file mode 100644 index 0000000000..ec08a19e02 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md @@ -0,0 +1,202 @@ +--- +title: 匿名函数 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +language: zh-cn +num: 29 +previous-page: fun-intro +next-page: fun-function-variables + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +匿名函数(也称为 *lambda*)是作为参数传递给高阶函数的代码块。 +维基百科将 [匿名函数](https://en.wikipedia.org/wiki/Anonymous_function) 定义为“未绑定到标识符的函数定义”。 + +例如,给定这样的列表: + +{% tabs fun-anonymous-1 %} +{% tab 'Scala 2 and 3' %} +```scala +val ints = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +您可以通过使用 `List` 类 `map` 方法和自定义匿名函数将 `ints` 中的每个元素加倍来创建一个新列表: + +{% tabs fun-anonymous-2 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map(_ * 2) // List(2, 4, 6) +``` +{% endtab %} +{% endtabs %} + +如注释所示,`doubledInts` 包含列表`List(2, 4, 6)`。 +在该示例中,这部分代码是一个匿名函数: + +{% tabs fun-anonymous-3 %} +{% tab 'Scala 2 and 3' %} +```scala +_ * 2 +``` +{% endtab %} +{% endtabs %} + +这是“将给定元素乘以 2”的简写方式。 + +## 更长的形式 + +一旦你熟悉了 Scala,你就会一直使用这种形式来编写匿名函数,这些函数在函数的一个位置使用一个变量。 +但如果你愿意,你也可以用更长的形式来写它们,所以除了写这段代码: + +{% tabs fun-anonymous-4 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +您也可以使用以下形式编写它: + +{% tabs fun-anonymous-5 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +val doubledInts = ints.map((i) => i * 2) +val doubledInts = ints.map(i => i * 2) +``` +{% endtab %} +{% endtabs %} + +所有这些行的含义完全相同:将 `ints` 中的每个元素加倍以创建一个新列表 `doubledInts`。 +(稍后会解释每种形式的语法。) + +如果您熟悉 Java,了解这些 `map` 示例与以下 Java 代码等价可能会有所帮助: + +{% tabs fun-anonymous-5-b %} +{% tab 'Java' %} +```java +List ints = List.of(1, 2, 3); +List doubledInts = ints.stream() + .map(i -> i * 2) + .collect(Collectors.toList()); +``` +{% endtab %} +{% endtabs %} + +## 缩短匿名函数 + +当你想要明确时,你可以使用这个长格式编写一个匿名函数: + +{% tabs fun-anonymous-6 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +该表达式中的匿名函数是这样的: + +{% tabs fun-anonymous-7 %} +{% tab 'Scala 2 and 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +如果您不熟悉这种语法,将 `=>` 符号视为转换器会有所帮助,因为表达式使用 `=>` 符号右侧的算法(在这种情况下,一个将 `Int` 加倍的表达式)把符号左侧的参数列表(名为 `i` 的 `Int` 变量) *转换*为新结果。 + +### 缩短该表达式 + +这种长形式可以缩短,如以下步骤所示。 +首先,这是最长和最明确的形式: + +{% tabs fun-anonymous-8 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +因为 Scala 编译器可以从 `ints` 中的数据推断 `i` 是一个 `Int`,所以可以删除 `Int` 声明: + +{% tabs fun-anonymous-9 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map((i) => i * 2) +``` +{% endtab %} +{% endtabs %} + +因为只有一个参数,所以不需要在参数 `i` 周围的括号: + +{% tabs fun-anonymous-10 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map(i => i * 2) +``` +{% endtab %} +{% endtabs %} + +因为当参数在函数中只出现一次时,Scala 允许您使用 `_` 符号而不是变量名,所以代码可以进一步简化: + +{% tabs fun-anonymous-11 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +### 变得更短 + +在其他示例中,您可以进一步简化匿名函数。 +例如,从最显式的形式开始,您可以使用带有 `List` 类 `foreach` 方法的匿名函数打印 `ints` 中的每个元素: + +{% tabs fun-anonymous-12 %} +{% tab 'Scala 2 and 3' %} +```scala +ints.foreach((i: Int) => println(i)) +``` +{% endtab %} +{% endtabs %} + +和以前一样,不需要 `Int` 声明,因为只有一个参数,所以不需要 `i` 周围的括号: + +{% tabs fun-anonymous-13 %} +{% tab 'Scala 2 and 3' %} +```scala +ints.foreach(i => println(i)) +``` +{% endtab %} +{% endtabs %} + +因为 `i` 在函数体中只使用一次,表达式可以进一步简化为 `_` 符号: + +{% tabs fun-anonymous-14 %} +{% tab 'Scala 2 and 3' %} +```scala +ints.foreach(println(_)) +``` +{% endtab %} +{% endtabs %} + +最后,如果一个匿名函数由一个接受单个参数的方法调用组成,您不需要显式命名和指定参数,因此您最终可以只写方法的名称(此处为 `println`): + +{% tabs fun-anonymous-15 %} +{% tab 'Scala 2 and 3' %} +```scala +ints.foreach(println) +``` +{% endtab %} +{% endtabs %} + diff --git a/_zh-cn/overviews/scala3-book/fun-eta-expansion.md b/_zh-cn/overviews/scala3-book/fun-eta-expansion.md new file mode 100644 index 0000000000..562ed0074e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-eta-expansion.md @@ -0,0 +1,114 @@ +--- +title: Eta 扩展 +type: section +description: This page discusses Eta Expansion, the Scala technology that automatically and transparently converts methods into functions. +language: zh-cn +num: 31 +previous-page: fun-function-variables +next-page: fun-hofs + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +当你查看 Scala 集合类的 `map` 方法的 Scaladoc 时,你会看到它被定义为接受一个_函数_: + +{% tabs fun_1 %} +{% tab 'Scala 2 and 3' for=fun_1 %} +```scala +def map[B](f: (A) => B): List[B] + ------------ +``` +{% endtab %} +{% endtabs %} + +事实上,Scaladoc 明确指出,“`f` 是应用于每个元素的_函数_。” +但尽管如此,你可以通过某种方式将_方法_传递给 `map`,它仍然有效: + +{% tabs fun_2 %} +{% tab 'Scala 2 and 3' for=fun_2 %} +```scala +def times10(i: Int) = i * 10 // a method +List(1, 2, 3).map(times10) // List(10,20,30) +``` +{% endtab %} +{% endtabs %} + +你有没有想过这是如何工作的——如何将_方法_传递给需要_函数_的`map`? + +这背后的技术被称为_Eta Expansion_。 +它将 _方法类型_的表达式转换为 _函数类型_的等效表达式,并且它无缝而安静地完成了。 + +## 方法和函数的区别 + +{% comment %} +NOTE: I got the following “method” definition from this page (https://dotty.epfl.ch/docs/reference/changed-features/eta-expansion-spec.html), but I’m not sure it’s 100% accurate now that 方法 can exist outside of classes/traits/objects. +I’ve made a few changes to that description that I hope are more accurate and up to date. +{% endcomment %} + +从历史上看,_方法_一直是类定义的一部分,尽管在 Scala 3 中您现在可以拥有类之外的方法,例如 [Toplevel definitions][toplevel] 和 [extension 方法][extension]。 + +与方法不同,_函数_本身就是完整的对象,使它们成为第一等的实体。 + +它们的语法也不同。 +此示例说明如何定义执行相同任务的方法和函数,确定给定整数是否为偶数: + +{% tabs fun_3 %} +{% tab 'Scala 2 and 3' for=fun_3 %} +```scala +def isEvenMethod(i: Int) = i % 2 == 0 // a method +val isEvenFunction = (i: Int) => i % 2 == 0 // a function +``` +{% endtab %} +{% endtabs %} + +该函数确实是一个对象,因此您可以像使用任何其他变量一样使用它,例如将其放入列表中: + +{% tabs fun_4 %} +{% tab 'Scala 2 and 3' for=fun_4 %} +```scala +val functions = List(isEvenFunction) +``` +{% endtab %} +{% endtabs %} + + +{% tabs fun_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=fun_5 %} +```scala +// this example shows the Scala 2 error message +val methods = List(isEvenMethod) + ^ +error: missing argument list for method isEvenMethod +Unapplied methods are only converted to functions when a function type is expected. +You can make this conversion explicit by writing `isEvenMethod _` or `isEvenMethod(_)` instead of `isEvenMethod`. +``` + +相反,从技术上讲,方法不是对象,因此在 Scala 2 中,您不能将方法放入 `List` 中,至少不能直接放入,如下例所示: + +{% endtab %} +{% tab 'Scala 3' for=fun_5 %} + +```scala +val functions = List(isEvenFunction) // works +val methods = List(isEvenMethod) // works +``` + +Scala 3 的重要部分是改进了 Eta 扩展技术,所以现在当您尝试将方法当作变量用,它是可以工作的---您不必自己处理手动转换: + +{% endtab %} +{% endtabs %} + +就这本入门书而言,需要了解的重要事项是: + +- Eta Expansion 是 Scala 技术,让您可以像使用函数一样使用方法 +- 该技术在 Scala 3 中得到了改进,几乎完全无缝 + +有关其工作原理的更多详细信息,请参阅参考文档中的 [Eta 扩展页面][eta_expansion]。 + +[eta_expansion]: {{ site.scala3ref }}/changed-features/eta-expansion.html +[extension]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[toplevel]: {% link _zh-cn/overviews/scala3-book/taste-toplevel-definitions.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-function-variables.md b/_zh-cn/overviews/scala3-book/fun-function-variables.md new file mode 100644 index 0000000000..e4591ef2ee --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-function-variables.md @@ -0,0 +1,166 @@ +--- +title: 函数变量 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +language: zh-cn +num: 30 +previous-page: fun-anonymous-functions +next-page: fun-eta-expansion + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +从上一节回到这个例子: + +{% tabs fun-function-variables-1 %} +{% tab 'Scala 2 and 3' %} +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` +{% endtab %} +{% endtabs %} + +我们注意到这部分表达式是一个匿名函数: + +{% tabs fun-function-variables-2 %} +{% tab 'Scala 2 and 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +它被称为 *匿名* 的原因是它没有分配给变量,因此没有名称。 + +但是,可以将匿名函数(也称为*函数字面量*)分配给变量以创建*函数变量*: + +{% tabs fun-function-variables-3 %} +{% tab 'Scala 2 and 3' %} +```scala +val double = (i: Int) => i * 2 +``` +{% endtab %} +{% endtabs %} + +这将创建一个名为 `double` 的函数变量。 +在这个表达式中,原始函数字面量在 `=` 符号的右侧: + +{% tabs fun-function-variables-4 %} +{% tab 'Scala 2 and 3' %} +```scala +val double = (i: Int) => i * 2 + ----------------- +``` +{% endtab %} +{% endtabs %} + +新变量名在左侧: + +{% tabs fun-function-variables-5 %} +{% tab 'Scala 2 and 3' %} +```scala +val double = (i: Int) => i * 2 + ------ +``` +{% endtab %} +{% endtabs %} + +并且函数的参数列表在此处加下划线: + +{% tabs fun-function-variables-6 %} +{% tab 'Scala 2 and 3' %} +```scala +val double = (i: Int) => i * 2 + -------- +``` +{% endtab %} +{% endtabs %} + +就像方法的参数列表一样,这意味着 `double` 函数有一个参数,一个名为 `i` 的 `Int`。 +你可以在 REPL 中看到 `double` 的类型为 `Int => Int`,这意味着它接受一个 `Int` 参数并返回一个 `Int`: + +{% tabs fun-function-variables-7 %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val double = (i: Int) => i * 2 +val double: Int => Int = ... +``` +{% endtab %} +{% endtabs %} + +### 调用函数 + +现在你可以像这样调用`double`函数: + +{% tabs fun-function-variables-8 %} +{% tab 'Scala 2 and 3' %} +```scala +val x = double(2) // 4 +``` +{% endtab %} +{% endtabs %} + +您还可以将 `double` 传递给 `map` 调用: + +{% tabs fun-function-variables-9 %} +{% tab 'Scala 2 and 3' %} +```scala +List(1, 2, 3).map(double) // List(2, 4, 6) +``` +{% endtab %} +{% endtabs %} + +此外,当您有 `Int => Int` 类型的其他函数时: + +{% tabs fun-function-variables-10 %} +{% tab 'Scala 2 and 3' %} +```scala +val triple = (i: Int) => i * 3 +``` +{% endtab %} +{% endtabs %} + +您可以将它们存储在 `List` 或 `Map` 中: + +{% tabs fun-function-variables-11 %} +{% tab 'Scala 2 and 3' %} +```scala +val functionList = List(double, triple) + +val functionMap = Map( + "2x" -> double, + "3x" -> triple +) +``` +{% endtab %} +{% endtabs %} + +如果将这些表达式粘贴到 REPL 中,您会看到它们具有以下类型: + +{% tabs fun-function-variables-12 %} +{% tab 'Scala 2 and 3' %} +```` +// a List that contains functions of the type `Int => Int` +functionList: List[Int => Int] + +// a Map whose keys have the type `String`, and whose +// values have the type `Int => Int` +functionMap: Map[String, Int => Int] +```` +{% endtab %} +{% endtabs %} + +## 关键点 + +这里的重要部分是: + +- 要创建函数变量,只需将变量名分配给函数字面量 +- 一旦你有了一个函数,你可以像对待任何其他变量一样对待它,即像一个`String`或`Int`变量 + +并且由于 Scala 3 中改进的 [Eta Expansion][eta_expansion] 函数式,您可以以相同的方式处理 *方法*。 + +[eta_expansion]: {% link _zh-cn/overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-hofs.md b/_zh-cn/overviews/scala3-book/fun-hofs.md new file mode 100644 index 0000000000..a0fda317b8 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-hofs.md @@ -0,0 +1,384 @@ +--- +title: 高阶函数 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +language: zh-cn +num: 32 +previous-page: fun-eta-expansion +next-page: fun-write-map-function + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +高阶函数 (HOF) 通常定义为这类函数,它 (a) 将其他函数作为输入参数或 (b) 返回函数作为结果。 +在 Scala 中,HOF 是可能的,因为函数是一等值。 + +需要注意的是,虽然我们在本文档中使用了常见的行业术语“高阶函数”,但在 Scala 中,该短语同时适用于 *方法* 和 *函数*。 +得益于 Scala 的 [Eta 扩展技术][eta_expansion],它们通常可以在相同的地方使用。 + +## 从消费者到创造者 + +在本书到目前为止的示例中,您已经了解了如何成为方法的*消费者*,该方法将其他函数作为输入参数,例如使用诸如 `map` 和 `filter` 之类的 HOF。 +在接下来的几节中,您将了解如何成为 HOF 的*创造者*,包括: + +- 如何编写将函数作为输入参数的方法 +- 如何从方法中返回函数 + +在这个过程中你会看到: + +- 用于定义函数输入参数的语法 +- 引用函数后如何调用它 + +作为本次讨论的一个有益的副作用,一旦您对这种语法感到满意,您将使用它来定义函数参数、匿名函数和函数变量,并且也更容易阅读有关高阶函数的 Scaladoc。 + +## 理解过滤器的 Scaladoc + +要了解高阶函数的工作原理,深入研究一个示例会有所帮助。 +例如,您可以通过查看 Scaladoc 来了解 `filter` 接受的函数类型。 +这是 `List[A]` 类中的 `filter` 定义: + +{% tabs filter-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def filter(p: (A) => Boolean): List[A] +``` +{% endtab %} +{% endtabs %} + +这表明 `filter` 是一个接受名为 `p` 的函数参数的方法。 +按照惯例,`p` 代表 *谓词(predicate)*,它只是一个返回 `Boolean` 值的函数。 +所以 `filter` 将谓词 `p` 作为输入参数,并返回一个 `List[A]`,其中 `A` 是列表中保存的类型;如果你在 `List[Int]` 上调用 `filter`,`A` 是 `Int` 类型。 + +在这一点上,如果你不知道 `filter` 方法的用途,你只知道它的算法以某种方式使用谓词 `p` 创建并返回 `List[A]`。 + +具体看函数参数 `p`,这部分 `filter` 的描述: + +{% tabs filter-definition_1 %} +{% tab 'Scala 2 and 3' %} +```scala +p: (A) => Boolean +``` +{% endtab %} +{% endtabs %} + +意味着您传入的任何函数都必须将类型 `A` 作为输入参数并返回一个 `Boolean` 。 +因此,如果您的列表是 `List[Int]`,则可以将通用类型 `A` 替换为 `Int`,并像这样读取该签名: + +{% tabs filter-definition_2 %} +{% tab 'Scala 2 and 3' %} +```scala +p: (Int) => Boolean +``` +{% endtab %} +{% endtabs %} + +因为 `isEven` 具有这种类型——它将输入 `Int` 转换为结果 `Boolean`——它可以与 `filter` 一起使用。 + +{% comment %} +NOTE: (A low-priority issue): The next several sections can be condensed. +{% endcomment %} + +## 编写接受函数参数的方法 + +鉴于此背景,让我们开始编写将函数作为输入参数的方法。 + +**注意:**为了使下面的讨论更清楚,我们将您编写的代码称为*方法*,将您作为输入参数接受的代码称为*函数*。 + +### 第一个例子 + +要创建一个接受函数参数的方法,您所要做的就是: + +1. 在方法的参数列表中,定义要接受的函数的签名 +2. 在你的方法中使用那个函数 + +为了证明这一点,这里有一个方法,它接受一个名为 `f` 的输入参数,其中 `f` 是一个函数: + +{% tabs sayHello-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def sayHello(f: () => Unit): Unit = f() +``` +{% endtab %} +{% endtabs %} + +这部分代码---*类型签名*---声明 `f` 是一个函数,并定义了 `sayHello` 方法将接受的函数类型: + +{% tabs sayHello-definition_1 %} +{% tab 'Scala 2 and 3' %} +```scala +f: () => Unit +``` +{% endtab %} +{% endtabs %} + +这是它的工作原理: + +- `f` 是函数输入参数的名称。 + 这就像将 `String` 参数命名为 `s` 或 `Int` 参数命名为 `i`。 +- `f` 的类型签名指定此方法将接受的函数的 *类型*。 +- `f` 签名的 `()` 部分(在 `=>` 符号的左侧)表明 `f` 不接受输入参数。 +- 签名的 `Unit` 部分(在 `=>` 符号的右侧)表示 `f` 不应返回有意义的结果。 +- 回顾 `sayHello` 方法的主体(在 `=` 符号的右侧),那里的 `f()` 语句调用传入的函数。 + +现在我们已经定义了 `sayHello`,让我们创建一个函数来匹配 `f` 的签名,以便我们可以测试它。 +以下函数不接受任何输入参数并且不返回任何内容,因此它匹配 `f` 的类型签名: + +{% tabs helloJoe-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def helloJoe(): Unit = println("Hello, Joe") +``` +{% endtab %} +{% endtabs %} + +因为类型签名匹配,你可以将 `helloJoe` 传递给 `sayHello`: + +{% tabs sayHello-usage %} +{% tab 'Scala 2 and 3' %} +```scala +sayHello(helloJoe) // prints "Hello, Joe" +``` +{% endtab %} +{% endtabs %} + +如果您以前从未这样做过,那么恭喜您: +您刚刚定义了一个名为 `sayHello` 的方法,它接受一个函数作为输入参数,然后在其方法体中调用该函数。 + +### sayHello 可以带很多函数 + +重要的是要知道这种方法的美妙之处并不是说​​ `sayHello` 可以将 *一个* 函数作为输入参数;而在于它可以采用与 `f` 签名匹配的 *任意一个* 函数。 +例如,因为下一个函数没有输入参数并且不返回任何内容,所以它也适用于 `sayHello`: + +{% tabs bonjourJulien-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def bonjourJulien(): Unit = println("Bonjour, Julien") +``` +{% endtab %} +{% endtabs %} + +它在 REPL 中: + +{% tabs bonjourJulien-usage %} +{% tab 'Scala 2 and 3' %} +```` +scala> sayHello(bonjourJulien) +Bonjour, Julien +```` +{% endtab %} +{% endtabs %} + +这是一个好的开始。 +现在唯一要做的就是查看更多示例,了解如何为函数参数定义不同的类型签名。 + +## 定义函数输入参数的通用语法 + +在这种方法中: + +{% tabs sayHello-definition-2 %} +{% tab 'Scala 2 and 3' %} +```scala +def sayHello(f: () => Unit): Unit +``` +{% endtab %} +{% endtabs %} + +我们注意到 `f` 的类型签名是: + +{% tabs sayHello-definition-2_1 %} +{% tab 'Scala 2 and 3' %} +```scala +() => Unit +``` +{% endtab %} +{% endtabs %} + +我们知道这意味着,“一个没有输入参数并且不返回任何有意义的东西的函数(由 `Unit` 给出)。” + +为了演示更多类型签名示例,这里有一个函数,它接受一个 `String` 参数并返回一个 `Int`: + +{% tabs sayHello-definition-2_2 %} +{% tab 'Scala 2 and 3' %} +```scala +f: (String) => Int +``` +{% endtab %} +{% endtabs %} + +什么样的函数接受一个字符串并返回一个整数? +“字符串长度”和校验和等函数就是两个例子。 + +同样,此函数接受两个 `Int` 参数并返回一个 `Int`: + +{% tabs sayHello-definition-2_3 %} +{% tab 'Scala 2 and 3' %} +```scala +f: (Int, Int) => Int +``` +{% endtab %} +{% endtabs %} + +你能想象什么样的函数匹配那个签名? + +答案是任何接受两个 `Int` 输入参数并返回 `Int` 的函数都与该签名匹配,因此所有这些“函数”(实际上是方法)都是匹配的: + +{% tabs add-sub-mul-definitions %} +{% tab 'Scala 2 and 3' %} +```scala +def add(a: Int, b: Int): Int = a + b +def subtract(a: Int, b: Int): Int = a - b +def multiply(a: Int, b: Int): Int = a * b +``` +{% endtab %} +{% endtabs %} + +正如您可以从这些示例中推断出的,定义函数参数类型签名的一般语法是: + +{% tabs add-sub-mul-definitions_1 %} +{% tab 'Scala 2 and 3' %} +```scala +variableName: (parameterTypes ...) => returnType +``` +{% endtab %} +{% endtabs %} + +> 因为函数式编程就像创建和组合一系列代数方程,所以在设计函数和应用程序时通常会考虑*很多*类型。 +> 你可能会说你“在类型中思考”。 + +## 将函数参数与其他参数一起使用 + +为了使 HOF 真正有用,它们还需要一些数据来处理。 +对于像 `List` 这样的类,它的 `map` 方法已经有数据可以处理:`List` 中的数据。 +但是对于没有自己数据的独立 HOF,它也应该接受数据作为其他输入参数。 + +例如,这是一个名为 `executeNTimes` 的方法,它有两个输入参数:一个函数和一个 `Int`: + +{% tabs executeNTimes-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for (i <- 1 to n) f() +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for i <- 1 to n do f() +``` +{% endtab %} +{% endtabs %} + +如代码所示,`executeNTimes` 执行了`f` 函数 `n` 次。 +因为像这样的简单 `for` 循环没有返回值,`executeNTimes` 返回 `Unit`。 + +要测试 `executeNTimes`,请定义一个匹配 `f` 签名的方法: + +{% tabs helloWorld-definition %} +{% tab 'Scala 2 and 3' %} +```scala +// a method of type `() => Unit` +def helloWorld(): Unit = println("Hello, world") +``` +{% endtab %} +{% endtabs %} + +然后将该方法与 `Int` 一起传递给`executeNTimes`: + +{% tabs helloWorld-usage %} +{% tab 'Scala 2 and 3' %} +```` +scala> executeNTimes(helloWorld, 3) +Hello, world +Hello, world +Hello, world +```` +{% endtab %} +{% endtabs %} + +优秀。 +`executeNTimes` 方法执行 `helloWorld` 函数 3 次。 + +### 需要多少参数 + +您的方法可以继续变得尽可能复杂。 +例如,此方法采用类型为 `(Int, Int) => Int` 的函数,以及两个输入参数: + +{% tabs executeAndPrint-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = + println(f(i, j)) +``` +{% endtab %} +{% endtabs %} + +因为这些 `sum` 和 `multiply` 方法与该类型签名匹配,所以它们可以与两个 `Int` 值一起传递到 `executeAndPrint` 中: + +{% tabs executeAndPrint-usage %} +{% tab 'Scala 2 and 3' %} +```scala +def sum(x: Int, y: Int) = x + y +def multiply(x: Int, y: Int) = x * y + +executeAndPrint(sum, 3, 11) // prints 14 +executeAndPrint(multiply, 3, 9) // prints 27 +``` +{% endtab %} +{% endtabs %} + +## 函数类型签名一致性 + +学习 Scala 的函数类型签名的一个好处是,用于定义函数输入参数的语法与用于编写函数字面量的语法相同。 + +例如,如果你要编写一个计算两个整数之和的函数,你可以这样写: + +{% tabs f-val-definition %} +{% tab 'Scala 2 and 3' %} +```scala +val f: (Int, Int) => Int = (a, b) => a + b +``` +{% endtab %} +{% endtabs %} + +该代码由类型签名组成: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +输入参数: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ------ +```` + +和函数体: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----- +```` + +这里展示了 Scala 的一致性,这里的函数类型: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +与用于定义函数输入参数的类型签名相同: + +```` +def executeAndPrint(f: (Int, Int) => Int, ... + ----------------- +```` + +一旦你熟悉了这种语法,你就会用它来定义函数参数、匿名函数和函数变量,而且当你阅读 Scaladoc 中有关高阶函数的内容时,这些内容变得更容易了。 + +[eta_expansion]: {% link _zh-cn/overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-intro.md b/_zh-cn/overviews/scala3-book/fun-intro.md new file mode 100644 index 0000000000..90d45c4387 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-intro.md @@ -0,0 +1,17 @@ +--- +title: 函数 +type: chapter +description: This chapter looks at all topics related to functions in Scala 3. +language: zh-cn +num: 28 +previous-page: methods-summary +next-page: fun-anonymous-functions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +上一章介绍了 Scala *方法*,本章深入研究 *函数*。 +涵盖的主题包括匿名函数、函数变量和高阶函数 (HOF),包括如何创建自己的 HOF。 diff --git a/_zh-cn/overviews/scala3-book/fun-summary.md b/_zh-cn/overviews/scala3-book/fun-summary.md new file mode 100644 index 0000000000..efc747c7ce --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-summary.md @@ -0,0 +1,40 @@ +--- +title: 总结 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +language: zh-cn +num: 35 +previous-page: fun-write-method-returns-function +next-page: packaging-imports + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +这是一个很长的章节,所以让我们回顾一下所涵盖的关键点。 + +我们通常这样定义高阶函数 (HOF),它以其他函数作为输入参数或将函数作为其值。 +在 Scala 中这是可能的,因为函数是一等值。 + +浏览这些部分,首先您会看到: + +- 您可以将匿名函数编写为小代码片段 +- 您可以将它们传递给集合类上的几十个 HOF(方法),即像 `filter`、`map` 等方法。 +- 使用这些小代码片段和强大的 HOF,您只需少量代码即可创建大量的函数 + +在查看了匿名函数和 HOF 之后,您看到了: + +- 函数变量只是绑定到变量的匿名函数 + +在了解如何成为 HOF 的*消费者*之后,您将了解如何成为 HOF 的*创造者*。 +具体来说,您看到了: + +- 如何编写将函数作为输入参数的方法 +- 如何从方法中返回函数 + +本章的一个有益的副作用是您看到了许多关于如何为函数声明类型签名的示例。 +这样做的好处是,您可以使用相同的语法来定义函数参数、匿名函数和函数变量,而且对于 `map`、`filter` 等高阶函数,阅读 Scaladoc 也变得更容易。 + diff --git a/_zh-cn/overviews/scala3-book/fun-write-map-function.md b/_zh-cn/overviews/scala3-book/fun-write-map-function.md new file mode 100644 index 0000000000..3ba7e44c1b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-write-map-function.md @@ -0,0 +1,140 @@ +--- +title: 自定义 map 函数 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +language: zh-cn +num: 33 +previous-page: fun-hofs +next-page: fun-write-method-returns-function + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +现在您已经了解了如何编写自己的高阶函数,让我们快速浏览一个更真实的示例。 + +想象一下,`List` 类没有自己的 `map` 方法,而您想编写自己的方法。 +创建函数的第一步是准确地陈述问题。 +只关注 `List[Int]`,你说: + +> 我想编写一个 `map` 方法,该方法可用于将函数应用于给定的 `List[Int]` 中的每个元素,并将转换后的元素作为新列表返回。 + +鉴于该声明,您开始编写方法签名。 +首先,您知道您想接受一个函数作为参数,并且该函数应该将 `Int` 转换为某种通用类型 `A`,因此您编写: + +{% tabs map-accept-func-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def map(f: (Int) => A) +``` +{% endtab %} +{% endtabs %} + +使用泛型类型的语法要求在参数列表之前声明该类型符号,因此您添加: + +{% tabs map-type-symbol-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def map[A](f: (Int) => A) +``` +{% endtab %} +{% endtabs %} + +接下来,您知道 `map` 也应该接受 `List[Int]`: + +{% tabs map-list-int-param-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]) +``` +{% endtab %} +{% endtabs %} + +最后,您还知道 `map` 返回一个转换后的 `List`,其中包含泛型类型 `A` 的元素: + +{% tabs map-with-return-type-definition %} +{% tab 'Scala 2 and 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = ??? +``` +{% endtab %} +{% endtabs %} + +这负责方法签名。 +现在您所要做的就是编写方法体。 +`map` 方法将它赋予的函数应用于它赋予的列表中的每个元素,以生成一个新的、转换的列表。 +一种方法是使用 `for` 表达式: + +{% tabs for-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + +`for` 表达式通常使代码出奇地简单,对于我们的目的,它最终成为整个方法体。 + +把它和方法签名放在一起,你现在有了一个独立的 `map` 方法,它与 `List[Int]` 一起工作: + +{% tabs map-function class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + +### 使其泛型化 + +作为奖励,请注意 `for` 表达式不做任何取决于 `List` 中的类型为 `Int` 的事情。 +因此,您可以将类型签名中的 `Int` 替换为泛型类型参数 `B`: + +{% tabs map-function-full-generic class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for (x <- xs) yield f(x) +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for x <- xs yield f(x) +``` +{% endtab %} +{% endtabs %} + +现在你有了一个适用于任何 `List` 的 `map` 方法。 + +这些示例表明 `map` 可以按需要工作: + +{% tabs map-use-example %} +{% tab 'Scala 2 and 3' %} +```scala +def double(i : Int) = i * 2 +map(double, List(1, 2, 3)) // List(2, 4, 6) + +def strlen(s: String) = s.length +map(strlen, List("a", "bb", "ccc")) // List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +现在您已经了解了如何编写接受函数作为输入参数的方法,让我们看看返回函数的方法。 + diff --git a/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md b/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md new file mode 100644 index 0000000000..84e3460b6f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md @@ -0,0 +1,244 @@ +--- +title: 创建可以返回函数的方法 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +language: zh-cn +num: 34 +previous-page: fun-write-map-function +next-page: fun-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +由于 Scala 的一致性,编写一个返回函数的方法与您在前几节中看到的所有内容相似。 +例如,假设您想编写一个返回函数的 `greet` 方法。 +我们再次从问题陈述开始: + +> 我想创建一个返回函数的 `greet` 方法。 +> 该函数将接受一个字符串参数并使用 `println` 打印它。 +> 为了简化第一个示例,`greet` 不会接受任何输入参数;它只会构建一个函数并返回它。 + +鉴于该声明,您可以开始构建 `greet`。 +你知道这将是一种方法: + +{% tabs fun-write-method-returns-function-1 %} +{% tab 'Scala 2 and 3' %} +```scala +def greet() +``` +{% endtab %} +{% endtabs %} + +您还知道此方法将返回一个函数,该函数 (a) 采用 `String` 参数,并且 (b) 使用 `println` 打印该字符串。 +因此,该函数的类型为 `String => Unit`: + +{% tabs fun-write-method-returns-function-2 %} +{% tab 'Scala 2 and 3' %} +```scala +def greet(): String => Unit = ??? + ---------------- +``` +{% endtab %} +{% endtabs %} + +现在你只需要一个方法体。 +您知道该方法需要返回一个函数,并且该函数接受一个“字符串”并打印它。 +此匿名函数与该描述匹配: + +{% tabs fun-write-method-returns-function-3 %} +{% tab 'Scala 2 and 3' %} +```scala +(name: String) => println(s"Hello, $name") +``` +{% endtab %} +{% endtabs %} + +现在您只需从方法中返回该函数: + +{% tabs fun-write-method-returns-function-4 %} +{% tab 'Scala 2 and 3' %} +```scala +// a method that returns a function +def greet(): String => Unit = + (name: String) => println(s"Hello, $name") +``` +{% endtab %} +{% endtabs %} + +因为这个方法返回一个函数,所以你可以通过调用`greet()`来得到这个函数。 +这是在 REPL 中做的一个很好的步骤,因为它验证了新函数的类型: + +{% tabs fun-write-method-returns-function-5 %} +{% tab 'Scala 2 and 3' %} +```` +scala> val greetFunction = greet() +val greetFunction: String => Unit = Lambda.... + ----------------------------------------- +```` +{% endtab %} +{% endtabs %} + +现在你可以调用`greetFunction`了: + +{% tabs fun-write-method-returns-function-6 %} +{% tab 'Scala 2 and 3' %} +```scala +greetFunction("Joe") // prints "Hello, Joe" +``` +{% endtab %} +{% endtabs %} + +恭喜,您刚刚创建了一个返回函数的方法,然后执行了该函数。 + +## 改进方法 + +如果您可以传递问候语,我们的方法会更有用,所以让我们这样做。 +您所要做的就是将问候语作为参数传递给 `greet` 方法,并在 `println` 中的字符串中使用它: + +{% tabs fun-write-method-returns-function-7 %} +{% tab 'Scala 2 and 3' %} +```scala +def greet(theGreeting: String): String => Unit = + (name: String) => println(s"$theGreeting, $name") +``` +{% endtab %} +{% endtabs %} + +现在,当您调用您的方法时,该过程更加灵活,因为您可以更改问候语。 +当您从此方法创建函数时,它是这样的: + +{% tabs fun-write-method-returns-function-8 %} +{% tab 'Scala 2 and 3' %} +```` +scala> val sayHello = greet("Hello") +val sayHello: String => Unit = Lambda..... + ---------------------- +```` +{% endtab %} +{% endtabs %} + +REPL 类型签名输出显示 `sayHello` 是一个接受 `String` 输入参数并返回 `Unit`(无)的函数。 +所以现在当你给 `sayHello` 一个 `String` 时,它会打印问候语: + +{% tabs fun-write-method-returns-function-9 %} +{% tab 'Scala 2 and 3' %} +```scala +sayHello("Joe") // prints "Hello, Joe" +``` +{% endtab %} +{% endtabs %} + +您还可以根据需要更改问候语以创建新函数: + +{% tabs fun-write-method-returns-function-10 %} +{% tab 'Scala 2 and 3' %} +```scala +val sayCiao = greet("Ciao") +val sayHola = greet("Hola") + +sayCiao("Isabella") // prints "Ciao, Isabella" +sayHola("Carlos") // prints "Hola, Carlos" +``` +{% endtab %} +{% endtabs %} + +## 一个更真实的例子 + +当您的方法返回许多可能的函数之一时,这种技术会更加有用,例如返回自定义构建函数的工厂。 + +例如,假设您想编写一个方法,该方法返回用不同语言问候人们的函数。 +我们将其限制为使用英语或法语问候的函数,具体取决于传递给方法的参数。 + +您知道的第一件事是,您想要创建一个方法,该方法 (a) 将“所需语言”作为输入,并且 (b) 返回一个函数作为其结果。 +此外,由于该函数会打印给定的字符串,因此您知道它的类型为 `String => Unit`。 +使用该信息编写方法签名: + +{% tabs fun-write-method-returns-function-11 %} +{% tab 'Scala 2 and 3' %} +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = ??? +``` +{% endtab %} +{% endtabs %} + +接下来,因为您知道可能返回的函数接受一个字符串并打印它,所以您可以为英语和法语编写两个匿名函数: + +{% tabs fun-write-method-returns-function-12 %} +{% tab 'Scala 2 and 3' %} +```scala +(name: String) => println(s"你好,$name") +(name: String) => println(s"Bonjour, $name") +``` +{% endtab %} +{% endtabs %} + +在方法内部,如果你给这些匿名函数起一些名字,它可能会更易读,所以让我们将它们分配给两个变量: + +{% tabs fun-write-method-returns-function-13 %} +{% tab 'Scala 2 and 3' %} +```scala +val englishGreeting = (name: String) => println(s"Hello, $name") +val frenchGreeting = (name: String) => println(s"Bonjour, $name") +``` +{% endtab %} +{% endtabs %} + +现在您需要做的就是 (a) 如果 `desiredLanguage` 是英语,则返回 `englishGreeting`,并且 (b) 如果 `desiredLanguage` 是法语,则返回 `frenchGreeting`。 +一种方法是使用 `match` 表达式: + +{% tabs fun-write-method-returns-function-14 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = { + val englishGreeting = (name: String) => println(s"Hello, $name") + val frenchGreeting = (name: String) => println(s"Bonjour, $name") + desiredLanguage match { + case "english" => englishGreeting + case "french" => frenchGreeting + } +} +``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = + val englishGreeting = (name: String) => println(s"Hello, $name") + val frenchGreeting = (name: String) => println(s"Bonjour, $name") + desiredLanguage match + case "english" => englishGreeting + case "french" => frenchGreeting +``` +{% endtab %} +{% endtabs %} + +这是最后的方法。 +请注意,从方法返回函数值与返回字符串或整数没有什么不同呃值。 + +这就是 `createGreetingFunction` 构建法语问候函数的方式: + +{% tabs fun-write-method-returns-function-15 %} +{% tab 'Scala 2 and 3' %} +```scala +val greetInFrench = createGreetingFunction("french") +greetInFrench("Jonathan") // prints "Bonjour, Jonathan" +``` +{% endtab %} +{% endtabs %} + +这就是它构建英语问候功能的方式: + +{% tabs fun-write-method-returns-function-16 %} +{% tab 'Scala 2 and 3' %} +```scala +val greetInEnglish = createGreetingFunction("english") +greetInEnglish("Joe") // prints "Hello, Joe" +``` +{% endtab %} +{% endtabs %} + +如果你对这段代码感到满意——恭喜——你现在知道如何编写返回函数的方法了。 + diff --git a/_zh-cn/overviews/scala3-book/interacting-with-java.md b/_zh-cn/overviews/scala3-book/interacting-with-java.md new file mode 100644 index 0000000000..f2263d0bd2 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/interacting-with-java.md @@ -0,0 +1,343 @@ +--- +title: 与 Java 交互 +type: chapter +description: This page demonstrates how Scala code can interact with Java, and how Java code can interact with Scala code. +language: zh-cn +num: 72 +previous-page: tools-worksheets +next-page: scala-for-java-devs + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +## 介绍 + +本节介绍如何在 Scala 中使用 Java 代码,以及如何在 Java 中使用 Scala 代码。 + +一般来说,在 Scala 中使用 Java 代码是非常无缝的。 +只有几个地方需要使用 Scala 实用程序将 Java 概念转换为 Scala,包括: + +- Java 集合类 +- Java `Optional` 类 + +同样,如果您正在编写 Java 代码并想使用 Scala 概念,则需要转换 Scala 集合和 Scala `Option` 类。 + +以下部分演示了您需要的最常见的转换: + +- 如何在 Scala 中使用 Java 集合 +- 如何在 Scala 中使用 Java `Optional` +- 在 Scala 中扩展 Java 接口 +- 如何在 Java 中使用 Scala 集合 +- 如何在 Java 中使用 Scala `Option` +- 如何在 Java 中使用 Scala traits +- 如何在 Java 代码中处理 Scala 方法引发的异常 +- 如何在 Java 中使用 Scala 可变参数 +- 创建备用名称以在 Java 中使用 Scala 方法 + +请注意,本节中的 Java 示例假设您使用的是 Java 11 或更高版本。 + +## 如何在 Scala 中使用 Java 集合 + +当您编写 Scala 代码并需要使用 Java 集合类时,您_可以_按原样使用该类。 +但是,如果您想在 Scala `for` 循环中使用该类,或者想利用 Scala 集合类中的高阶函数,则需要将 Java 集合转换为 Scala 集合。 + +这是一个如何工作的例子。 +假定这个例子是 Java `ArrayList`: + +```java +// java +public class JavaClass { + public static List getStrings() { + return new ArrayList(List.of("a", "b", "c")); + } +} +``` + +您可以使用 Scala _scala.jdk.CollectionConverters_ 包中的转换实用程序将该 Java 列表转换为 Scala `Seq`: + +```scala +// scala +import scala.jdk.CollectionConverters.* + +def testList() = + println("Using a Java List in Scala") + val javaList: java.util.List[String] = JavaClass.getStrings() + val scalaSeq: Seq[String] = javaList.asScala.toSeq + scalaSeq.foreach(println) + for s <- scalaSeq do println(s) +``` + +当然,该代码可以缩短,但此处显示的各个步骤是为了准确演示转换过程是如何工作的。 + +## 如何在 Scala 中使用 Java `Optional` + +当您需要在 Scala 代码中使用 Java `Optional` 类时,导入 _scala.jdk.OptionConverters_ 对象,然后使用 `toScala` 方法将 `Optional` 值转换为 Scala `Option`。 + +为了证明这一点,这里有一个带有两个 `Optional` 值的 Java 类,一个包含字符串,另一个为空: + +```java +// java +import java.util.Optional; + +public class JavaClass { + static Optional oString = Optional.of("foo"); + static Optional oEmptyString = Optional.empty(); +} +``` + +现在在您的 Scala 代码中,您可以访问这些字段。 +如果您只是直接访问它们,它们都将是 `Optional` 值: + +```scala +// scala +import java.util.Optional + +val optionalString = JavaClass.oString // Optional[foo] +val eOptionalString = JavaClass.oEmptyString // Optional.empty +``` + +但是通过使用 _scala.jdk.OptionConverters_ 方法,您可以将它们转换为 Scala `Option` 值: + +```scala +import java.util.Optional +import scala.jdk.OptionConverters.* + +val optionalString = JavaClass.oString // Optional[foo] +val optionString = optionalString.toScala // Some(foo) + +val eOptionalString = JavaClass.oEmptyString // Optional.empty +val eOptionString = eOptionalString.toScala // None +``` + +## 在 Scala 中扩展 Java 接口 + +如果您需要在 Scala 代码中使用 Java 接口,请将它们扩展为 Scala trait。 +例如,给定这三个 Java 接口: + +```java +// java +interface Animal { + void speak(); +} + +interface Wagging { + void wag(); +} + +interface Running { + // an implemented method + default void run() { + System.out.println("I’m running"); + } +} +``` + +你可以在 Scala 中创建一个 `Dog` 类,就像使用 trait 一样。 +你所要做的就是实现 `speak` 和 `wag` 方法: + +```scala +// scala +class Dog extends Animal, Wagging, Running: + def speak = println("Woof") + def wag = println("Tail is wagging") + +@main def useJavaInterfaceInScala = + val d = new Dog + d.speak + d.wag +``` + +## 如何在 Java 中使用 Scala 集合 + +当您需要在 Java 代码中使用 Scala 集合类时,请在 Java 代码中使用 Scala 的 `scala.jdk.javaapi.CollectionConverters` 对象的方法来进行转换。 +例如,如果您在 Scala 类中有这样的 `List[String]`: + +```scala +// scala +class ScalaClass: + val strings = List("a", "b") +``` + +您可以像这样在 Java 代码中访问该 Scala `List`: + +```java +// java +import scala.jdk.javaapi.CollectionConverters; + +// create an instance of the Scala class +ScalaClass sc = new ScalaClass(); + +// access the `strings` field as `sc.strings()` +scala.collection.immutable.List xs = sc.strings(); + +// convert the Scala `List` a Java `List` +java.util.List listOfStrings = CollectionConverters.asJava(xs); +listOfStrings.forEach(System.out::println); +``` + +该代码可以缩短,但显示了完整的步骤以演示该过程的工作原理。 +以下是该代码中需要注意的一些事项: + +- 在你的 Java 代码中,你创建一个 `ScalaClass` 的实例,就像一个 Java 类的实例一样 +- `ScalaClass` 有一个名为 `strings` 的字段,但在 Java 中,您可以将该字段作为方法访问,即,作为 `sc.strings()` + +## 如何在 Java 中使用 Scala `Option` + +当您需要在 Java 代码中使用 Scala `Option` 时,可以使用 Scala `scala.jdk.javaapi.OptionConverters` 对象的 `toJava` 方法将 `Option` 转换为 Java `Optional` 值。 + +为了证明这一点,创建一个具有两个 `Option[String]` 值的 Scala 类,一个包含字符串,另一个为空: + +```scala +// scala +object ScalaObject: + val someString = Option("foo") + val noneString: Option[String] = None +``` + +然后在您的 Java 代码中,使用来自 `scala.jdk.javaapi.OptionConverters` 对象的 `toJava` 方法将这些 `Option[String]` 值转换为 `java.util.Optional[String]`: + +```java +// java +import java.util.Optional; +import static scala.jdk.javaapi.OptionConverters.toJava; + +public class JUseScalaOptionInJava { + public static void main(String[] args) { + Optional stringSome = toJava(ScalaObject.someString()); // Optional[foo] + Optional stringNone = toJava(ScalaObject.noneString()); // Optional.empty + System.out.printf("stringSome = %s\n", stringSome); + System.out.printf("stringNone = %s\n", stringNone); + } +} +``` + +两个 Scala `Option` 字段现在可用作 Java `Optional` 值。 + +## 如何在 Java 中使用 Scala trait + +在 Java 11 中,您可以像使用 Java 接口一样使用 Scala trait,即使 trait 已经实现了方法。 +例如,给定这两个 Scala trait,一个具有已实现的方法,一个只有一个接口: + +```scala +// scala +trait ScalaAddTrait: + def sum(x: Int, y: Int) = x + y // implemented + +trait ScalaMultiplyTrait: + def multiply(x: Int, y: Int): Int // abstract +``` + +Java 类可以实现这两个接口,并定义 `multiply` 方法: + +```java +// java +class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait { + public int multiply(int a, int b) { + return a * b; + } +} + +JavaMath jm = new JavaMath(); +System.out.println(jm.sum(3,4)); // 7 +System.out.println(jm.multiply(3,4)); // 12 +``` + +## 如何处理在 Java 代码中抛出异常的 Scala 方法 + +当您使用 Scala 编程习惯编写 Scala 代码时,您永远不会编写引发异常的方法。 +但是如果由于某种原因你有一个抛出异常的 Scala 方法,并且你希望 Java 开发人员能够使用该方法,请在你的 Scala 方法中添加`@throws`注解,以便 Java 使用者知道它可以抛出的异常. + +例如,注解这个 Scala `exceptionThrower` 方法抛出一个 `Exception`: + +```scala +// scala +object SExceptionThrower: + @throws(classOf[Exception]) + def exceptionThrower = + throw new Exception("Idiomatic Scala methods don’t throw exceptions") +``` + +因此,您需要处理 Java 代码中的异常。 +例如,这段代码不会编译,因为我不处理异常: + +```java +// java: won’t compile because the exception isn’t handled +public class ScalaExceptionsInJava { + public static void main(String[] args) { + SExceptionThrower.exceptionThrower(); + } +} +``` + +编译器给出了这个错误: + +```` +[error] ScalaExceptionsInJava: unreported exception java.lang.Exception; + must be caught or declared to be thrown +[error] SExceptionThrower.exceptionThrower() +```` + +这很好——这就是你想要的:注解告诉 Java 编译器 `exceptionThrower` 可以抛出异常。 +现在,当您编写 Java 代码时,您必须使用 `try` 块处理异常或声明您的 Java 方法抛出异常。 + +相反,如果你 Scala `exceptionThrower` 方法的注释,Java 代码_将编译_。 +这可能不是您想要的,因为 Java 代码可能无法考虑引发异常的 Scala 方法。 + +## 如何在 Java 中使用 Scala 可变参数 + +当 Scala 方法具有可变参数并且您想在 Java 中使用该方法时,请使用 `@varargs` 注解来标记 Scala 方法。 +例如,这个 Scala 类中的 `printAll` 方法声明了一个 `String*` 可变参数字段: + +```scala +// scala +import scala.annotation.varargs + +object VarargsPrinter: + @varargs def printAll(args: String*): Unit = args.foreach(println) +``` + +因为 `printAll` 是用 `@varargs` 注释声明的,所以可以从具有可变数量参数的 Java 程序中调用它,如下例所示: + +```scala +// java +public class JVarargs { + public static void main(String[] args) { + VarargsPrinter.printAll("Hello", "world"); + } +} +``` + +运行此代码时,结果在以下输出中: + +```` +Hello +world +```` + +## 创建备用名称以在 Java 中使用 Scala 方法 + +在 Scala 中,您可能希望使用符号字符创建方法名称: + +```scala +def +(a: Int, b: Int) = a + b +``` + +该方法名称在 Java 中不能很好地工作,但您可以在 Scala 3 中为该方法提供一个“替代”名称——别名——这将在 Java 中工作: + +```scala +import scala.annotation.targetName + +class Adder: + @targetName("add") def +(a: Int, b: Int) = a + b +``` + +现在在您的 Java 代码中,您可以使用别名方法名称 `add`: + +```scala +var adder = new Adder(); +int x = adder.add(1,1); +System.out.printf("x = %d\n", x); +``` diff --git a/_zh-cn/overviews/scala3-book/introduction.md b/_zh-cn/overviews/scala3-book/introduction.md index 21d8562dc2..2e82050a0e 100644 --- a/_zh-cn/overviews/scala3-book/introduction.md +++ b/_zh-cn/overviews/scala3-book/introduction.md @@ -2,11 +2,11 @@ title: 导言 type: chapter description: This page begins the overview documentation of the Scala 3 language. +language: zh-cn num: 1 previous-page: next-page: scala-features -scala3: true partof: scala3-book overview-name: "Scala 3 — Book" layout: multipage-overview @@ -17,14 +17,19 @@ permalink: "/zh-cn/scala3/book/:title.html" 本书的目标是提供对 Scala 语言的非正式介绍,并以相对轻松的方式涉及所有的 Scala 主题。 若您在阅读本书时想了解有关特定功能的更多信息,可以随时参阅[_参考文档_][reference],其中更详细地涵盖了 Scala 语言的许多新特性。 +
+  如果你有兴趣看本书的 Scala 2 版,你可以在这获得。 +我们目前在整合这两本书,你可以帮助我们。 +
+ 在本书中,我们希望证明 Scala 是一种优美的、富有表现力的编程语言。它具有简洁、现代的语法,支持函数式编程(FP)和面向对象编程(OOP),并提供安全的静态类型系统。 Scala 的语法和特性都经过了重新思考与公开辩论,并在2020年更新,比以往任何时候都更清晰、更容易理解。 本书首先在[“品味 Scala”部分][taste]对 Scala 的许多特性进行了一次“旋风之旅”。随后的章节会提供有关这些语言特性的更多详细信息。 -> 我们仍在撰写本书的过程中。 -> 您可以[帮助我们改进][contributing] +{% comment %} +We should have a link structure on the whole tour here +{% endcomment %} -[contributing]: {% link scala3/contribute-to-docs.md %} [reference]: {{ site.scala3ref }}/overview.html -[taste]: {% link _overviews/scala3-book/taste-intro.md %} +[taste]: {% link _zh-cn/overviews/scala3-book/taste-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/methods-intro.md b/_zh-cn/overviews/scala3-book/methods-intro.md new file mode 100644 index 0000000000..ec383b437e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-intro.md @@ -0,0 +1,22 @@ +--- +title: 方法 +type: chapter +description: This section introduces methods in Scala 3. +language: zh-cn +num: 24 +previous-page: domain-modeling-fp +next-page: methods-most + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在 Scala 2 中,_方法_可以在类、traits、对象、样例类和样例对象中定义。 +但它变得更好了:在Scala 3中,可以在这些结构的_外部_定义方法;我们说它们是“顶级”定义,因为它们没有嵌套在另一个定义中。 +简而言之,现在可以在任何地方定义方法。 + +方法的许多功能将在下一节中演示。 +由于 `main` 方法需要更多的解释,因此后面有单独部分对其进行描述。 diff --git a/_zh-cn/overviews/scala3-book/methods-main-methods.md b/_zh-cn/overviews/scala3-book/methods-main-methods.md new file mode 100644 index 0000000000..1b185854b1 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-main-methods.md @@ -0,0 +1,188 @@ +--- +title: main 方法 +type: section +description: This page describes how 'main' methods and the '@main' annotation work in Scala 3. +language: zh-cn +num: 26 +previous-page: methods-most +next-page: methods-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +
编写仅Scala 3 适用的一行程序
+ +Scala 3 提供了一种定义可以从命令行调用的程序的新方法:在方法中添加 `@main` 注释会将其变成可执行程序的入口点: + +{% tabs method_1 %} +{% tab 'Scala 3 Only' for=method_1 %} + +```scala +@main def hello() = println("Hello, world") +``` + +{% endtab %} +{% endtabs %} + +只需将该行代码保存在一个名为 *Hello.scala* 的文件中——文件名不必与方法名匹配——并使用 `scala` 运行它: + +```bash +$ scala Hello.scala +Hello, world +``` + +`@main` 注释方法可以写在顶层(如图所示),也可以写在静态可访问的对象中。 +在任何一种情况下,程序的名称都是方法的名称,没有任何对象前缀。 + +学习更多 `@main` 注解,可以阅读以下章节,或者看这个视频: + +
+ +
+ +### 命令行参数 + +使用这种方法,您的`@main` 方法可以处理命令行参数,并且这些参数可以有不同的类型。 +例如,给定这个 `@main` 方法,它接受一个 `Int`、一个 `String` 和一个可变参数 `String*` 参数: + +{% tabs method_2 %} +{% tab 'Scala 3 Only' for=method_2 %} + +```scala +@main def happyBirthday(age: Int, name: String, others: String*) = + val suffix = (age % 100) match + case 11 | 12 | 13 => "th" + case _ => (age % 10) match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + + val sb = StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do sb.append(" and ").append(other) + sb.toString +``` + +{% endtab %} +{% endtabs %} + +当你编译该代码时,它会创建一个名为 `happyBirthday` 的主程序,它的调用方式如下: + +``` +$ scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` + +如上所示,`@main` 方法可以有任意数量的参数。 +对于每个参数类型,必须是 `scala.util.CommandLineParser.FromString` 类型类的一个 [given实例]({% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %}),它将参数 `String` 转换为所需的参数类型。 +同样如图所示,主方法的参数列表可以以重复参数结尾,例如 `String*`,它接受命令行中给出的所有剩余参数。 + +从 `@main` 方法实现的程序检查命令行上是否有足够的参数来填充所有参数,以及参数字符串是否可以转换为所需的类型。 +如果检查失败,程序将终止并显示错误消息: + +``` +$ scala happyBirthday 22 +Illegal command line after first argument: more arguments expected + +$ scala happyBirthday sixty Fred +Illegal command line: java.lang.NumberFormatException: For input string: "sixty" +``` + +## 用户自定义的类型作为参数 + +正如上面指出的,编译器为参数类型寻找 `scala.util.CommandLineParser.FromString` 类型类 的given 实例。 +例如,我们自定义了一个 `Color`类型,并希望当以参数使用。你可以像以下代码这样使用: + +{% tabs method_3 %} +{% tab 'Scala 3 Only' for=method_3 %} + +```scala +enum Color: + case Red, Green, Blue + +given ComamndLineParser.FromString[Color] with + def fromString(value: String): Color = Color.valueOf(value) + +@main def run(color: Color): Unit = + println(s"The color is ${color.toString}") +``` + +{% endtab %} +{% endtabs %} + +这对于您程序中的自定义类型以及可能使用的其他库中的类型,都是一样的。 + +## 细节 + +Scala 编译器从 `@main` 方法 `f` 生成程序,如下所示: + +- 它在有 `@main` 方法的包中创建一个名为 `f` 的类。 +- 该类有一个静态方法 `main`,具有 Java `main` 方法的通常签名:它以 `Array[String]` 作为参数并返回 `Unit`。 +- 生成的 `main` 方法调用方法 `f` 并使用 `scala.util.CommandLineParser` 对象中的方法转换参数。 + +例如,上面的 `happyBirthday` 方法会生成与以下类等效的附加代码: + +{% tabs method_3 %} +{% tab 'Scala 3 Only' for=method_3 %} + +```scala +final class happyBirthday { + import scala.util.{CommandLineParser as CLP} + def main(args: Array[String]): Unit = + try + happyBirthday( + CLP.parseArgument[Int](args, 0), + CLP.parseArgument[String](args, 1), + CLP.parseRemainingArguments[String](args, 2)) + catch { + case error: CLP.ParseError => CLP.showError(error) + } +} +``` + +> **注意**:在这个生成的代码中,`` 修饰符表示 `main` 方法是作为 `happyBirthday` 类的静态方法生成的。 +> 此功能不适用于 Scala 中的用户程序。 +> 常规“静态”成员在 Scala 中使用对象生成。 + +{% endtab %} +{% endtabs %} + +## 与 Scala 2 的向后兼容性 + +`@main` 方法是在 Scala 3 中生成可以从命令行调用的程序的推荐方法。 +它们取代了 Scala 2 中以前的方法,即创建一个扩展 `App` 类的 `object` : + +之前依赖于“神奇”的 `DelayedInit` trait 的 `App` 功能不再可用。 +`App` 目前仍以有限的形式存在,但它不支持命令行参数,将来会被弃用。 + +如果程序需要在 Scala 2 和 Scala 3 之间交叉构建,建议使用带有 `Array[String]` 参数的显式 `main` 方法: + +{% tabs method_4 %} +{% tab 'Scala 2 and 3' %} + +```scala +object happyBirthday { + private def happyBirthday(age: Int, name: String, others: String*) = { + ... // same as before + } + def main(args: Array[String]): Unit = + happyBirthday(args(0).toInt, args(1), args.drop(2).toIndexedSeq:_*) +} +``` + +> 注意我们用 `:_*` 来传递不定数量的参数。为了保持向后兼容性,Scala 3 保持了这种用法。 + +{% endtab %} +{% endtabs %} + +如果将该代码放在名为 *happyBirthday.scala* 的文件中,则可以使用 `scalac` 编译它并使用 `scala` 运行它,如前所示: + +```bash +$ scalac happyBirthday.scala + +$ scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md new file mode 100644 index 0000000000..86d78f7960 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -0,0 +1,700 @@ +--- +title: 方法特性 +type: section +description: This section introduces Scala 3 methods, including main methods, extension methods, and more. +language: zh-cn +num: 25 +previous-page: methods-intro +next-page: methods-main-methods + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本节介绍如何在 Scala 3 中定义和调用方法的各个方面。 + +## 定义方法 + +Scala 方法有很多特性,包括: + +- 泛型(类型)参数 +- 参数的默认值 +- 多个参数组(柯里化) +- 上下文提供的参数 +- 传名参数 +- ... + +本节演示了其中一些功能,但是当您定义一个不使用这些功能的“简单”方法时,语法如下所示: + +{% tabs method_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = { + // the method body + // goes here +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +end methodName // this is optional +``` + +{% endtab %} +{% endtabs %} + +在该语法中: + +- 关键字 `def` 用于定义方法 +- Scala 标准是使用驼峰式命法来命名方法 +- 方法参数总是和它们的类型一起定义 +- 声明方法返回类型是可选的 +- 方法可以包含多行,也可以只包含一行 +- 在方法体之后提供 `end methodName` 部分也是可选的,仅推荐用于长方法 + +下面是一个名为 `add` 的单行方法的两个示例,它接受两个 `Int` 输入参数。 +第一个版本明确显示方法的 `Int` 返回类型,第二个版本没有: + +{% tabs method_2 %} +{% tab 'Scala 2 and 3' for=method_2 %} + +```scala +def add(a: Int, b: Int): Int = a + b +def add(a: Int, b: Int) = a + b +``` + +{% endtab %} +{% endtabs %} + +建议使用返回类型注释公开可见的方法。 +声明返回类型可以让您在几个月或几年后查看它或查看其他人的代码时更容易理解它。 + +## 调用方法 + +调用方法很简单: + +{% tabs method_3 %} +{% tab 'Scala 2 and 3' for=method_3 %} + +```scala +val x = add(1, 2) // 3 +``` + +{% endtab %} +{% endtabs %} + +Scala 集合类有几十个内置方法。 +这些示例显示了如何调用它们: + +{% tabs method_4 %} +{% tab 'Scala 2 and 3' for=method_4 %} + +```scala +val x = List(1, 2, 3) + +x.size // 3 +x.contains(1) // true +x.map(_ * 10) // List(10, 20, 30) +``` + +{% endtab %} +{% endtabs %} + +注意: + +- `size` 不带参数,并返回列表中元素的数量 +- `contains` 方法接受一个参数,即要搜索的值 +- `map` 接受一个函数参数;在上述情况下,传递给它的是一个匿名函数 + +## 多行方法 + +当方法超过一行时,从第二行开始方法体,向右缩进: + +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = { + // imagine that this body requires multiple lines + val sum = a + b + sum * 2 +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_5 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = + // imagine that this body requires multiple lines + val sum = a + b + sum * 2 +``` + +{% endtab %} +{% endtabs %} + +在那个方法中: + +- `sum` 是一个不可变的局部变量;它不能在方法之外访问 +- 最后一行将 `sum` 的值加倍;这个值是从方法返回的 + +当您将该代码粘贴到 REPL 中时,您会看到它按预期工作: + +{% tabs method_6 %} +{% tab 'Scala 2 and 3' for=method_6 %} + +```scala +scala> addThenDouble(1, 1) +res0: Int = 4 +``` + +{% endtab %} +{% endtabs %} + +请注意,方法末尾不需要 `return` 语句。 +因为在 Scala 中几乎所有的东西都是一个_表达式_——意味着每一行代码都返回(或_执行_)一个值——不需要使用 `return`。 + +当您压缩该方法并将其写在一行上时,这一点变得更加清晰: + +{% tabs method_7 %} +{% tab 'Scala 2 and 3' for=method_7 %} + +```scala +def addThenDouble(a: Int, b: Int): Int = (a + b) * 2 +``` + +{% endtab %} +{% endtabs %} + +方法的主体可以使用语言的所有不同特性: + +- `if`/`else` 表达式 +- `match` 表达式 +- `while` 循环 +- `for` 循环和 `for` 表达式 +- 变量赋值 +- 调用其他方法 +- 其他方法的定义 + +作为一个真实世界的多行方法的例子,这个 `getStackTraceAsString` 方法将它的 `Throwable` 输入参数转换成一个格式良好的 `String`: + +{% tabs method_8 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_8 %} + +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter() + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_8 %} + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = StringWriter() + t.printStackTrace(PrintWriter(sw)) + sw.toString +``` + +{% endtab %} +{% endtabs %} + +在那个方法中: + +- 第一行将 `StringWriter` 的新实例分配给值绑定器 `sw` +- 第二行将堆栈跟踪内容存储到 `StringWriter` +- 第三行产生堆栈跟踪的 `String` 表示 + +## 默认参数值 + +方法参数可以有默认值。 +在此示例中,为 `timeout` 和 `protocol` 参数提供了默认值: + +{% tabs method_9 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_9 %} + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = { + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // more code here ... +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_9 %} + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // more code here ... +``` + +{% endtab %} +{% endtabs %} + +由于参数具有默认值,因此可以通过以下方式调用该方法: + +{% tabs method_10 %} +{% tab 'Scala 2 and 3' for=method_10 %} + +```scala +makeConnection() // timeout = 5000, protocol = http +makeConnection(2_000) // timeout = 2000, protocol = http +makeConnection(3_000, "https") // timeout = 3000, protocol = https +``` + +{% endtab %} +{% endtabs %} + +以下是关于这些示例的一些要点: + +- 在第一个示例中,没有提供任何参数,因此该方法使用默认参数值 `5_000` 和 `http` +- 在第二个示例中,为 `timeout` 值提供了 `2_000`,因此它与 `protocol` 的默认值一起使用 +- 在第三个示例中,为两个参数提供了值,因此它们都被使用 + +请注意,通过使用默认参数值,消费者似乎可以使用三种不同的重载方法。 + +## 命名参数 + +如果您愿意,也可以在调用方法时使用方法参数的名称。 +例如,`makeConnection` 也可以通过以下方式调用: + +{% tabs method_11 %} +{% tab 'Scala 2 and 3' for=method_11 %} + +```scala +makeConnection(timeout=10_000) +makeConnection(protocol="https") +makeConnection(timeout=10_000, protocol="https") +makeConnection(protocol="https", timeout=10_000) +``` + +{% endtab %} +{% endtabs %} + +在某些框架中,命名参数被大量使用。 +当多个方法参数具有相同类型时,它们也非常有用: + +{% tabs method_12 %} +{% tab 'Scala 2 and 3' for=method_12 %} + +```scala +engage(true, true, true, false) +``` + +{% endtab %} +{% endtabs %} + +如果没有 IDE 的帮助,代码可能难以阅读,但这段代码更加清晰和明显: + +{% tabs method_13 %} +{% tab 'Scala 2 and 3' for=method_13 %} + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` + +{% endtab %} +{% endtabs %} + +## 关于不带参数的方法的建议 + +当一个方法不带参数时,它的 _arity_ 级别为 _arity-0_。 +类似地,当一个方法采用一个参数时,它是一个_arity-1_方法。 +当您创建 arity-0 方法时: + +- 如果方法执行副作用,例如调用`println`,用空括号声明方法 +- 如果该方法不执行副作用——例如获取集合的大小,这类似于访问集合上的字段——请去掉括号 + +例如,这个方法会产生副作用,所以它用空括号声明: + +{% tabs method_14 %} +{% tab 'Scala 2 and 3' for=method_14 %} + +```scala +def speak() = println("hi") +``` + +{% endtab %} +{% endtabs %} + +这样做需要方法的调用者在调用方法时使用括号: + +{% tabs method_15 %} +{% tab 'Scala 2 and 3' for=method_15 %} + +```scala +speak // error: "method speak must be called with () argument" +speak() // prints "hi" +``` + +{% endtab %} +{% endtabs %} + +虽然这只是一个约定,但遵循它可以显着提高代码的可读性:它可以让您更容易一目了然地理解 arity-0 方法执行副作用。 + +{% comment %} +Some of that wording comes from this page: https://docs.scala-lang.org/style/method-invocation.html +{% endcomment %} + +## 使用 `if` 作为方法体 + +因为 `if`/`else` 表达式返回一个值,所以它们可以用作方法的主体。 +这是一个名为 `isTruthy` 的方法,它实现了 Perl 对 `true` 和 `false` 的定义: + +{% tabs method_16 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_16 %} + +```scala +def isTruthy(a: Any) = { + if (a == 0 || a == "" || a == false) + false + else + true +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_16 %} + +```scala +def isTruthy(a: Any) = + if a == 0 || a == "" || a == false then + false + else + true +``` + +{% endtab %} +{% endtabs %} + +这些示例显示了该方法的工作原理: + +{% tabs method_17 %} +{% tab 'Scala 2 and 3' for=method_17 %} + +```scala +isTruthy(0) // false +isTruthy("") // false +isTruthy("hi") // true +isTruthy(1.0) // true +``` + +{% endtab %} +{% endtabs %} + +## 使用 `match` 作为方法体 + +`match` 表达式也可以用作整个方法体,而且经常如此。 +这是 `isTruthy` 的另一个版本,用 `match` 表达式编写: + +{% tabs method_18 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_18 %} + +```scala +def isTruthy(a: Any) = a match { + case 0 | "" | false => false + case _ => true +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_18 %} + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` + +> 此方法的工作方式与之前使用 `if`/`else` 表达式的方法一样。我们使用 `Matchable` 而不是 `Any` 作为参数的类型来接受任何支持模式匹配的值。 + +> 有关 `Matchable` trait 的更多详细信息,请参阅 [参考文档][reference_matchable]。 + +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +{% endtab %} +{% endtabs %} + +## 控制类中的可见性 + +在类、对象、trait和枚举中,Scala 方法默认是公共的,所以这里创建的 `Dog` 实例可以访问 `speak` 方法: + +{% tabs method_19 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_19 %} + +```scala +class Dog { + def speak() = println("Woof") +} + +val d = new Dog +d.speak() // prints "Woof" +``` + +{% endtab %} +{% tab 'Scala 3' for=method_19 %} + +```scala +class Dog: + def speak() = println("Woof") + +val d = new Dog +d.speak() // prints "Woof" +``` + +{% endtab %} +{% endtabs %} + +方法也可以标记为 `private`。 +这使得它们对当前类是私有的,因此它们不能在子类中被调用或重载: + +{% tabs method_20 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_20 %} + +```scala +class Animal { + private def breathe() = println("I’m breathing") +} + +class Cat extends Animal { + // this method won’t compile + override def breathe() = println("Yo, I’m totally breathing") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_20 %} + +```scala +class Animal: + private def breathe() = println("I’m breathing") + +class Cat extends Animal: + // this method won’t compile + override def breathe() = println("Yo, I’m totally breathing") +``` + +{% endtab %} +{% endtabs %} + +如果你想让一个方法对当前类私有,并且允许子类调用它或覆盖它,将该方法标记为 `protected`,如本例中的 `speak` 方法所示: + +{% tabs method_21 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_21 %} + +```scala +class Animal { + private def breathe() = println("I’m breathing") + def walk() = { + breathe() + println("I’m walking") + } + protected def speak() = println("Hello?") +} + +class Cat extends Animal { + override def speak() = println("Meow") +} + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // won’t compile because it’s private +``` + +{% endtab %} +{% tab 'Scala 3' for=method_21 %} + +```scala +class Animal: + private def breathe() = println("I’m breathing") + def walk() = + breathe() + println("I’m walking") + protected def speak() = println("Hello?") + +class Cat extends Animal: + override def speak() = println("Meow") + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // won’t compile because it’s private +``` + +{% endtab %} +{% endtabs %} + +`protected` 设置意味着: + +- 方法(或字段)可以被同一类的其他实例访问 +- 对当前包中的其他代码是不可见的 +- 它适用于子类 + +## 对象可以包含方法 + +之前你看到trait和类可以有方法。 +Scala `object` 关键字用于创建单例类,对象也可以包含方法。 +这是对一组“实用程序”方法进行分组的好方法。 +例如,此对象包含一组处理字符串的方法: + +{% tabs method_22 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_22 %} + +```scala +object StringUtils { + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_22 %} + +```scala +object StringUtils: + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +end StringUtils +``` + +{% endtab %} +{% endtabs %} + +## 扩展方法 + +扩展方法在上下文抽象一章的[扩展方法部分][extension]中讨论。 +有很多情况,你想向封闭类添加功能。 +如该部分所示,假设您有一个 `Circle` 类,但您无法更改其源代码。 +例如,它可以在第三方库中这样定义: + +{% tabs method_23 %} +{% tab 'Scala 2 and 3' for=method_23 %} + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +{% endtab %} +{% endtabs %} + +当你想给这个类添加方法时,你可以将它们定义为扩展方法,像这样: + +{% tabs method_24 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_24 %} + +```scala +implicit class CircleOps(c: Circle) { + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +} +``` +在 Scala 2 中使用 `implicit class`,在[这](/overviews/core/implicit-classes.html)找到更多细节。 + +{% endtab %} +{% tab 'Scala 3' for=method_24 %} + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` +在 Scala 3 中使用新的 `extension` 结构。更多细节可以看[本书][extension]的章节,或者 [Scala 3 参考][reference-ext]。 + +[reference-ext]: {{ site.scala3ref }}/contextual/extension-methods.html +[extension]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +{% endtab %} +{% endtabs %} + +现在,当您有一个名为 `aCircle` 的 `Circle` 实例时,您可以像这样调用这些方法: + +{% tabs method_25 %} +{% tab 'Scala 2 and 3' for=method_25 %} + +```scala +aCircle.circumference +aCircle.diameter +aCircle.area +``` + +{% endtab %} +{% endtabs %} + +## 更多 + +还有更多关于方法的知识,包括如何: + +- 调用超类的方法 +- 定义和使用传名参数 +- 编写一个带有函数参数的方法 +- 创建内嵌方法 +- 处理异常 +- 使用可变参数作为输入参数 +- 编写具有多个参数组的方法(部分应用的函数) +- 创建具有泛型类型参数的方法 + +{% comment %} +Jamie: there really needs better linking here - previously it was to the Scala 3 Reference, which doesnt cover any +of this +{% endcomment %} + +有关这些特性的更多详细信息,请看本书其它章节。 + +[reference_extension_methods]: {{ site.scala3ref }}/contextual/extension-methods.html +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_zh-cn/overviews/scala3-book/methods-summary.md b/_zh-cn/overviews/scala3-book/methods-summary.md new file mode 100644 index 0000000000..472c6230a1 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-summary.md @@ -0,0 +1,28 @@ +--- +title: 总结 +type: section +description: This section summarizes the previous sections on Scala 3 methods. +language: zh-cn +num: 27 +previous-page: methods-main-methods +next-page: fun-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +还有更多关于方法的知识,包括如何: + +- 调用超类的方法 +- 定义和使用按名称参数 +- 编写一个带有函数参数的方法 +- 创建内嵌方法 +- 处理异常 +- 使用可变参数输入参数 +- 编写具有多个参数组的方法(部分应用的函数) +- 创建具有泛型类型参数的方法 + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/packaging-imports.md b/_zh-cn/overviews/scala3-book/packaging-imports.md new file mode 100644 index 0000000000..bc928dbe67 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/packaging-imports.md @@ -0,0 +1,632 @@ +--- +title: 打包和导入 +type: chapter +description: A discussion of using packages and imports to organize your code, build related modules of code, control scope, and help prevent namespace collisions. +language: zh-cn +num: 36 +previous-page: fun-summary +next-page: collections-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 使用 *包* 创建命名空间,让您可以模块化程序并帮助防止命名空间冲突。 +Scala 支持 Java 使用的包命名样式,也支持 C++ 和 C# 等语言使用的“花括号”命名空间表示法。 + +Scala 导入成员的方法也类似于 Java,并且更灵活。 +使用 Scala,您可以: + +- 导入包、类、对象、traits 和方法 +- 将导入语句放在任何地方 +- 导入成员时隐藏和重命名成员 + +这些特性在以下示例中进行了演示。 + +## 创建一个包 + +通过在 Scala 文件的顶部声明一个或多个包名称来创建包。 +例如,当您的域名是 _acme.com_ 并且您正在使用名为 _myapp_ 的应用程序中的 _model_ 包中工作时,您的包声明如下所示: + +{% tabs packaging-imports-0 %} +{% tab 'Scala 2 and 3' %} +```scala +package com.acme.myapp.model + +class Person ... +``` +{% endtab %} +{% endtabs %} + +按照约定,包名应全部小写,正式命名约定为 *\.\.\.\*。 + +虽然不是必需的,但包名称通常遵循目录结构名称,因此如果您遵循此约定,则此项目中的 `Person` 类将在 *MyApp/src/main/scala/com/acme/myapp/model/Person.scala* 文件中找到。 + +### 在同一个文件中使用多个包 + +上面显示的语法适用于整个源文件:文件中的所有定义 +`Person.scala` 属于 `com.acme.myapp.model` 包,根据包子句 +在文件的开头。 + +或者,可以编写仅适用于定义的包子句 +他们包含: + +{% tabs packaging-imports-1 class=tabs-scala-version %} +{% tab 'Scala 2' %}```scala +package users { + + package administrators { // the full name of this package is users.administrators + class AdminUser // the full name of this class users.administrators.AdminUser + } + package normalusers { // the full name of this package is users.normalusers + class NormalUser // the full name of this class is users.normalusers.NormalUser + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-1 %} + +```scala +package users: + + package administrators: // the full name of this package is users.administrators + class AdminUser // the full name of this class is users.administrators.AdminUser + + package normalusers: // the full name of this package is users.normalusers + class NormalUser // the full name of this class is users.normalusers.NormalUser +``` +{% endtab %} +{% endtabs %} + +请注意,包名称后跟一个冒号,并且其中的定义 +一个包是缩进的。 + +这种方法的优点是它允许包嵌套,并提供更明显的范围和封装控制,尤其是在同一个文件中。 + +## 导入语句,第 1 部分 + +导入语句用于访问其他包中的实体。 +导入语句分为两大类: + +- 导入类、trait、对象、函数和方法 +- 导入 `given` 子句 + +如果您习惯于 Java 之类的语言,则第一类 import 语句与 Java 使用的类似,只是语法略有不同,因此具有更大的灵活性。 +这些示例展示了其中的一些灵活性: + +{% tabs packaging-imports-2 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import users._ // import everything from the `users` package +import users.User // import only the `User` class +import users.{User, UserPreferences} // import only two selected members +import users.{UserPreferences as UPrefs} // rename a member as you import it +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-2 %} + +```scala +import users.* // import everything from the `users` package +import users.User // import only the `User` class +import users.{User, UserPreferences} // import only two selected members +import users.{UserPreferences as UPrefs} // rename a member as you import it +``` + +{% endtab %} +{% endtabs %} + +这些示例旨在让您了解第一类 `import` 语句的工作原理。 +在接下来的小节中对它们进行了更多解释。 + +导入语句还用于将 `given` 实例导入本范围。 +这些将在本章末尾讨论。 + +继续之前的注意事项: + +> 访问同一包的成员不需要导入子句。 + +### 导入一个或多个成员 + +在 Scala 中,您可以从包中导入一个成员,如下所示: + +{% tabs packaging-imports-3 %} +{% tab 'Scala 2 and 3' %} +```scala +import scala.concurrent.Future +``` +{% endtab %} +{% endtabs %} + +和这样的多个成员: + +{% tabs packaging-imports-4 %} +{% tab 'Scala 2 and 3' %} +```scala +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.blocking +``` +{% endtab %} +{% endtabs %} + +导入多个成员时,您可以像这样更简洁地导入它们: + +{% tabs packaging-imports-5 %} +{% tab 'Scala 2 and 3' %} +```scala +import scala.concurrent.{Future, Promise, blocking} +``` +{% endtab %} +{% endtabs %} + +当您想从 *scala.concurrent* 包中导入所有内容时,请使用以下语法: + +{% tabs packaging-imports-6 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.concurrent._ +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-6 %} + +```scala +import scala.concurrent.* +``` +{% endtab %} +{% endtabs %} + +### 在导入时重命名成员 + +有时,在导入实体时重命名实体会有所帮助,以避免名称冲突。 +例如,如果您想同时使用 Scala `List` 类和 *java.util.List* 类,可以在导入时重命名 *java.util.List* 类: + +{% tabs packaging-imports-7 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{List => JavaList} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-7 %} + +```scala +import java.util.{List as JavaList} +``` +{% endtab %} +{% endtabs %} + +现在您使用名称 `JavaList` 来引用该类,并使用 `List` 来引用 Scala 列表类。 + +您还可以使用以下语法一次重命名多个成员: + +{% tabs packaging-imports-8 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{Date => JDate, HashMap => JHashMap, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-8 %} + +```scala +import java.util.{Date as JDate, HashMap as JHashMap, *} +``` + +{% endtab %} +{% endtabs %} + +那行代码说,“重命名 `Date` 和 `HashMap` 类,如图所示,并导入 _java.util_ 包中的所有其他内容,而不重命名任何其他成员。” + +### 在导入时隐藏成员 + +您还可以在导入过程中*隐藏*成员。 +这个 `import` 语句隐藏了 *java.util.Random* 类,同时导入 *java.util 中的所有其他内容* 包裹: + +{% tabs packaging-imports-9 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.util.{Random => _, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-9 %} + +```scala +import java.util.{Random as _, *} +``` +{% endtab %} +{% endtabs %} + +如果您尝试访问 `Random` 类,它将无法正常工作,但您可以访问该包中的所有其他成员: + +{% tabs packaging-imports-10 %} +{% tab 'Scala 2 and 3' %} +```scala +val r = new Random // won’t compile +new ArrayList // works +``` +{% endtab %} +{% endtabs %} + +#### 隐藏多个成员 + +要在导入过程中隐藏多个成员,请在使用最终通配符导入之前列出它们: + +{% tabs packaging-imports-11 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +scala> import java.util.{List => _, Map => _, Set => _, _} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-11 %} + +```scala +scala> import java.util.{List as _, Map as _, Set as _, *} +``` +{% endtab %} +{% endtabs %} + +这些类再次被隐藏,但您可以使用 *java.util* 中的所有其他类: + +{% tabs packaging-imports-12 %} +{% tab 'Scala 2 and 3' %} +```scala +scala> new ArrayList[String] +val res0: java.util.ArrayList[String] = [] +``` +{% endtab %} +{% endtabs %} + +因为这些 Java 类是隐藏的,所以您也可以使用 Scala 的 `List`、`Set` 和 `Map` 类而不会发生命名冲突: + +{% tabs packaging-imports-13 %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val a = List(1, 2, 3) +val a: List[Int] = List(1, 2, 3) + +scala> val b = Set(1, 2, 3) +val b: Set[Int] = Set(1, 2, 3) + +scala> val c = Map(1 -> 1, 2 -> 2) +val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2) +``` +{% endtab %} +{% endtabs %} + +### 在任何地方使用导入 + +在 Scala 中,`import` 语句可以在任何地方。 +它们可以在源代码文件的顶部使用: + +{% tabs packaging-imports-14 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package foo + +import scala.util.Random + +class ClassA { + def printRandom(): Unit = { + val r = new Random // use the imported class + // more code here... + } +} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-14 %} + +```scala +package foo + +import scala.util.Random + +class ClassA: + def printRandom: + val r = new Random // use the imported class + // more code here... +``` +{% endtab %} +{% endtabs %} + +如果您愿意,您还可以使用更接近需要它们的点的 `import` 语句: + +{% tabs packaging-imports-15 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package foo + +class ClassA { + import scala.util.Random // inside ClassA + def printRandom(): Unit = { + val r = new Random + // more code here... + } +} + +class ClassB { + // the Random class is not visible here + val r = new Random // this code will not compile +} +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-15 %} + +```scala +package foo + +class ClassA: + import scala.util.Random // inside ClassA + def printRandom { + val r = new Random + // more code here... + +class ClassB: + // the Random class is not visible here + val r = new Random // this code will not compile +``` + +{% endtab %} +{% endtabs %} + +### “静态”导入 + +当您想以类似于 Java “静态导入”方法的方式导入成员时——因此您可以直接引用成员名称,而不必在它们前面加上类名——使用以下方法。 + +使用此语法导入 Java `Math` 类的所有静态成员: + +{% tabs packaging-imports-16 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.lang.Math._ +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-16 %} + +```scala +import java.lang.Math.* +``` +{% endtab %} +{% endtabs %} + +现在您可以访问静态的 `Math` 类方法,例如 `sin` 和 `cos`,而不必在它们前面加上类名: + +{% tabs packaging-imports-17 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import java.lang.Math._ + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-17 %} + +```scala +import java.lang.Math.* + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` +{% endtab %} +{% endtabs %} + +### 默认导入的包 + +两个包被隐式导入到所有源代码文件的范围内: + +- java.lang.* +- scala.* + +Scala 对象 `Predef` 的成员也是默认导入的。 + +> 如果您想知道为什么可以使用 `List`、`Vector`、`Map` 等类,而无需导入它们,它们是可用的,因为 `Predef` 对象中的定义。 + +### 处理命名冲突 + +在极少数情况下会出现命名冲突,您需要从项目的根目录导入一些东西,在包名前加上 `_root_`: + +{% tabs packaging-imports-18 class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +package accounts + +import _root_.accounts._ +``` + +{% endtab %} +{% tab 'Scala 3' for=packaging-imports-18 %} + +```scala +package accounts + +import _root_.accounts.* +``` +{% endtab %} +{% endtabs %} + +## 导入 `given` 实例 + +正如您将在 [上下文抽象][contextual] 一章中看到的,`import` 语句的一种特殊形式用于导入 `given` 实例。 +基本形式如本例所示: + +{% tabs packaging-imports-19 %} +{% tab 'Scala 3 only' %} +```scala +object A: + class TC + given tc: TC + def f(using TC) = ??? + +object B: + import A.* // import all non-given members + import A.given // import the given instance +``` +{% endtab %} +{% endtabs %} + +在此代码中,对象 `B` 的 `import A.*` 子句导入了 `A` 的所有成员 *除了* `given` 实例 `tc`。 +相反,第二个导入,`import A.given`,*仅*导入那个 `given` 实例。 +两个 `import` 子句也可以合并为一个: + +{% tabs packaging-imports-20 %} +{% tab 'Scala 3 only' %} +```scala +object B: + import A.{given, *} +``` +{% endtab %} +{% endtabs %} + +### 讨论 + +通配符选择器 `*` 将除给定或扩展之外的所有定义带入范围,而 `given` 选择器将所有*给定*——包括那些由扩展产生的定义——带入范围。 + +这些规则有两个主要好处: + +- 范围内的给定来自哪里更清楚。 + 特别是,不可能在一长串其他通配符导入中隐藏导入的给定。 +- 它可以在不导入任何其他内容的情况下导入所有给定。 + 这一点特别重要,因为给定可以是匿名的,所以通常使用命名导入是不切实际的。 + +### 按类型导入 + +由于给定可以是匿名的,因此按名称导入它们并不总是可行的,通常使用通配符导入。 +*按类型导入* 为通配符导入提供了更具体的替代方案,这使得导入的内容更加清晰: + +{% tabs packaging-imports-21 %} +{% tab 'Scala 3 only' %} +```scala +import A.{given TC} +``` +{% endtab %} +{% endtabs %} + +这会在 `A` 中导入任何具有符合 `TC` 的类型的 `given`。 +导入多种类型的给定 `T1,...,Tn` 由多个 `given` 选择器表示: + +{% tabs packaging-imports-22 %} +{% tab 'Scala 3 only' %} +```scala +import A.{given T1, ..., given Tn} +``` +{% endtab %} +{% endtabs %} + +导入参数化类型的所有 `given` 实例由通配符参数表示。 +例如,当你有这个 `object` 时: + +{% tabs packaging-imports-23 %} +{% tab 'Scala 3 only' %} +```scala +object Instances: + given intOrd: Ordering[Int] + given listOrd[T: Ordering]: Ordering[List[T]] + given ec: ExecutionContext = ... + given im: Monoid[Int] +``` +{% endtab %} +{% endtabs %} + +此导入语句导入 `intOrd`、`listOrd` 和 `ec` 实例,但省略了 `im` 实例,因为它不符合任何指定的边界: + +{% tabs packaging-imports-24 %} +{% tab 'Scala 3 only' %} +```scala +import Instances.{given Ordering[?], given ExecutionContext} +``` +{% endtab %} +{% endtabs %} + +按类型导入可以与按名称导入混合。 +如果两者都存在于导入子句中,则按类型导入排在最后。 +例如,这个 import 子句导入了 `im`、`intOrd` 和 `listOrd`,但省略了 `ec`: + +{% tabs packaging-imports-25 %} +{% tab 'Scala 3 only' %} +```scala +import Instances.{im, given Ordering[?]} +``` +{% endtab %} +{% endtabs %} + +### 一个例子 + +作为一个具体的例子,假设你有这个 `MonthConversions` 对象,它包含两个 `given` 定义: + +{% tabs packaging-imports-26 %} +{% tab 'Scala 3 only' %} + +```scala +object MonthConversions: + trait MonthConverter[A]: + def convert(a: A): String + + given intMonthConverter: MonthConverter[Int] with + def convert(i: Int): String = + i match + case 1 => "January" + case 2 => "February" + // more cases here ... + + given stringMonthConverter: MonthConverter[String] with + def convert(s: String): String = + s match + case "jan" => "January" + case "feb" => "February" + // more cases here ... +``` +{% endtab %} +{% endtabs %} + +要将这些给定导入当前范围,请使用以下两个 `import` 语句: + +{% tabs packaging-imports-27 %} +{% tab 'Scala 3 only' %} + +```scala +import MonthConversions.* +import MonthConversions.{given MonthConverter[?]} +``` +{% endtab %} +{% endtabs %} + +现在您可以创建一个使用这些 `given` 实例的方法: + +{% tabs packaging-imports-28 %} +{% tab 'Scala 3 only' %} + +```scala +def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = + monthConverter.convert(a) +``` +{% endtab %} +{% endtabs %} + +然后您可以在您的应用程序中使用该方法: + +{% tabs packaging-imports-29 %} +{% tab 'Scala 3 only' %} + +```scala +@main def main = + println(genericMonthConverter(1)) // January + println(genericMonthConverter("jan")) // January +``` +{% endtab %} +{% endtabs %} + +如前所述, `import given` 语法的主要设计优势之一是明确范围内的给定来自何处,并且在这些 `import` 语句中,很清楚地表明给定是来自 `MonthConversions` 对象。 + +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/scala-features.md b/_zh-cn/overviews/scala3-book/scala-features.md index 394681e1cf..9c8d02f085 100644 --- a/_zh-cn/overviews/scala3-book/scala-features.md +++ b/_zh-cn/overviews/scala3-book/scala-features.md @@ -2,35 +2,22 @@ title: Scala 3 特性 type: chapter description: This page discusses the main features of the Scala 3 programming language. +language: zh-cn num: 2 previous-page: introduction -next-page: +next-page: why-scala-3 -scala3: true partof: scala3-book overview-name: "Scala 3 — Book" layout: multipage-overview permalink: "/zh-cn/scala3/book/:title.html" --- -{% comment %} -The name _Scala_ comes from the word _scalable_, and true to that name, the Scala language is used to power busy websites and analyze huge data sets. -This section introduces the features that make Scala a scalable language. -These features are split into three sections: -{% endcomment %} _Scala_ 这个名字来源于 _scalable_ 一词。正如其名,Scala 语言被用于支撑高流量网站以及分析庞大的数据集。 本节介绍了使 Scala 成为一门可扩展语言的特性。 这些特性分为三个部分: -{% comment %} - -- High-level language features -- Lower-level language features -- Scala ecosystem features - -{% endcomment %} - - 高级语言特性 - 底层语言特性 - Scala 生态系统特性 @@ -39,36 +26,10 @@ _Scala_ 这个名字来源于 _scalable_ 一词。正如其名,Scala 语言被 I think of this section as being like an “elevator pitch.” {% endcomment %} -{% comment %} - -## High-level features - -{% endcomment %} - ## 高级特性 -{% comment %} -Looking at Scala from the proverbial “30,000 foot view,” you can make the following statements about it: -{% endcomment %} - 从宏观视角来看 Scala,您可以对它做出以下陈述: -{% comment %} - -- It’s a high-level programming language -- It has a concise, readable syntax -- It’s statically-typed (but feels dynamic) -- It has an expressive type system -- It’s a functional programming (FP) language -- It’s an object-oriented programming (OOP) language -- It supports the fusion of FP and OOP -- Contextual abstractions provide a clear way to implement _term inference_ -- It runs on the JVM (and in the browser) -- It interacts seamlessly with Java code -- It’s used for server-side applications (including microservices), big data applications, and can also be used in the browser with Scala.js - -{% endcomment %} - - 它是一种高级编程语言 - 它具有简明易读的语法 - 它是静态类型的(但使人感觉是动态的) @@ -81,94 +42,80 @@ Looking at Scala from the proverbial “30,000 foot view,” you can make the fo - 它与 Java 代码无缝交互 - 它可被用于服务器端应用(包括微服务)、大数据应用,也可以在浏览器中与 Scala.js 共同使用 -{% comment %} -The following sections take a quick look at these features. -{% endcomment %} - 以下部分将对这些特性进行简要介绍。 -{% comment %} - -### A high-level language - -{% endcomment %} - ### 一门高级语言 -{% comment %} -Scala is considered a high-level language in at least two ways. -First, like Java and many other modern languages, you don’t deal with low-level concepts like pointers and memory management. -{% endcomment %} - Scala 至少在两个方面被认为是一门高级语言。 首先,像 Java 和许多其他现代语言一样,您不需要与指针和内存管理等底层概念打交道。 -{% comment %} -Second, with the use of lambdas and higher-order functions, you write your code at a very high level. -As the functional programming saying goes, in Scala you write _what_ you want, not _how_ to achieve it. -That is, we don’t write imperative code like this: -{% endcomment %} - 其次,通过使用 lambda 与高阶函数,您可以在非常高的层次上编写代码。 正如函数式编程的说法,在 Scala 中,您编写您想要 _“什么”_,而不是 _“如何”_ 去实现它。 也就是说,我们不会像这样编写命令式代码: +{% tabs scala-features-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-1 %} ```scala +import scala.collection.mutable.ListBuffer + def double(ints: List[Int]): List[Int] = { val buffer = new ListBuffer[Int]() for (i <- ints) { - buffer += i * 2 + buffer += i * 2 } buffer.toList } +val oldNumbers = List(1, 2, 3) val newNumbers = double(oldNumbers) ``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-1 %} +```scala +import scala.collection.mutable.ListBuffer -{% comment %} -That code instructs the compiler what to do on a step-by-step basis. -Instead, we write high-level, functional code using higher-order functions and lambdas like this to compute the same result: -{% endcomment %} +def double(ints: List[Int]): List[Int] = + val buffer = new ListBuffer[Int]() + for i <- ints do + buffer += i * 2 + buffer.toList + +val oldNumbers = List(1, 2, 3) +val newNumbers = double(oldNumbers) +``` +{% endtab %} +{% endtabs %} 这段代码指示编译器逐步执行特定操作。 相反,我们使用像这样的高阶函数与 lambda 来编写高层次的函数式代码以计算出相同的结果: +{% tabs scala-features-2 %} +{% tab 'Scala 2 and 3' for=scala-features-2 %} ```scala val newNumbers = oldNumbers.map(_ * 2) ``` - -{% comment %} -As you can see, that code is much more concise, easier to read, and easier to maintain. -{% endcomment %} +{% endtab %} +{% endtabs %} 如您所见,该代码更简洁、更容易阅读且更易于维护。 -{% comment %} - -### Concise syntax - -{% endcomment %} - ### 简明的语法 -{% comment %} -Scala has a concise, readable syntax. -For instance, variables are created concisely, and their types are clear: -{% endcomment %} - Scala 具有简明易读的语法。例如,变量的创建十分简洁,其类型也很明确。 +{% tabs scala-features-3 %} +{% tab 'Scala 2 and 3' for=scala-features-3 %} ```scala val nums = List(1,2,3) val p = Person("Martin", "Odersky") ``` - -{% comment %} -Higher-order functions and lambdas make for concise code that’s readable: -{% endcomment %} +{% endtab %} +{% endtabs %} 高阶函数与 lambda 使代码简明易读: +{% tabs scala-features-4 %} +{% tab 'Scala 2 and 3' for=scala-features-4 %} ```scala nums.map(i => i * 2) // long form nums.map(_ * 2) // short form @@ -176,13 +123,29 @@ nums.map(_ * 2) // short form nums.filter(i => i > 1) nums.filter(_ > 1) ``` - -{% comment %} -Traits, classes, and methods are defined with a clean, light syntax: -{% endcomment %} +{% endtab %} +{% endtabs %} 特质(Traits)、类(Class)和方法(Method)都是用简洁、轻巧的语法定义的。 +{% tabs scala-features-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-5 %} +```scala mdoc +trait Animal { + def speak(): Unit +} + +trait HasTail { + def wagTail(): Unit +} + +class Dog extends Animal with HasTail { + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") +} +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-5 %} ```scala trait Animal: def speak(): Unit @@ -191,61 +154,47 @@ trait HasTail: def wagTail(): Unit class Dog extends Animal, HasTail: - def speak() = println("Woof") - def wagTail() = println("⎞⎜⎛ ⎞⎜⎛") + def speak(): Unit = println("Woof") + def wagTail(): Unit = println("⎞⎜⎛ ⎞⎜⎛") ``` - -{% comment %} -Studies have shown that the time a developer spends _reading_ code to _writing_ code is at least a 10:1 ratio, so writing code that is concise _and_ readable is important. -{% endcomment %} +{% endtab %} +{% endtabs %} 研究表明,开发人员花在 _阅读_ 代码和 _编写_ 代码上的时间比例至少为 10:1。因此,编写简洁 _并_ 易读的代码非常重要。 -{% comment %} - -### A dynamic feel - -{% endcomment %} - ### 动态感受 -{% comment %} -Scala is a statically-typed language, but thanks to its type inference capabilities it feels dynamic. -All of these expressions look like a dynamically-typed language like Python or Ruby, but they’re all Scala: -{% endcomment %} - Scala 是一种静态类型的语言,但由于其类型推断能力,它使人感觉是动态的。所有这些表达式看起来都像 Python 或 Ruby 这样的动态类型语言代码,但其实它们都是 Scala 代码: +{% tabs scala-features-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=scala-features-6 %} +```scala +val s = "Hello" +val p = Person("Al", "Pacino") +val sum = nums.reduceLeft(_ + _) +val y = for (i <- nums) yield i * 2 +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) +``` +{% endtab %} +{% tab 'Scala 3' for=scala-features-6 %} ```scala val s = "Hello" val p = Person("Al", "Pacino") -val sum = ints.reduceLeft(_ + _) +val sum = nums.reduceLeft(_ + _) val y = for i <- nums yield i * 2 -val z = nums.filter(_ > 100) - .filter(_ < 10_000) - .map(_ * 2) +val z = nums + .filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) ``` - -{% comment %} -As Heather Miller states, Scala is considered to be a [strong, statically-typed language](https://heather.miller.am/blog/types-in-scala.html), and you get all the benefits of static types: -{% endcomment %} +{% endtab %} +{% endtabs %} 正如 Heather Miller 所说,Scala 被认为是一种[强静态类型语言](https://heather.miller.am/blog/types-in-scala.html)。您可以获得静态类型的全部益处: -{% comment %} - -- Correctness: you catch most errors at compile-time -- Great IDE support - - Reliable code completion - - Catching errors at compile-time means catching mistakes as you type - - Easy and reliable refactoring -- You can refactor your code with confidence -- Method type declarations tell readers what the method does, and help serve as documentation -- Scalability and maintainability: types help ensure correctness across arbitrarily large applications and development teams -- Strong typing in combination with excellent inference enables mechanisms like [contextual abstraction]({{ site.scala3ref }}/contextual.html) that allows you to omit boilerplate code. Often, this boilerplate code can be inferred by the compiler, based on type definitions and a given context. - -{% endcomment %} - - 正确性:您可以在编译时捕获大多数错误 - 强大的 IDE 支持 - 可靠的代码补全 @@ -254,7 +203,7 @@ As Heather Miller states, Scala is considered to be a [strong, statically-typed - 您可以自信地重构您的代码 - 方法类型声明告诉读者该方法的作用,并作为文档提供帮助 - 可扩展性与可维护性:类型有助于在任意大小的应用程序与开发团队中确保正确性 -- 强类型结合优秀的推断能力可实现[上下文抽象]({{ site.scala3ref }}/contextual.html)等机制,这允许您省略样板代码。通常,这些样板代码可由编译器根据类型定义及给定的上下文推断出来。 +- 强类型结合优秀的推断能力可实现[上下文抽象]({{ site.scala3ref }}/contextual)等机制,这允许您省略样板代码。通常,这些样板代码可由编译器根据类型定义及给定的上下文推断出来。 {% comment %} In that list: @@ -265,12 +214,6 @@ In that list: - Reliable code completion {% endcomment %} -{% comment %} - -### Expressive type system - -{% endcomment %} - ### 富有表现力的类型系统 {% comment %} @@ -283,49 +226,21 @@ In that list: * [Explicitly typed self references](/tour/self-types.html) {% endcomment %} -{% comment %} -Scala’s type system enforces, at compile-time, that abstractions are used in a safe and coherent manner. -In particular, the type system supports: -{% endcomment %} - Scala 的类型系统在编译时强制要求以安全与连贯的方式使用抽象概念。特别是,该类型系统支持: -{% comment %} -- [Inferred types]({% link _overviews/scala3-book/types-inferred.md %}) -- [Generic classes]({% link _overviews/scala3-book/types-generics.md %}) -- [Variance annotations]({% link _overviews/scala3-book/types-variance.md %}) -- [Upper](/tour/upper-type-bounds.html) and [lower](/tour/lower-type-bounds.html) type bounds -- [Polymorphic methods](/tour/polymorphic-methods.html) -- [Intersection types]({% link _overviews/scala3-book/types-intersection.md %}) -- [Union types]({% link _overviews/scala3-book/types-union.md %}) -- [Type lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html) -- [`given` instances and `using` clauses]({% link _overviews/scala3-book/ca-given-using-clauses.md %}) -- [Extension methods]({% link _overviews/scala3-book/ca-extension-methods.md %}) -- [Type classes]({% link _overviews/scala3-book/ca-type-classes.md %}) -- [Multiversal equality]({% link _overviews/scala3-book/ca-multiversal-equality.md %}) -- [Opaque type aliases]({% link _overviews/scala3-book/types-opaque-types.md %}) -- [Open classes]({{ site.scala3ref }}/other-new-features/open-classes.html) -- [Match types]({{ site.scala3ref }}/new-types/match-types.html) -- [Dependent function types]({{ site.scala3ref }}/new-types/dependent-function-types.html) -- [Polymorphic function types]({{ site.scala3ref }}/new-types/polymorphic-function-types.html) -- [Context bounds]({{ site.scala3ref }}/contextual/context-bounds.html) -- [Context functions]({{ site.scala3ref }}/contextual/context-functions.html) -- [Inner classes](/tour/inner-classes.html) and [abstract type members](/tour/abstract-type-members.html) as object members -{% endcomment %} - -- [推断类型]({% link _overviews/scala3-book/types-inferred.md %}) -- [泛型类]({% link _overviews/scala3-book/types-generics.md %}) -- [型变]({% link _overviews/scala3-book/types-variance.md %}) +- [推断类型]({% link _zh-cn/overviews/scala3-book/types-inferred.md %}) +- [泛型类]({% link _zh-cn/overviews/scala3-book/types-generics.md %}) +- [型变]({% link _zh-cn/overviews/scala3-book/types-variance.md %}) - [类型上界](/tour/upper-type-bounds.html) 与 [类型下界](/tour/lower-type-bounds.html) - [多态方法](/tour/polymorphic-methods.html) -- [交叉类型]({% link _overviews/scala3-book/types-intersection.md %}) -- [联合类型]({% link _overviews/scala3-book/types-union.md %}) +- [交叉类型]({% link _zh-cn/overviews/scala3-book/types-intersection.md %}) +- [联合类型]({% link _zh-cn/overviews/scala3-book/types-union.md %}) - [类型 Lambda]({{ site.scala3ref }}/new-types/type-lambdas.html) -- [`given` 实例与 `using` 子句]({% link _overviews/scala3-book/ca-given-using-clauses.md %}) -- [扩展方法]({% link _overviews/scala3-book/ca-extension-methods.md %}) -- [类型类]({% link _overviews/scala3-book/ca-type-classes.md %}) -- [多元相等]({% link _overviews/scala3-book/ca-multiversal-equality.md %}) -- [不透明类型别名]({% link _overviews/scala3-book/types-opaque-types.md %}) +- [`given` 实例与 `using` 子句]({% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %}) +- [扩展方法]({% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %}) +- [类型类]({% link _zh-cn/overviews/scala3-book/ca-type-classes.md %}) +- [多元相等]({% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %}) +- [不透明类型别名]({% link _zh-cn/overviews/scala3-book/types-opaque-types.md %}) - [开放类]({{ site.scala3ref }}/other-new-features/open-classes.html) - [匹配类型]({{ site.scala3ref }}/new-types/match-types.html) - [依赖函数类型]({{ site.scala3ref }}/new-types/dependent-function-types.html) @@ -334,38 +249,12 @@ Scala 的类型系统在编译时强制要求以安全与连贯的方式使用 - [上下文函数]({{ site.scala3ref }}/contextual/context-functions.html) - 作为对象成员的[内部类](/tour/inner-classes.html) 与 [抽象类型](/tour/abstract-type-members.html) -{% comment %} -In combination, these features provide a powerful basis for the safe reuse of programming abstractions and for the type-safe extension of software. -{% endcomment %} - 通过结合使用,这些特性为编程抽象的安全重用及软件的类型安全扩展提供了强大的基础。 -{% comment %} - -### A functional programming language - -{% endcomment %} - ### 一门函数式编程语言 -{% comment %} -Scala is a functional programming (FP) language, meaning: -{% endcomment %} - Scala 是一门函数式编程(FP)语言,也就是说: -{% comment %} - -- Functions are values, and can be passed around like any other value -- Higher-order functions are directly supported -- Lambdas are built in -- Everything in Scala is an expression that returns a value -- Syntactically it’s easy to use immutable variables, and their use is encouraged -- It has a wealth of immutable collection classes in the standard library -- Those collection classes come with dozens of functional methods: they don’t mutate the collection, but instead return an updated copy of the data - -{% endcomment %} - - 函数是值,可以像任何其他值一样被传递 - 直接支持高阶函数 - 原生地支持 Lambda @@ -374,28 +263,11 @@ Scala 是一门函数式编程(FP)语言,也就是说: - 在标准库中有大量的不可变集合类 - 这些集合类带有许多函数式方法:它们不改变集合本身,而是返回数据的更新副本 -{% comment %} - -### An object-oriented language - -{% endcomment %} - ### 一门面向对象语言 -{% comment %} -Scala is an object-oriented programming (OOP) language. -Every value is an instance of a class and every “operator” is a method. -{% endcomment %} - Scala 是一门面向对象编程(OOP)语言。 每个值都是一个类的实例,每个“运算符”都是一个方法。 -{% comment %} -In Scala, all types inherit from a top-level class `Any`, whose immediate children are `AnyVal` (_value types_, such as `Int` and `Boolean`) and `AnyRef` (_reference types_, as in Java). -This means that the Java distinction between primitive types and boxed types (e.g. `int` vs. `Integer`) isn’t present in Scala. -Boxing and unboxing is completely transparent to the user. -{% endcomment %} - 在 Scala 中,所有类型都继承自顶层类 `Any`,其直接子类是 `AnyVal`(_值类型_,例如 `Int` 与 `Boolean`)和 `AnyRef`(_引用类型_,与 Java 中相同)。 这意味着 Scala 中不存在 Java 中原始类型和包装类型的区别(例如 `int` 与 `Integer`)。 装箱与拆箱对用户来说是完全透明的。 @@ -406,25 +278,10 @@ Boxing and unboxing is completely transparent to the user. - Add the “types hierarchy” image here? {% endcomment %} -{% comment %} - -### Supports FP/OOP fusion - -{% endcomment %} - ### 支持 FP 与 OOP 融合 {% comment %} -NOTE: This text in the first line comes from this slide: https://twitter.com/alexelcu/status/996408359514525696 -{% endcomment %} - -{% comment %} -The essence of Scala is the fusion of functional programming and object-oriented programming in a typed setting: - -- Functions for the logic -- Objects for the modularity - -As [Martin Odersky has stated](https://jaxenter.com/current-state-scala-odersky-interview-129495.html), “Scala was designed to show that a fusion of functional and object-oriented programming is possible and practical.” +NOTE: This text in the first line comes from this slide: https://x.com/alexelcu/status/996408359514525696 {% endcomment %} Scala 的本质是函数式编程和面向对象编程的融合: @@ -434,27 +291,8 @@ Scala 的本质是函数式编程和面向对象编程的融合: 正如 [Martin Odersky 所说](https://jaxenter.com/current-state-scala-odersky-interview-129495.html),“Scala 旨在表明函数式编程与面向对象编程的融合是切实可行的。” -{% comment %} - -### Term inference, made clearer - -{% endcomment %} - ### 表达式推断,更加清晰 -{% comment %} -Following Haskell, Scala was the second popular language to have some form of _implicits_. -In Scala 3 these concepts have been completely re-thought and more clearly implemented. - -The core idea is _term inference_: Given a type, the compiler synthesizes a “canonical” term that has that type. -In Scala, a context parameter directly leads to an inferred argument term that could also be written down explicitly. - -Use cases for this concept include implementing [type classes]({% link _overviews/scala3-book/ca-type-classes.md %}), establishing context, dependency injection, expressing capabilities, computing new types, and proving relationships between them. - -Scala 3 makes this process more clear than ever before. -Read about contextual abstractions in the [Reference documentation]({{ site.scala3ref }}/contextual.html). -{% endcomment %} - 继 Haskell 之后,Scala 是第二种具有某种形式的 _隐式_ 的流行语言。 在 Scala 3 中,这些概念经过了重新考虑并更清晰地实现。 @@ -464,28 +302,10 @@ Read about contextual abstractions in the [Reference documentation]({{ site.scal 此概念的用例包括实现[类型类]({% link _overviews/scala3-book/ca-type-classes.md %})、建立上下文、依赖注入、表达能力、计算新类型以及证明它们之间的关系。 Scala 3 使此过程比以往任何时候都更加清晰。 -请在[参考文档]({{ site.scala3ref }}/contextual.html)中阅读关于上下文抽象的内容。 - -{% comment %} - -### Client & server - -{% endcomment %} +请在[参考文档]({{ site.scala3ref }}/contextual)中阅读关于上下文抽象的内容。 ### 客户端与服务器 -{% comment %} -Scala code runs on the Java Virtual Machine (JVM), so you get all of its benefits: - -- Security -- Performance -- Memory management -- Portability and platform independence -- The ability to use the wealth of existing Java and JVM libraries - -In addition to running on the JVM, Scala also runs in the browser with Scala.js (and open source third-party tools to integrate popular JavaScript libraries), and native executables can be built with Scala Native and GraalVM. -{% endcomment %} - Scala 代码在 Java 虚拟机(JVM)上运行,因此您可以获得它的全部益处: - 安全性 @@ -496,77 +316,43 @@ Scala 代码在 Java 虚拟机(JVM)上运行,因此您可以获得它的 除了在 JVM 上运行外,Scala 还可以通过 Scala.js (以及开源的第三方工具以集成流行的 JavaScript 库)在浏览器中运行,并且可以使用Scala Native 与 GraalVM 构建原生可执行文件。 -{% comment %} - -### Seamless Java interaction - -{% endcomment %} - ### 与 Java 无缝交互 -{% comment %} -You can use Java classes and libraries in your Scala applications, and you can use Scala code in your Java applications. -In regards to the second point, large libraries like [Akka](https://akka.io) and the [Play Framework](https://www.playframework.com) are written in Scala, and can be used in Java applications. -{% endcomment %} - 您可以在 Scala 应用程序中使用 Java 类和库,也可以在 Java 应用程序中使用 Scala 代码。 对于第二点来说,诸如 [Akka](https://akka.io) 和 [Play Framework](https://www.playframework.com) 之类的大型库是用 Scala 编写的,并且它们可以在 Java 应用程序中使用。 -{% comment %} -In regards to the first point, Java classes and libraries are used in Scala applications every day. -For instance, in Scala you can read files with a Java `BufferedReader` and `FileReader`: -{% endcomment %} - 对于第一点来说,Scala 应用程序中每天都会用到 Java 类和库。 例如,在 Scala 中,您可以使用 Java 的 `BufferedReader` 和 `FileReader` 来读取文件: +{% tabs scala-features-7 %} +{% tab 'Scala 2 and 3' for=scala-features-7 %} ```scala import java.io.* val br = BufferedReader(FileReader(filename)) // read the file with `br` ... ``` - -{% comment %} -Using Java code in Scala is generally seamless. - -Java collections can also be used in Scala, and if you want to use Scala’s rich collection class methods with them, you can convert them with just a few lines of code: -{% endcomment %} +{% endtab %} +{% endtabs %} 在 Scala 中使用 Java 代码通常是无缝衔接的。 Java 集合也可以在 Scala 中使用, 如果您想将 Scala 丰富的集合类方法与其一起使用,只需几行代码即可转换它们: - +{% tabs scala-features-8 %} +{% tab 'Scala 2 and 3' for=scala-features-8 %} ```scala import scala.jdk.CollectionConverters.* val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq ``` - -{% comment %} - -### Wealth of libraries - -{% endcomment %} +{% endtab %} +{% endtabs %} ### 丰富的库 -{% comment %} -As you’ll see in the third section of this page, Scala libraries and frameworks like these have been written to power busy websites and work with huge datasets: - -1. The [Play Framework](https://www.playframework.com) is a lightweight, stateless, developer-friendly, web-friendly architecture for creating highly-scalable applications -2. [Lagom](https://www.lagomframework.com) is a microservices framework that helps you decompose your legacy monolith and build, test, and deploy entire systems of reactive microservices -3. [Apache Spark](https://spark.apache.org) is a unified analytics engine for big data processing, with built-in modules for streaming, SQL, machine learning and graph processing - -The [Awesome Scala list](https://github.com/lauris/awesome-scala) shows dozens of additional open source tools that developers have created to build Scala applications. - -In addition to server-side programming, [Scala.js](https://www.scala-js.org) is a strongly-typed replacement for writing JavaScript, with open source third-party libraries that include tools to integrate with Facebook’s React library, jQuery, and more. -{% endcomment %} - 正如您将在本页的第三部分中所看到的那样,已经有诸如此类的 Scala 库和框架被编写出来用于支撑高流量网站以及分析庞大的数据集: 1. [Play Framework](https://www.playframework.com) 是一种用于创建高度可扩展应用程序的轻量级、无状态、对开发者及Web友好的架构 -2. [Lagom](https://www.lagomframework.com) 是一种微服务框架,可帮助您分解遗留的单体应用并构建、测试和部署整个响应式微服务系统 -3. [Apache Spark](https://spark.apache.org) 是一种面向大规模数据处理的统一分析引擎,内置流、SQL、机器学习和图形处理等模块 +2. [Apache Spark](https://spark.apache.org) 是一种面向大规模数据处理的统一分析引擎,内置流、SQL、机器学习和图形处理等模块 [Awesome Scala 列表](https://github.com/lauris/awesome-scala)展示了开发人员为构建 Scala 应用程序而创建的许多其他开源工具。 @@ -577,50 +363,11 @@ The Lower-Level Features section is like the second part of an elevator pitch. Assuming you told someone about the previous high-level features and then they say, “Tell me more,” this is what you might tell them. {% endcomment %} -{% comment %} - -## Lower-level language features - -{% endcomment %} - ## 底层语言特性 -{% comment %} -Where the previous section covered high-level features of Scala 3, it’s interesting to note that at a high level you can make the same statements about both Scala 2 and Scala 3. -A decade ago Scala started with a strong foundation of desirable features, and as you’ll see in this section, those benefits have been improved with Scala 3. -{% endcomment %} - 上一节介绍了 Scala 3 的高级特性,有趣的是,您可以从高层次上对 Scala 2 和 Scala 3 作出相同的表述。 十年前,Scala 就为各种理想特性打下了坚实基础,正如您在本节中即将看到的那样,这些效益在 Scala 3 中得到了提高。 -{% comment %} -At a “sea level” view of the details---i.e., the language features programmers use everyday---Scala 3 has significant advantages over Scala 2: - -- The ability to create algebraic data types (ADTs) more concisely with enums -- An even more concise and readable syntax: - - The “quiet” control structure syntax is easier to read - - Optional braces - - Fewer symbols in the code creates less visual noise, making it easier to read - - The `new` keyword is generally no longer needed when creating class instances - - The formality of package objects have been dropped in favor of simpler “top level” definitions -- A grammar that’s more clear: - - Multiple different uses of the `implicit` keyword have been removed; those uses are replaced by more obvious keywords like `given`, `using`, and `extension`, focusing on intent over mechanism (see the [Givens][givens] section for details) - - [Extension methods][extension] replace implicit classes with a clearer and simpler mechanism - - The addition of the `open` modifier for classes makes the developer intentionally declare that a class is open for modification, thereby limiting ad-hoc extensions to a code base - - [Multiversal equality][multiversal] rules out nonsensical comparisons with `==` and `!=` (i.e., attempting to compare a `Person` to a `Planet`) - - Macros are implemented much more easily - - Union and intersection offer a flexible way to model types - - Trait parameters replace and simplify early initializers - - [Opaque type aliases][opaque_types] replace most uses of value classes, while guaranteeing the absence of boxing - - Export clauses provide a simple and general way to express aggregation, which can replace the previous facade pattern of package objects inheriting from classes - - The procedure syntax has been dropped, and the varargs syntax has been changed, both to make the language more consistent - - The `@infix` annotation makes it obvious how you want a method to be applied - - The [`@targetName`]({{ site.scala3ref }}/other-new-features/targetName.html) method annotation defines an alternate name for the method, improving Java interoperability, and letting you provide aliases for symbolic operators - -It would take too much space to demonstrate all of those features here, but follow the links in the items above to see those features in action. -All of these features are discussed in detail in the *New*, *Changed*, and *Dropped* features pages in the [Overview documentation][reference]. -{% endcomment %} - 以小见大,从程序员日常使用的语言特性来看,Scala 3 比 Scala 2 具有显著优势: - 可以用枚举更简洁地创建代数数据类型(ADT) @@ -710,62 +457,19 @@ DROPPED FEATURES and will be phased out {% endcomment %} - -{% comment %} - -## Scala ecosystem - -{% endcomment %} - ## Scala 生态系统 -{% comment %} -TODO: I didn’t put much work into this section because I don’t know if you want - to add many tools because (a) that can be seen as an endorsement and - (b) it creates a section that can need more maintenance than average - since tool popularity can wax and wane. One way to avoid the first - point is to base the lists on Github stars and activity. -{% endcomment %} - -{% comment %} -Scala has a vibrant ecosystem, with libraries and frameworks for every need. -The [“Awesome Scala” list](https://github.com/lauris/awesome-scala) provides a list of hundreds of open source projects that are available to Scala developers, and the [Scaladex](https://index.scala-lang.org) provides a searchable index of Scala libraries. -Some of the more notable libraries are listed below. -{% endcomment %} - Scala 拥有一个充满活力的生态系统,有满足各种需求的库和框架。 [Awesome Scala 列表](https://github.com/lauris/awesome-scala)提供了数百个可供 Scala 开发者使用的开源项目,[Scaladex](https://index.scala-lang.org) 则提供了 Scala 库的可搜索索引。 以下列出了一些比较著名的库: -{% comment %} - -### Web development - -{% endcomment %} - ### Web 开发 -{% comment %} - -- The [Play Framework](https://www.playframework.com) followed the Ruby on Rails model to become a lightweight, stateless, developer-friendly, web-friendly architecture for highly-scalable applications -- [Scalatra](https://scalatra.org) is a tiny, high-performance, async web framework, inspired by Sinatra -- [Finatra](https://twitter.github.io/finatra) is Scala services built on TwitterServer and Finagle -- [Scala.js](https://www.scala-js.org) is a strongly-typed replacement for JavaScript that provides a safer way to build robust front-end web applications -- [ScalaJs-React](https://github.com/japgolly/scalajs-react) lifts Facebook’s React library into Scala.js, and endeavours to make it as type-safe and Scala-friendly as possible -- [Lagom](https://www.lagomframework.com) is a microservices framework that helps you decompose your legacy monolith and build, test, and deploy entire systems of Reactive microservices -- -{% endcomment %} - - [Play Framework](https://www.playframework.com) 遵循 Ruby on Rails 模型,是一种用于高度可扩展应用程序的轻量级、无状态、对开发者及Web友好的架构 - [Scalatra](https://scalatra.org) 是一个小型的、高性能的、异步的网络框架,其灵感来自于 Sinatra - [Finatra](https://twitter.github.io/finatra) 是基于 TwitterServer 和 Finagle 构建的 Scala 服务 - [Scala.js](https://www.scala-js.org) 是 JavaScript 的强类型替代品,它提供了一种更安全的方式以构建稳健的前端 Web 应用程序 - [ScalaJs-React](https://github.com/japgolly/scalajs-react) 将 Facebook 的 React 库整合至 Scala.js,并努力使其尽可能类型安全和 Scala 友好 -- [Lagom](https://www.lagomframework.com) 是一种微服务框架,可帮助您分解遗留的单体应用并构建、测试和部署整个响应式微服务系统 - -{% comment %} -HTTP(S) libraries: -{% endcomment %} HTTP(S) 库: @@ -774,10 +478,6 @@ HTTP(S) 库: - [Http4s](https://github.com/http4s/http4s) - [Sttp](https://github.com/softwaremill/sttp) -{% comment %} -JSON libraries: -{% endcomment %} - JSON 库: - [Argonaut](https://github.com/argonaut-io/argonaut) @@ -785,107 +485,51 @@ JSON 库: - [Json4s](https://github.com/json4s/json4s) - [Play-JSON](https://github.com/playframework/play-json) -{% comment %} -Serialization: -{% endcomment %} - 序列化: - [ScalaPB](https://github.com/scalapb/ScalaPB) -{% comment %} - -### Science and data analysis: - -{% endcomment %} - ### 科学和数据分析 - [Algebird](https://github.com/twitter/algebird) - [Spire](https://github.com/typelevel/spire) - [Squants](https://github.com/typelevel/squants) -{% comment %} - -### Big data - -{% endcomment %} - ### 大数据 - [Apache Spark](https://github.com/apache/spark) - [Apache Flink](https://github.com/apache/flink) -{% comment %} - -### AI, machine learning - -- [BigDL](https://github.com/intel-analytics/BigDL) (Distributed Deep Learning Framework for Apache Spark) for Apache Spark -- [TensorFlow Scala](https://github.com/eaplatanios/tensorflow_scala) - -{% endcomment %} - ### 人工智能,机器学习 - [BigDL](https://github.com/intel-analytics/BigDL) (用于 Apache Spark 的分布式深度学习框架) - [TensorFlow Scala](https://github.com/eaplatanios/tensorflow_scala) -{% comment %} - -### Functional Programming & Functional Reactive Programming - -{% endcomment %} - ### 函数式编程 & 函数式响应式编程 -{% comment %} -FP: -{% endcomment %} - 函数式编程: - [Cats](https://github.com/typelevel/cats) - [Zio](https://github.com/zio/zio) -{% comment %} -Functional reactive programming (FRP): -{% endcomment %} - 函数式响应式编程(FRP) - [fs2](https://github.com/typelevel/fs2) - [monix](https://github.com/monix/monix) -{% comment %} - -### Build tools - -{% endcomment %} - ### 构建工具 - [sbt](https://www.scala-sbt.org) - [Gradle](https://gradle.org) - [Mill](https://github.com/lihaoyi/mill) -{% comment %} - -## Summary - -As this page shows, Scala has many terrific programming language features at a high level, at an everyday programming level, and through its developer ecosystem. - -{% endcomment %} - ## 总结 如此页所示,Scala 在高层、日常编程层面以及贯穿开发者生态系统都具有许多出色的编程语言特性。 [reference]: {{ site.scala3ref }}/overview.html -[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} -[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} -[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} -[opaque_types]: {% link _overviews/scala3-book/types-opaque-types.md %} - - - +[multiversal]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[extension]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[givens]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %} +[opaque_types]: {% link _zh-cn/overviews/scala3-book/types-opaque-types.md %} diff --git a/_zh-cn/overviews/scala3-book/scala-for-java-devs.md b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md new file mode 100644 index 0000000000..c8f449fd56 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md @@ -0,0 +1,1279 @@ +--- +title: 向 Java 开发者介绍Scala +type: chapter +description: This page is for Java developers who are interested in learning about Scala 3. +language: zh-cn +num: 73 +previous-page: interacting-with-java +next-page: scala-for-javascript-devs + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% include_relative scala4x.css %} +
+ +此页面通过共享每种语言的并排示例,对 Java 和 Scala 编程语言进行了比较。 +它适用于了解 Java 并希望了解 Scala 的程序员,特别是通过 Scala 特性与 Java 特性的对比来了解。 + +## 概述 + +在进入示例之前,第一部分提供了以下部分的相对简短的介绍和总结。 +它从高层次上介绍了 Java 和 Scala 之间的异同,然后介绍了您在每天编写代码时会遇到的差异。 + +### 高层次的相似性 + +在高层次上,Scala 与 Java 有以下相似之处: + +- Scala代码编译成_.class_文件,打包成JAR文件,运行在JVM上 +- 这是一种[面向对象编程][modeling-oop] (OOP) 语言 +- 它是静态类型的 +- 两种语言都支持 lambdas 和 [高阶函数][hofs] +- 它们都可以与 IntelliJ IDEA 和 Microsoft VS Code 等 IDE 一起使用 +- 可以使用 Gradle、Ant 和 Maven 等构建工具构建项目 +- 它具有用于构建服务器端、网络密集型应用程序的出色库和框架,包括 Web 服务器应用程序、微服务、机器学习等(参见 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)) +- Java 和 Scala 都可以使用 Scala 库: + - 他们可以使用 [Akka actor 库](https://akka.io) 来构建基于 actor 的并发系统,并使用 Apache Spark 来构建数据密集型应用程序 + - 他们可以使用 [Play Framework](https://www.playframework.com) 开发服务器端应用程序 +- 您可以使用 [GraalVM](https://www.graalvm.org) 将您的项目编译为本机可执行文件 +- Scala 可以无缝使用为 Java 开发的大量库 + +### 高层次的差异 + +同样在高层次上,Java 和 Scala 之间的区别是: + +- Scala 语法简洁易读;我们称之为_表现力_ +- 虽然它是静态类型的,但 Scala 经常感觉像是一门动态语言 +- Scala 是一种纯 OOP 语言,因此每个对象都是类的一个实例,而像运算符一样的符号 `+` 和 `+=` 是真正的方法;这意味着您可以创建自己的运算符 +- 除了是纯OOP语言,Scala还是纯FP语言;实际上,它鼓励 OOP 和 FP 的融合,具有用于逻辑的函数和用于模块化的对象 +- Scala 拥有一整套不可变集合,包括 `List`、`Vector` 和不可变的 `Map` 和 `Set` 实现 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- Scala 习惯上倾向缺省使用不可变性:鼓励您使用不可变(`final`)变量和不可变集合 +- 惯用的 Scala 代码不使用 `null`,因此不会遭受 `NullPointerException` +- Scala 生态系统在 sbt、Mill 等中还有其他 [构建工具][tools] +- 除了在 JVM 上运行之外,[Scala.js](https://www.scala-js.org) 项目允许您使用 Scala 作为 JavaScript 替代品 +- [Scala Native](http://www.scala-native.org) 项目添加了低级结构,让您可以编写“系统”级代码,也可以编译为本机可执行文件 + +{% comment %} +These are several notes that came up early in the writing process, and I (Alvin) can’t really address them: +TODO: Need a good, simple way to state that Scala has a sound type system +TODO: Points to make about Scala’s consistency? +TODO: Add a point about how the type system lets you express details as desired +{% endcomment %} + +### 编程层次差异 + +最后,这些是您在编写代码时每天都会看到的一些差异: + +- Scala 的语法极其一致 +- 变量和参数被定义为`val`(不可变,如Java中的`final`)或`var`(可变) +- _类型推导_ 让您的代码感觉是动态类型的,并有助于保持您的代码简洁 +- 除了简单的 `for` 循环之外,Scala 还具有强大的 `for` comprehensions,可以根据您的算法产生结果 +- 模式匹配和 `match` 表达式将改变你编写代码的方式 +- 默认情况下编写不可变代码会导致编写_表达式_而不是_语句_;随着时间的推移,您会发​​现编写表达式可以简化您的代码(和您的测试) +- [顶层定义][toplevel] 让您可以将方法、字段和其他定义放在任何地方,同时也带来简洁、富有表现力的代码 +- 您可以通过将多个 traits “混合”到类和对象中来创建_混搭_(特征类似于 Java 8 和更新版本中的接口) +- 默认情况下类是封闭的,支持 Joshua Bloch 在 _Effective Java_ 的习惯用法,“Design and document for inheritance or else forbid it” +- Scala 的 [上下文抽象][contextual] 和 _术语推导_ 提供了一系列特性: + - [扩展方法][extension-methods] 让您向封闭类添加新功能 + - [_给_实例][givens] 让您定义编译器可以在 _using_ 点合成的术语,从而使您的代码不那么冗长,实质上让编译器为您编写代码 + - [多元等式][multiversal] 允许您在编译时将相等比较限制为仅那些有意义的比较 +- Scala 拥有最先进的第三方开源函数式编程库 +- Scala 样例类就像 Java 14 中的记录;它们可以帮助您在编写 FP 代码时对数据进行建模,并内置对模式匹配和克隆等概念的支持 +- 由于名称参数、中缀符号、可选括号、扩展方法和 [高阶函数][hofs] 等功能,您可以创建自己的“控制结构”和 DSL +- Scala 文件不必根据它们包含的类或 trait 来命名 +- 许多其他好东西:伴生类和对象、宏、[联合][union-types] 和 [交集][intersection-types]、数字字面量、多参数列表、参数的默认值、命名参数等 + +### 用例子来进行特性对比 + +鉴于该介绍,以下部分提供了 Java 和 Scala 编程语言功能的并排比较。 + +## OOP 风格的类和方法 + +本节提供了与 OOP 风格的类和方法相关的特性的比较。 + +### 注释: + + + + + + + + + + +
+ // +
/* ... */ +
/** ... */
+
+ // +
/* ... */ +
/** ... */
+
+ +### OOP 风格类,主构造函数: + +Scala不遵循JavaBeans标准,因此我们在这里展示的Java代码 +与它后面的Scala代码等效,而不是显示以JavaBeans风格编写 +的Java代码。 + + + + + + + + + + +
+ class Person { +
  public String firstName; +
  public String lastName; +
  public int age; +
  public Person( +
    String firstName, +
    String lastName, +
    int age +
  ) { +
    this.firstName = firstName; +
    this.lastName = lastName; +
    this.age = age; +
  } +
  public String toString() { +
    return String.format("%s %s is %d years old.", firstName, lastName, age); +
  } +
}
+
+ class Person ( +
  var firstName: String, +
  var lastName: String, +
  var age: Int +
):   +
  override def toString = s"$firstName $lastName is $age years old." +
+
+ +### 辅助构造函数: + + + + + + + + + + +
+ public class Person { +
  public String firstName; +
  public String lastName; +
  public int age; +
+
  // primary constructor +
  public Person( +
    String firstName, +
    String lastName, +
    int age +
  ) { +
    this.firstName = firstName; +
    this.lastName = lastName; +
    this.age = age; +
  } +
+
  // zero-arg constructor +
  public Person() { +
    this("", "", 0); +
  } +
+
  // one-arg constructor +
  public Person(String firstName) { +
    this(firstName, "", 0); +
  } +
+
  // two-arg constructor +
  public Person( +
    String firstName, +
    String lastName +
  ) { +
    this(firstName, lastName, 0); +
  } +
+
}
+
+ class Person ( +
  var firstName: String, +
  var lastName: String, +
  var age: Int +
): +
    // zero-arg auxiliary constructor +
    def this() = this("", "", 0) +
+
    // one-arg auxiliary constructor +
    def this(firstName: String) = +
      this(firstName, "", 0) +
+
    // two-arg auxiliary constructor +
    def this( +
      firstName: String, +
      lastName: String +
    ) = +
      this(firstName, lastName, 0) +
+
end Person
+
+ +### 类默认是封闭的: +“Plan for inheritance or else forbid it.” + + + + + + + + + + +
+ final class Person +
+ class Person +
+ +### 为扩展开放的类: + + + + + + + + + + +
+ class Person +
+ open class Person +
+ +### 单行方法: + + + + + + + + + + +
+ public int add(int a, int b) { +
  return a + b; +
}
+
+ def add(a: Int, b: Int): Int = a + b +
+ +### 多行方法: + + + + + + + + + + +
+ public void walkThenRun() { +
  System.out.println("walk"); +
  System.out.println("run"); +
}
+
+ def walkThenRun() = +
  println("walk") +
  println("run")
+
+ +### 不可变字段: + + + + + + + + + + +
+ final int i = 1; +
+ val i = 1 +
+ +### 可变字段: + + + + + + + + + + +
+ int i = 1; +
var i = 1;
+
+ var i = 1 +
+ +## 接口、trait 和继承 + +本节将Java接口与Scala trait 进行比较,包括类如何扩展接口和 trait。 + +### 接口/trait: + + + + + + + + + + +
+ public interface Marker; +
+ trait Marker +
+ +### 简单接口: + + + + + + + + + + +
+ public interface Adder { +
  public int add(int a, int b); +
}
+
+ trait Adder: +
  def add(a: Int, b: Int): Int
+
+ +### 有实体方法的接口: + + + + + + + + + + +
+ public interface Adder { +
  int add(int a, int b); +
  default int multiply( +
    int a, int b +
  ) { +
    return a * b; +
  } +
}
+
+ trait Adder: +
  def add(a: Int, b: Int): Int +
  def multiply(a: Int, b: Int): Int = +
    a * b
+
+ +### 继承: + + + + + + + + + + +
+ class Dog extends Animal implements HasLegs, HasTail +
+ class Dog extends Animal, HasLegs, HasTail +
+ +### 扩展多个接口 + +这些接口和特征具有具体的、已实现的方法(默认方法): + + + + + + + + + + +
+ interface Adder { +
  default int add(int a, int b) { +
    return a + b; +
  } +
} +
+
interface Multiplier { +
  default int multiply ( +
    int a, +
    int b) +
  { +
    return a * b; +
  } +
} +
+
public class JavaMath
implements Adder, Multiplier {} +
+
JavaMath jm = new JavaMath(); +
jm.add(1,1); +
jm.multiply(2,2);
+
+ trait Adder: +
  def add(a: Int, b: Int) = a + b +
+
trait Multiplier: +
  def multiply(a: Int, b: Int) = a * b +
+
class ScalaMath extends Adder, Multiplier +
+
val sm = new ScalaMath +
sm.add(1,1) +
sm.multiply(2,2)
+
+ +### 混搭: + + + + + + + + + + +
+ N/A +
+ class DavidBanner +
+
trait Angry: +
  def beAngry() = +
    println("You won’t like me ...") +
+
trait Big: +
  println("I’m big") +
+
trait Green: +
  println("I’m green") +
+
// mix in the traits as DavidBanner +
// is created +
val hulk = new DavidBanner with Big with Angry with Green +
+
+ +## 控制结构 + +本节比较在 Java 和 Scala 中的[控制结构][control]。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if (x == 1) { System.out.println(1); } +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if (x == 1) { +
  System.out.println("x is 1, as you can see:") +
  System.out.println(x) +
}
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if (x < 0) { +
  System.out.println("negative") +
} else if (x == 0) { +
  System.out.println("zero") +
} else { +
  System.out.println("positive") +
}
+
+ if x < 0 then +
  println("negative") +
else if x == 0 +
  println("zero") +
else +
  println("positive")
+
+ +### `if` 作为方法体: + + + + + + + + + + +
+ public int min(int a, int b) { +
  return (a < b) ? a : b; +
}
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +### 从 `if` 返回值: + +在 Java 中调用_三元运算符_: + + + + + + + + + + +
+ int minVal = (a < b) ? a : b; +
+ val minValue = if a < b then a else b +
+ +### `while` 循环: + + + + + + + + + + +
+ while (i < 3) { +
  System.out.println(i); +
  i++; +
}
+
+ while i < 3 do +
  println(i) +
  i += 1
+
+ +### `for` 循环,单行: + + + + + + + + + + +
+ for (int i: ints) { +
  System.out.println(i); +
}
+
+ //preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +### `for` 循环,多行: + + + + + + + + + + +
+ for (int i: ints) { +
  int x = i * 2; +
  System.out.println(x); +
}
+
+ for +
  i <- ints +
do +
  val x = i * 2 +
  println(s"i = $i, x = $x")
+
+ +### `for` 循环,多生成器: + + + + + + + + + + +
+ for (int i: ints1) { +
  for (int j: chars) { +
    for (int k: ints2) { +
      System.out.printf("i = %d, j = %d, k = %d\n", i,j,k); +
    } +
  } +
}
+
+ for +
  i <- 1 to 2 +
  j <- 'a' to 'b' +
  k <- 1 to 10 by 5 +
do +
  println(s"i = $i, j = $j, k = $k")
+
+ +### 带守卫(`if`)表达式的生成器: + + + + + + + + + + +
+ List ints = +
  ArrayList(1,2,3,4,5,6,7,8,9,10); +
+
for (int i: ints) { +
  if (i % 2 == 0 && i < 5) { +
    System.out.println(x); +
  } +
}
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### `for` comprehension: + + + + + + + + + + +
+ N/A +
+ val list = +
  for +
    i <- 1 to 3 +
  yield +
    i * 10 +
// list: Vector(10, 20, 30)
+
+ +### switch/match: + + + + + + + + + + +
+ String monthAsString = ""; +
switch(day) { +
  case 1: monthAsString = "January"; +
          break; +
  case 2: monthAsString = "February"; +
          break; +
  default: monthAsString = "Other"; +
          break; +
}
+
+ val monthAsString = day match +
  case 1 => "January" +
  case 2 => "February" +
  _ => "Other" +
+
+ +### switch/match, 每个情况下多个条件: + + + + + + + + + + +
+ String numAsString = ""; +
switch (i) { +
  case 1: case 3: +
  case 5: case 7: case 9: +
    numAsString = "odd"; +
    break; +
  case 2: case 4: +
  case 6: case 8: case 10: +
    numAsString = "even"; +
    break; +
  default: +
    numAsString = "too big"; +
    break; +
}
+
+ val numAsString = i match +
  case 1 | 3 | 5 | 7 | 9 => "odd" +
  case 2 | 4 | 6 | 8 | 10 => "even" +
  case _ => "too big" +
+
+ +### try/catch/finally: + + + + + + + + + + +
+ try { +
  writeTextToFile(text); +
} catch (IOException ioe) { +
  println(ioe.getMessage()) +
} catch (NumberFormatException nfe) { +
  println(nfe.getMessage()) +
} finally { +
  println("Clean up resources here.") +
}
+
+ try +
  writeTextToFile(text) +
catch +
  case ioe: IOException => +
    println(ioe.getMessage) +
  case nfe: NumberFormatException => +
    println(nfe.getMessage) +
finally +
  println("Clean up resources here.")
+
+ +## 集合类 + +本节比较 Java 和 Scala 里的[集合类][collections-classes]。 + +### 不可变集合类 + +如何创建不可变集合实例的例子。 + +### Sequences: + + + + + + + + + + +
+ List strings = List.of("a", "b", "c"); +
+ val strings = List("a", "b", "c") +
val strings = Vector("a", "b", "c")
+
+ +### Sets: + + + + + + + + + + +
+ Set set = Set.of("a", "b", "c"); +
+ val set = Set("a", "b", "c") +
+ +### Maps: + + + + + + + + + + +
+ Map map = Map.of( +
  "a", 1, +
  "b", 2, +
  "c", 3 +
);
+
+ val map = Map( +
  "a" -> 1, +
  "b" -> 2, +
  "c" -> 3 +
)
+
+ +### 可变集合类 + +Scala 在其 _scala.collection.mutable_ 包中有可变集合类,例如 `ArrayBuffer`、`Map` 和 `Set`。 +在 [导入它们][imports] 到当前作用域之后,创建它们就像刚刚显示的不可变 `List`、`Vector`、`Map` 和 `Set` 示例一样。 + +Scala 还有一个 `Array` 类,您可以将其视为 Java `array` 原始类型的包装器。 +一个 Scala `Array[A]` 映射到一个 Java `A[]`,所以你可以认为这个是 Scala `Array[String]`: + +```scala +val a = Array("a", "b") +``` + +这个追溯到 Java 的 `String[]`: + +```java +String[] a = {"a", "b"}; +``` + +但是,Scala `Array` 还具有您期望在 Scala 集合中使用的所有函数方法,包括 `map` 和 `filter`: + +```scala +val nums = Array(1, 2, 3, 4, 5) +val doubledNums = nums.map(_ * 2) +val 过滤Nums = nums.filter(_ > 2) +``` + +因为 Scala `Array` 的表示方式与 Java `array` 相同,所以您可以轻松地在 Scala 代码中使用返回数组的 Java 方法。 + +> 尽管讨论了 `Array`,但请记住,在 Scala 中通常有可能更适合的 `Array` 替代品。 +> 数组对于与其他语言(Java、JavaScript)的互操作很有用,并且在编写需要从底层平台获得最大性能的低级代码时也很有用。但总的来说,当你需要使用序列时,Scala 的习惯用法是更喜欢像 `Vector` 和 `List` 这样的不可变序列,然后在你真的需要可变序列时使用 `ArrayBuffer`。 + +您还可以使用 Scala `CollectionConverters` 对象在 Java 和 Scala 集合类之间进行转换。 +在不同的包中有两个对象,一个用于从 Java 转换为 Scala,另一个用于从 Scala 转换为 Java。 +下表显示了可能的转换: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JavaScala
java.util.Collectionscala.collection.Iterable
java.util.Listscala.collection.mutable.Buffer
java.util.Setscala.collection.mutable.Set
java.util.Mapscala.collection.mutable.Map
java.util.concurrent.ConcurrentMapscala.collection.mutable.ConcurrentMap
java.util.Dictionaryscala.collection.mutable.Map
+ +## 集合类的方法 + +由于能够将 Java 集合视为流,Java 和 Scala 现在可以使用许多相同的通用函数方法: + +- `map` +- `filter` +- `forEach`/`foreach` +- `findFirst`/`find` +- `reduce` + +如果您习惯在 Java 中将这些方法与 lambda 表达式一起使用,您会发现在 Scala 的 [集合类][collections-classes] 上使用相同的方法很容易。 + +Scala 也有_数十个_其他 [集合方法][collections-methods],包括 `head`、`tail`、`drop`、`take`、`distinct`、`flatten` 等等。 +起初你可能想知道为什么会有这么多方法,但是在使用 Scala 之后你会意识到_因为_有这些方法,你很少需要再编写自定义的 `for` 循环了。 + +(这也意味着你也很少需要_读_自定义的 `for` 循环。 +因为开发人员倾向于在_读_代码上花费的时间是_编写_代码的十倍,这很重要。) + +## 元组 + +Java 元组是这样创建的: + +```scala +Pair pair = + new Pair("Eleven", 11); + +Triplet triplet = + Triplet.with("Eleven", 11, 11.0); +Quartet triplet = + Quartet.with("Eleven", 11, 11.0, new Person("Eleven")); +``` + +其他 Java 元组名称是 Quintet、Sextet、Septet、Octet、Ennead、Decade。 + +Scala 中任何大小的元组都是通过将值放在括号内来创建的,如下所示: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + +## 枚举 + +本节比较 Java 和 Scala 中的枚举。 + +### 基本枚举: + + + + + + + + + + +
+ enum Color { +
  RED, GREEN, BLUE +
}
+
+ enum Color: +
  case Red, Green, Blue
+
+ +### 参数化的枚举: + + + + + + + + + + +
+ enum Color { +
  Red(0xFF0000), +
  Green(0x00FF00), +
  Blue(0x0000FF); +
+
  private int rgb; +
+
  Color(int rgb) { +
    this.rgb = rgb; +
  } +
}
+
+ enum Color(val rgb: Int): +
  case Red   extends Color(0xFF0000) +
  case Green extends Color(0x00FF00) +
  case Blue  extends Color(0x0000FF)
+
+ +### 用户定义的枚举成员: + + + + + + + + + + +
+ enum Planet { +
  MERCURY (3.303e+23, 2.4397e6), +
  VENUS   (4.869e+24, 6.0518e6), +
  EARTH   (5.976e+24, 6.37814e6); +
  // more planets ... +
+
  private final double mass; +
  private final double radius; +
+
  Planet(double mass, double radius) { +
    this.mass = mass; +
    this.radius = radius; +
  } +
+
  public static final double G = +
    6.67300E-11; +
+
  private double mass() { +
    return mass; +
  } +
+
  private double radius() { +
    return radius; +
  } +
+
  double surfaceGravity() { +
    return G * mass / +
      (radius * radius); +
  } +
+
  double surfaceWeight( +
    double otherMass +
  ) { +
    return otherMass * +
      surfaceGravity(); +
  } +
+
}
+
+ enum Planet( +
  mass: Double, +
  radius: Double +
): +
  case Mercury extends
    Planet(3.303e+23, 2.4397e6) +
  case Venus extends
    Planet(4.869e+24, 6.0518e6) +
  case Earth extends
    Planet(5.976e+24, 6.37814e6) +
    // more planets ... +
+
  private final val G = 6.67300E-11 +
+
  def surfaceGravity =
    G * mass / (radius * radius) +
+
  def surfaceWeight(otherMass: Double) +
    = otherMass * surfaceGravity
+
+ +## 异常和错误处理 + +本节介绍 Java 和 Scala 中的异常处理之间的差异。 + +### Java 使用检查异常 + +Java 使用检查的异常,因此在 Java 代码中,您历来编写过 `try`/`catch`/`finally` 块,以及方法上的 `throws` 子句: + +```scala +public int makeInt(String s) +throws NumberFormatException { + // code here to convert a String to an int +} +``` + +### Scala 不使用检查异常 + +Scala 的习惯用法是_不_使用这样的检查异常。 +在处理可能抛出异常的代码时,您可以使用 `try`/`catch`/`finally` 块从抛出异常的代码中捕获异常,但是如何从那里开始的方式是不同的。 + +解释这一点的最好方法是说 Scala 代码是由具有返回值的_表达式_组成的。 +其结果是,最终你写代就像写一系列代数表达式: + +```scala +val a = f(x) +val b = g(a,z) +val c = h(b,y) +``` + +这很好,它只是代数。 +您创建方程来解决小问题,然后组合方程来解决更大的问题。 + +非常重要的是——正如你在代数课程中所记得的那样——代数表达式不会短路——它们不会抛出会破坏一系列方程的异常。 + +因此,在 Scala 中,我们的方法不会抛出异常。 +相反,它们返回像 `Option` 这样的类型。 +例如,这个 `makeInt` 方法捕获一个可能的异常并返回一个 `Option` 值: + +```scala +def makeInt(s: String): Option[Int] = + try + Some(s.toInt) + catch + case e: NumberFormatException => None +``` + +Scala `Option` 类似于 Java `Optional` 类。 +如图所示,如果 string 到 int 的转换成功,则在 `Some` 值中返回 `Int`,如果失败,则返回 `None` 值。 +`Some` 和 `None` 是 `Option` 的子类型,因此该方法被声明为返回 `Option[Int]` 类型。 + +当您有一个 `Option` 值时,例如 `makeInt` 返回的值,有很多方法可以使用它,具体取决于您的需要。 +此代码显示了一种可能的方法: + +```scala +makeInt(aString) match + case Some(i) => println(s"Int i = $i") + case None => println(s"Could not convert $aString to an Int.") +``` + +`Option` 在 Scala 中很常用,它内置在标准库的许多类中。 +其他类似的类的集合,例如 Try/Success/Failure 和 Either/Left/Right,提供了更大的灵活性。 + +有关在 Scala 中处理错误和异常的更多信息,请参阅 [函数式错误处理][error-handling] 部分。 + +## Scala 独有的概念 + +以上就是 Java 和 Scala 语言的比较。 + +Scala 中还有其他一些概念目前在 Java 11 中是没有的。 +这包括: + +- 与 Scala 的 [上下文抽象][contextual] 相关的一切 +- 几个 Scala 方法特性: + - 多参数列表 + - 默认参数值 + - 调用方法时使用命名参数 +- 样例类(如 Java 14 中的“记录”)、样例对象以及伴生类和对象(参见 [领域建模][modeling-intro])一章 +- 创建自己的控制结构和 DSL 的能力 +- [顶级定义][toplevel] +- 模式匹配 +- `match` 表达式的高级特性 +- 类型 lambdas +- trait参数 +- [不透明类型别名][opaque] +- [多元相等性][equality] +- [类型类][type-classes] +- 中缀方法 +- 宏和元编程 + + +[collections-classes]: {% link _zh-cn/overviews/scala3-book/collections-classes.md %} +[collections-methods]: {% link _zh-cn/overviews/scala3-book/collections-methods.md %} +[control]: {% link _zh-cn/overviews/scala3-book/control-structures.md %} +[equality]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[error-handling]: {% link _zh-cn/overviews/scala3-book/fp-functional-error-handling.md %} +[extension-methods]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[givens]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %} +[hofs]: {% link _zh-cn/overviews/scala3-book/fun-hofs.md %} +[imports]: {% link _zh-cn/overviews/scala3-book/packaging-imports.md %} +[modeling-intro]: {% link _zh-cn/overviews/scala3-book/domain-modeling-intro.md %} +[modeling-oop]: {% link _zh-cn/overviews/scala3-book/domain-modeling-oop.md %} +[opaque]: {% link _zh-cn/overviews/scala3-book/types-opaque-types.md %} +[tools]: {% link _zh-cn/overviews/scala3-book/scala-tools.md %} +[toplevel]: {% link _zh-cn/overviews/scala3-book/taste-toplevel-definitions.md %} +[type-classes]: {% link _zh-cn/overviews/scala3-book/ca-type-classes.md %} + +[concurrency]: {% link _zh-cn/overviews/scala3-book/concurrency.md %} +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control]: {% link _zh-cn/overviews/scala3-book/control-structures.md %} +[fp-intro]: {% link _zh-cn/overviews/scala3-book/fp-intro.md %} +[intersection-types]: {% link _zh-cn/overviews/scala3-book/types-intersection.md %} +[modeling-fp]: {% link _zh-cn/overviews/scala3-book/domain-modeling-fp.md %} +[multiversal]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[union-types]: {% link _zh-cn/overviews/scala3-book/types-union.md %} + +
diff --git a/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md b/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md new file mode 100644 index 0000000000..0ec0816eed --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md @@ -0,0 +1,1336 @@ +--- +title: Scala for JavaScript Developers +type: chapter +description: This chapter provides an introduction to Scala 3 for JavaScript developers +language: zh-cn +num: 74 +previous-page: scala-for-java-devs +next-page: scala-for-python-devs + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% include_relative scala4x.css %} +
+ +此页面提供了 JavaScript 和 Scala 编程语言之间的比较。 +它适用于了解 JavaScript 并希望了解 Scala 的程序员,特别是通过查看 JavaScript 语言功能与 Scala 比较的示例。 + +## 概述 + +本节对以下各节进行了相对简短的介绍和总结。 +它从高层次上介绍了 JavaScript 和 Scala 之间的异同,然后介绍了您在每天编写代码时会遇到的差异。 + +### 高层次的相似性 + +在高层次上,Scala 与 JavaScript 有以下相似之处: + +- 两者都被认为是高级编程语言,您不必关心指针和手动内存管理等低级概念 +- 两者都有一个相对简单、简洁的语法 +- 两者都支持 C/C++/Java 风格的花括号语法,用于编写方法和其他代码块 +- 两者都包括面向对象编程 (OOP) 的特性(如类) +- 两者都包含用于 [函数式编程][fp-intro] (FP) 的(如 lambda) +- JavaScript 在浏览器和 Node.js 等其他环境中运行。 + Scala 的 [Scala.js](https://www.scala-js.org) 风格以 JavaScript 为目标,因此 Scala 程序可以在相同的环境中运行。 +- 开发人员使用 [Node.js](https://nodejs.org) 在 JavaScript 和 Scala 中编写服务器端应用程序; [Play Framework](https://www.playframework.com/) 之类的项目也可以让您在 Scala 中编写服务器端应用程序 +- 两种语言都有相似的 `if` 语句、`while` 循环和 `for` 循环 +- 从 [在这个 Scala.js 页面](https://www.scala-js.org/libraries/index.html) 开始,您会发现许多支持 React、Angular、jQuery 和许多其他 JavaScript 和Scala 库 +- JavaScript 对象是可变的;以命令式风格编写时,Scala 对象_可以_是可变的 +- JavaScript 和 Scala 都支持 _promises_ 作为处理异步计算结果的一种方式([Scala concurrency][concurrency] 使用期货和承诺) + +### 高层次差异 + +同样在高层次上,JavaScript 和 Scala 之间的一些区别是: + +- JavaScript 是动态类型的,Scala 是静态类型的 + - 尽管 Scala 是静态类型的,但类型推断之类的特性让它感觉像是一种动态语言(正如您将在下面的示例中看到的那样) +- Scala 惯用语默认支持不变性:鼓励您使用不可变变量和不可变集合 +- Scala 语法简洁易读;我们称之为_表现力_ +- Scala 是一种纯 OOP 语言,因此每个对象都是类的一个实例,而像运算符一样的符号 `+` 和 `+=` 是真正的方法;这意味着您可以创建自己的方法作为运算符 +- 作为一种纯 OOP 语言和纯 FP 语言,Scala 鼓励 OOP 和 FP 的融合,具有用于逻辑的函数和用于模块化的不可变对象 +- Scala 拥有最先进的第三方开源函数式编程库 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- [Scala Native](https://scala-native.org/) 项目让您可以编写“系统”级代码,也可以编译为本机可执行文件 + +### 编程层次差异 + +在较低的层次上,这些是您在编写代码时每天都会看到的一些差异: + +- Scala 变量和参数使用 `val`(不可变,如 JavaScript `const`)或 `var`(可变,如 JavaScript `var` 或 `let`)定义 +- Scala 不在行尾使用分号 +- Scala 是静态类型的,尽管在许多情况下您不需要声明类型 +- Scala 使用 trait 作为接口并创建_混搭_ +- 除了简单的 `for` 循环之外,Scala 还具有强大的 `for` comprehensions,可以根据您的算法产生结果 +- 模式匹配和 `match` 表达式将改变你编写代码的方式 +- Scala 的 Scala 的 [上下文抽象][contextual] 和 _术语推导_ 提供了一系列特性: + - [扩展方法][extension-methods] 允许您在不破坏模块化的情况下向封闭类添加新功能,方法是仅在特定范围内可用(与猴子补丁相反,它会污染代码的其他区域) + - [给实例][givens] 让您定义编译器可以用来为您合成代码的术语 + - 类型安全和[多元等式][multiversal]让您将相等比较——在编译时——仅限于那些有意义的比较 +- 由于名称参数、中缀符号、可选括号、扩展方法和 [高阶函数][hofs] 等功能,您可以创建自己的“控制结构”和 DSL +- 您可以在本书中阅读到许多其他好东西:样例类、伴生类和对象、宏、[联合][union-type]和[交集][intersection-types]类型、多参数列表、命名参数等 + +## 变量和类型 + +### 注释 + + + + + + + + + + +
+ // +
/* ... */ +
/** ... */
+
+ // +
/* ... */ +
/** ... */
+
+ +### 可变变量 + + + + + + + + + + +
+ let   // now preferred for mutable +
var   // old mutable style
+
+ var  // used for mutable variables +
+ +### 不可变变量 + + + + + + + + + + +
+ const +
+ val +
+ +Scala 的经验法则是使用 `val` 声明变量,除非有特定原因需要可变变量。 + +## 命名标准 + +JavaScript 和 Scala 通常使用相同的 _CamelCase_ 命名标准。 +变量命名为 `myVariableName`,方法命名为 `lastIndexOf`,类和对象命名为 `Animal` 和 `PrintedBook` 。 + +## 字符串 + +JavaScript 和 Scala 中字符串的许多用法相似,但 Scala 仅对简单字符串使用双引号,对多行字符串使用三引号。 + +### 字符串基础 + + + + + + + + + + +
+ // use single- or double-quotes +
let msg = 'Hello, world'; +
let msg = "Hello, world";
+
+ // use only double-quotes +
val msg = "Hello, world"
+
+ +### 插入 + + + + + + + + + + +
+ let name = 'Joe'; +
+
// JavaScript uses backticks +
let msg = `Hello, ${name}`;
+
+ val name = "Joe" +
val age = 42 +
val weight = 180.5 +
+
// use `s` before a string for simple interpolation +
println(s"Hi, $name")   // "Hi, Joe" +
println(s"${1 + 1}")    // "2" +
+
// `f` before a string allows printf-style formatting. +
// this example prints: +
// "Joe is 42 years old, and weighs" +
// "180.5 pounds." +
println(f"$name is $age years old, and weighs $weight%.1f pounds.")
+
+ +### 带插入的多行字符串 + + + + + + + + + + +
+ let name = "joe"; +
let str = ` +
Hello, ${name}. +
This is a multiline string. +
`; +
+
+ val name = "Martin Odersky" +
+
val quote = s""" +
|$name says +
|Scala is a fusion of +
|OOP and FP. +
""".stripMargin.replaceAll("\n", " ").trim +
+
// result: +
// "Martin Odersky says Scala is a fusion of OOP and FP." +
+
+ +JavaScript 和 Scala 也有类似的处理字符串的方法,包括 `charAt`、`concat`、`indexOf` 等等。 +`\n`、`\f`、`\t` 等转义字符在两种语言中也是相同的。 + +## 数字和算术 + +JavaScript 和 Scala 之间的数字运算符很相似。 +最大的不同是 Scala 不提供 `++` 和 `--` 运算符。 + +### 数字运算符: + + + + + + + + + + +
+ let x = 1; +
let y = 2.0; +
  +
let a = 1 + 1; +
let b = 2 - 1; +
let c = 2 * 2; +
let d = 4 / 2; +
let e = 5 % 2; +
+
+ val x = 1 +
val y = 2.0 +
  +
val a = 1 + 1 +
val b = 2 - 1 +
val c = 2 * 2 +
val d = 4 / 2 +
val e = 5 % 2 +
+
+ +### 自增和自减: + + + + + + + + + + +
+ i++; +
i += 1; +
+
i--; +
i -= 1;
+
+ i += 1; +
i -= 1;
+
+ +或许最大的区别在于像`+`和`-`这样的“操作符”在Scala中实际上是_方法_,而不是操作符。 +Scala 数字也有这些相关的方法: + +```scala +var a = 2 +a *= 2 // 4 +a /= 2 // 2 +``` + +Scala 的 `Double` 类型最接近于 JavaScript 的默认 `number` 类型, +`Int` 表示有符号的 32 位整数值,而 `BigInt` 对应于 JavaScript 的 `bigint`。 + +这些是 Scala `Int` 和 `Double` 值。 +请注意,类型不必显式声明: + +```scala +val i = 1 // Int +val d = 1.1 // Double +``` + +你可以按需要使用其它数字类型: + +```scala +val a: Byte = 0 // Byte = 0 +val a: Double = 0 // Double = 0.0 +val a: Float = 0 // Float = 0.0 +val a: Int = 0 // Int = 0 +val a: Long = 0 // Long = 0 +val a: Short = 0 // Short = 0 + +val x = BigInt(1_234_456_789) +val y = BigDecimal(1_234_456.890) +``` + +### 布尔值 + +两个语言都在布尔值中用 `true` 和 `false`。 + + + + + + + + + + +
+ let a = true; +
let b = false;
+
+ val a = true +
val b = false
+
+ +## 日期 + +日期是两种语言中另一种常用的类型。 + +### 获取当前日期: + + + + + + + + + + +
+ let d = new Date();
+
// result: +
// Sun Nov 29 2020 18:47:57 GMT-0700 (Mountain Standard Time) +
+
+ // different ways to get the current date and time +
import java.time.* +
+
val a = LocalDate.now +
    // 2020-11-29 +
val b = LocalTime.now +
    // 18:46:38.563737 +
val c = LocalDateTime.now +
    // 2020-11-29T18:46:38.563750 +
val d = Instant.now +
    // 2020-11-30T01:46:38.563759Z
+
+ +### 指定不同的日期: + + + + + + + + + + +
+ let d = Date(2020, 1, 21, 1, 0, 0, 0); +
let d = Date(2020, 1, 21, 1, 0, 0); +
let d = Date(2020, 1, 21, 1, 0); +
let d = Date(2020, 1, 21, 1); +
let d = Date(2020, 1, 21);
+
+ val d = LocalDate.of(2020, 1, 21) +
val d = LocalDate.of(2020, Month.JANUARY, 21) +
val d = LocalDate.of(2020, 1, 1).plusDays(20) +
+
+ +在这种情况下,Scala 使用 Java 附带的日期和时间类。 +JavaScript 和 Scala 之间的许多日期/时间方法是相似的。 +有关详细信息,请参阅 [_java.time_ 包](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/package-summary.html)。 + +## 函数 + +在 JavaScript 和 Scala 中,函数都是对象,因此它们的功能相似,但它们的语法和术语略有不同。 + +### 命名函数,一行: + + + + + + + + + + +
+ function add(a, b) { +
  return a + b; +
} +
add(2, 2);   // 4
+
+ // technically this is a method, not a function +
def add(a: Int, b: Int) = a + b +
add(2, 2)   // 4
+
+ +### 命名函数,多行: + + + + + + + + + + +
+ function addAndDouble(a, b) { +
  // imagine this requires +
  // multiple lines +
  return (a + b) * 2 +
}
+
+ def addAndDouble(a: Int, b: Int): Int = +
  // imagine this requires +
  // multiple lines +
  (a + b) * 2
+
+ +在 Scala 中,显示 `Int` 返回类型是可选的。 +它_不_显示在 `add` 示例中,而_是_显示在 `addAndDouble` 示例中,因此您可以看到这两种方法。 + +## 匿名函数 + +JavaScript 和 Scala 都允许您定义匿名函数,您可以将其传递给其他函数和方法。 + +### 箭头和匿名函数 + + + + + + + + + + +
+ // arrow function +
let log = (s) => console.log(s) +
+
// anonymous function +
let log = function(s) { +
  console.log(s); +
} +
+
// use either of those functions here +
function printA(a, log) { +
  log(a); +
}
+
+ // a function (an anonymous function assigned to a variable) +
val log = (s: String) => console.log(s) +
+
// a scala method. methods tend to be used much more often, +
// probably because they’re easier to read. +
def log(a: Any) = console.log(a) +
+
// a function or a method can be passed into another +
// function or method +
def printA(a: Any, f: log: Any => Unit) = log(a) +
+
+ +在 Scala 中,您很少使用所示的第一种语法来定义函数。 +相反,您经常在使用点定义匿名函数。 +许多集合方法是 [高阶函数][hofs]并接受函数参数,因此您编写如下代码: + +```scala +// map method, long form +List(1,2,3).map(i => i * 10) // List(10,20,30) + +// map, short form (which is more commonly used) +List(1,2,3).map(_ * 10) // List(10,20,30) + +// filter, short form +List(1,2,3).filter(_ < 3) // List(1,2) + +// filter and then map +List(1,2,3,4,5).filter(_ < 3).map(_ * 10) // List(10, 20) +``` + +## 类 + +Scala 既有类也有样例类。 +_类_ 与 JavaScript 类相似,通常用于 [OOP 风格应用程序][modeling-oop](尽管它们也可以在 FP 代码中使用),并且 _样例类_有附加的特性,这让它在 [FP 风格应用][modeling-fp]中很有用。 + +下面的例子展示了如何创建几个类型作为枚举,然后定义一个 OOP 风格的 `Pizza` 类。 +最后,创建并使用了一个 `Pizza` 实例: + +```scala +// create some enumerations that the Pizza class will use +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions + +// import those enumerations and the ArrayBuffer, +// so the Pizza class can use them +import CrustSize.* +import CrustType.* +import Topping.* +import scala.collection.mutable.ArrayBuffer + +// define an OOP style Pizza class +class Pizza( + var crustSize: CrustSize, + var crustType: CrustType +): + + private val toppings = ArrayBuffer[Topping]() + + def addTopping(t: Topping): Unit = + toppings += t + + def removeTopping(t: Topping): Unit = + toppings -= t + + def removeAllToppings(): Unit = + toppings.clear() + + override def toString(): String = + s""" + |Pizza: + | Crust Size: ${crustSize} + | Crust Type: ${crustType} + | Toppings: ${toppings} + """.stripMargin + +end Pizza + +// create a Pizza instance +val p = Pizza(Small, Thin) + +// change the crust +p.crustSize = Large +p.crustType = Thick + +// add and remove toppings +p.addTopping(Cheese) +p.addTopping(Pepperoni) +p.addTopping(BlackOlives) +p.removeTopping(Pepperoni) + +// print the pizza, which uses its `toString` method +println(p) +``` + +## 接口、trait 和继承 + +Scala 使用 trait 作为接口,也可以创建混搭。 +trait 可以有抽象和具体的成员,包括方法和字段。 + +这个例子展示了如何定义两个 traits,创建一个扩展和实现这些 traits 的类,然后创建和使用该类的一个实例: + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") + +trait HasTail: + def wagTail(): Unit + def stopTail(): Unit + +class Dog(var name: String) extends HasLegs, HasTail: + val numLegs = 4 + def walk() = println("I’m walking") + def wagTail() = println("⎞⎜⎛ ⎞⎜⎛") + def stopTail() = println("Tail is stopped") + override def toString = s"$name is a Dog" + +// create a Dog instance +val d = Dog("Rover") + +// use the class’s attributes and behaviors +println(d.numLegs) // 4 +d.wagTail() // "⎞⎜⎛ ⎞⎜⎛" +d.walk() // "I’m walking" +``` + +## 控制结构 + +除了在 JavaScript 中使用 `===` 和 `!==` 之外,比较和逻辑运算符在 JavaScript 和 Scala 中几乎相同。 + +{% comment %} +TODO: Sébastien mentioned that `===` is closest to `eql` in Scala. Update this area. +{% endcomment %} + +### 比较运算符 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JavaScriptScala
+ == + ==
+ === + ==
+ != + !=
+ !== + !=
+ > + >
+ < + <
+ >= + >=
+ <= + <=
+ +### 逻辑运算符 + + + + + + + + + + + + +
JavaScriptScala
+ && +
|| +
!
+
+ && +
|| +
!
+
+ +## if/then/else 表达式 + +JavaScript和 Scala if/then/else 语句相似。 +在 Scala 2 中它们几乎相同,但在 Scala 3 中,花括号不再是必需的(尽管它们仍然可以使用)。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if (x == 1) { console.log(1); } +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if (x == 1) { +
  console.log("x is 1, as you can see:") +
  console.log(x) +
}
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if (x < 0) { +
  console.log("negative") +
} else if (x == 0) { +
  console.log("zero") +
} else { +
  console.log("positive") +
}
+
+ if x < 0 then +
  println("negative") +
else if x == 0 +
  println("zero") +
else +
  println("positive")
+
+ +### 从 `if` 返回值: + +JavaScript 使用三元运算符,Scala 像往常一样使用它的 `if` 表达式: + + + + + + + + + + +
+ let minVal = a < b ? a : b; +
+ val minValue = if a < b then a else b +
+ +### `if` 作为方法体: + +Scala 方法往往很短,您可以轻松地使用 `if` 作为方法体: + + + + + + + + + + +
+ function min(a, b) { +
  return (a < b) ? a : b; +
}
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +在 Scala 3 中,如果您愿意,您仍然可以使用“花括号”样式。 +例如,您可以像这样编写 if/else-if/else 表达式: + +```scala +if (i == 0) { + println(0) +} else if (i == 1) { + println(1) +} else { + println("other") +} +``` + +## 循环 + +JavaScript 和 Scala 都有 `while` 循环和 `for` 循环。 +Scala 曾经有 do/while 循环,但它们已从语言中删除。 + +### `while` 循环: + + + + + + + + + + +
+ let i = 0; +
while (i < 3) { +
  console.log(i); +
  i++; +
}
+
+ var i = 0; +
while i < 3 do +
  println(i) +
  i += 1
+
+ +如果你愿意,Scala 代码也可以写成这样: + +```scala +var i = 0 +while (i < 3) { + println(i) + i += 1 +} +``` + +以下示例展示了 JavaScript 和 Scala 中的“for”循环。 +他们假设您可以使用这些集合: + +```scala +// JavaScript +let nums = [1, 2, 3]; + +// Scala +val nums = List(1, 2, 3) +``` + +### `for` 循环,单行: + + + + + + + + + + +
+ // newer syntax +
for (let i of nums) { +
  console.log(i); +
} +
+
// older +
for (i=0; i<nums.length; i++) { +
  console.log(nums[i]); +
}
+
+ // preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +### `for` 循环,在循环体内多行 + + + + + + + + + + +
+ // preferred +
for (let i of nums) { +
  let j = i * 2; +
  console.log(j); +
} +
+
// also available +
for (i=0; i<nums.length; i++) { +
  let j = nums[i] * 2; +
  console.log(j); +
}
+
+ // preferred +
for i <- ints do +
  val i = i * 2 +
  println(j) +
+
// also available +
for (i <- nums) { +
  val j = i * 2 +
  println(j) +
}
+
+ +### 在 `for` 循环中有多个生成器 + + + + + + + + + + +
+ let str = "ab"; +
for (let i = 1; i < 3; i++) { +
  for (var j = 0; j < str.length; j++) { +
    for (let k = 1; k < 11; k++) { +
      let c = str.charAt(j); +
      console.log(`i: ${i} j: ${c} k: ${k}`); +
    } +
  } +
}
+
+ for +
  i <- 1 to 2 +
  j <- 'a' to 'b' +
  k <- 1 to 10 by 5 +
do +
  println(s"i: $i, j: $j, k: $k")
+
+ +### 带守卫的生成器 + +_守卫_是 `for` 表达式中的 `if` 表达式的名称。 + + + + + + + + + + +
+ for (let i = 0; i < 10; i++) { +
  if (i % 2 == 0 && i < 5) { +
    console.log(i); +
  } +
}
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### `for` comprehension + +`for` comprehension 是一个 `for` 循环,它使用 `yield` 返回(产生)一个值。 它们经常在 Scala 中使用。 + + + + + + + + + + +
+ N/A +
+ val list = +
  for +
    i <- 1 to 3 +
  yield +
    i * 10 +
// result: Vector(10, 20, 30)
+
+ +## switch & match + +JavaScript 有 `switch` 语句,Scala 有 `match` 表达式。 +就像 Scala 中的所有其他东西一样,这些确实是_表达式_,这意味着它们返回一个结果: + +```scala +val day = 1 + +// later in the code ... +val monthAsString = day match + case 1 => "January" + case 2 => "February" + case _ => "Other" +``` + +`match` 表达式可以在每个 `case` 语句中处理多个匹配项: + +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` + +它们也可以用于方法体: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true + +def isPerson(x: Matchable): Boolean = x match + case p: Person => true + case _ => false +``` + +`match` 表达式有许多其他模式匹配选项。 + +## 集合类 + +Scala 有不同的 [集合类][collections-classes] 来满足不同的需求。 + +常见的_不可变_序列是: + +- `List` +- `Vector` + +常见的_可变_序列是: + +- `Array` +- `ArrayBuffer` + +Scala 还有可变和不可变的 Map 和 Set。 + +这是创建常见 Scala 集合类型的方式: + +```scala +val strings = List("a", "b", "c") +val strings = Vector("a", "b", "c") +val strings = ArrayBuffer("a", "b", "c") + +val set = Set("a", "b", "a") // result: Set("a", "b") +val map = Map( + "a" -> 1, + "b" -> 2, + "c" -> 3 +) +``` + +### 集合上的方法 + +以下示例展示了使用 Scala 集合的许多不同方法。 + +### 填充列表: + +```scala +// to, until +(1 to 5).toList // List(1, 2, 3, 4, 5) +(1 until 5).toList // List(1, 2, 3, 4) + +(1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +(1 until 10 by 2).toList // List(1, 3, 5, 7, 9) +(1 to 10).by(2).toList // List(1, 3, 5, 7, 9) + +('d' to 'h').toList // List(d, e, f, g, h) +('d' until 'h').toList // List(d, e, f, g) +('a' to 'f').by(2).toList // List(a, c, e) + +// range method +List.range(1, 3) // List(1, 2) +List.range(1, 6, 2) // List(1, 3, 5) + +List.fill(3)("foo") // List(foo, foo, foo) +List.tabulate(3)(n => n * n) // List(0, 1, 4) +List.tabulate(4)(n => n * n) // List(0, 1, 4, 9) +``` + +### 序列上的函数式方法: + +```scala +// these examples use a List, but they’re the same with Vector +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.contains(20) // true +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.filter(_ > 100) // List() +a.find(_ > 20) // Some(30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +a.takeWhile(_ < 30) // List(10, 20) + +// map, flatMap +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(10, 5, 8, 1, 7) +nums.sorted // List(1, 5, 7, 8, 10) +nums.sortWith(_ < _) // List(1, 5, 7, 8, 10) +nums.sortWith(_ > _) // List(10, 8, 7, 5, 1) + +List(1,2,3).updated(0,10) // List(10, 2, 3) +List(2,4).union(List(1,3)) // List(2, 4, 1, 3) + +// zip +val women = List("Wilma", "Betty") // List(Wilma, Betty) +val men = List("Fred", "Barney") // List(Fred, Barney) +val couples = women.zip(men) // List((Wilma,Fred), (Betty,Barney)) +``` + +Scala 有_很多_更多可供您使用的方法。 +所有这些方法的好处是: + +- 您不必编写自定义的 `for` 循环来解决问题 +- 当你阅读别人的代码时,你不必阅读他们自定义的 `for` 循环; 你只会找到像这样的常用方法,因此更容易阅读来自不同项目的代码 + +### 元组 + +当您想将多个数据类型放在同一个列表中时,JavaScript 允许您这样做: + +```javascript +stuff = ["Joe", 42, 1.0]; +``` + +在 Scala 中你这样做: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + +在 Scala 中,这些类型称为元组,如图所示,它们可以包含一个或多个元素,并且元素可以具有不同的类型。 +访问它们的元素就像访问 `List`、`Vector` 或 `Array` 的元素一样: + +```scala +d(0) // "eleven" +d(1) // 11 +``` + +### 枚举 + +JavaScript 没有枚举,但你可以这样做: + +```javascript +let Color = { + RED: 1, + GREEN: 2, + BLUE: 3 +}; +Object.freeze(Color); +``` + +在 Scala 3 中,您可以使用枚举做很多事情。 +您可以创建该代码的等效代码: + +```scala +enum Color: + case Red, Green, Blue +``` + +你可以创建带参数的枚举: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +你也可以创建用户自定义的枚举成员: + +```scala +enum Planet(mass: Double, radius: Double): + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24,6.0518e6) + case Earth extends Planet(5.976e+24,6.37814e6) + // more planets here ... + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity +``` + +## Scala.js DOM 代码 + +Scala.js 允许您编写 Scala 代码,这些代码编译为 JavaScript 代码,然后可以在浏览器中使用。 +该方法类似于 TypeScript、ReScript 和其他编译为 JavaScript 的语言。 + +包含必要的库并在项目中导入必要的包后,编写 Scala.js 代码看起来与编写 JavaScript 代码非常相似: + +```scala +// show an alert dialog on a button click +jQuery("#hello-button").click{() => + dom.window.alert("Hello, world") +} + +// define a button and what should happen when it’s clicked +val btn = button( + "Click me", + onclick := { () => + dom.window.alert("Hello, world") + }) + +// create two divs with css classes, an h2 element, and the button +val content = + div(cls := "foo", + div(cls := "bar", + h2("Hello"), + btn + ) + ) + +// add the content to the DOM +val root = dom.document.getElementById("root") +root.innerHTML = "" +root.appendChild(content.render) +``` + +请注意,尽管 Scala 是一种类型安全的语言,但在上面的代码中没有声明任何类型。 +Scala 强大的类型推断能力通常使 Scala 代码看起来像是动态类型的。 +但它是类型安全的,因此您可以在开发周期的早期捕获许多类错误。 + +## 其他 Scala.js 资源 + +Scala.js 网站为对使用 Scala.js 感兴趣的 JavaScript 开发人员提供了极好的教程集。 +以下是他们的一些初始教程: + +- [基础教程(创建第一个 Scala.js 项目)](https://www.scala-js.org/doc/tutorial/basic/) +- [适用于 JavaScript 开发人员的 Scala.js](https://www.scala-js.org/doc/sjs-for-js/) +- [从 ES6 到 Scala:基础](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part1.html) +- [从 ES6 到 Scala:集合](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part2.html) +- [从 ES6 到 Scala:高级](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part3.html) + +## Scala 独有的概念 + +Scala 中还有其他一些概念目前在 JavaScript 中没有等效的概念: + +- 几乎所有与[上下文抽象][contextual]相关的东西 +- 方法特性: + - 多个参数列表 + - 调用方法时使用命名参数 +- 使用 trait 作为接口 +- 样例类 +- 伴生类和对象 +- 创建自己的[控制结构][control]和 DSL 的能力 +- `match` 表达式和模式匹配的高级功能 +- `for` comprehension +- 中缀方法 +- 宏和元编程 +- 更多的 ... + + +[collections-classes]: {% link _zh-cn/overviews/scala3-book/collections-classes.md %} +[concurrency]: {% link _zh-cn/overviews/scala3-book/concurrency.md %} +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control]: {% link _zh-cn/overviews/scala3-book/control-structures.md %} +[extension-methods]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[fp-intro]: {% link _zh-cn/overviews/scala3-book/fp-intro.md %} +[givens]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %} +[hofs]: {% link _zh-cn/overviews/scala3-book/fun-hofs.md %} +[intersection-types]: {% link _zh-cn/overviews/scala3-book/types-intersection.md %} +[modeling-fp]: {% link _zh-cn/overviews/scala3-book/domain-modeling-fp.md %} +[modeling-oop]: {% link _zh-cn/overviews/scala3-book/domain-modeling-oop.md %} +[multiversal]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[union-types]: {% link _zh-cn/overviews/scala3-book/types-union.md %} + +
diff --git a/_zh-cn/overviews/scala3-book/scala-for-python-devs.md b/_zh-cn/overviews/scala3-book/scala-for-python-devs.md new file mode 100644 index 0000000000..618f8de44a --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-python-devs.md @@ -0,0 +1,1361 @@ +--- +title: Scala for Python Developers +type: chapter +description: This page is for Python developers who are interested in learning about Scala 3. +language: zh-cn +num: 75 +previous-page: scala-for-javascript-devs +next-page: where-next + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +{% include_relative scala4x.css %} + +
+ +{% comment %} + +NOTE: Hopefully someone with more Python experience can give this a thorough review. + +NOTE: On this page (https://contributors.scala-lang.org/t/feedback-sought-optional-braces/4702/10), Li Haoyi comments: “Python’s success also speaks for itself; beginners certainly don’t pick Python because of performance, ease of installation, packaging, IDE support, or simplicity of the language’s runtime semantics!” I’m not a Python expert, so these points are good to know, though I don’t want to go negative in any comparisons. +It’s more like thinking, “Python developers will appreciate Scala’s performance, ease of installation, packaging, IDE support, etc.” +{% endcomment %} + +{% comment %} +TODO: We should probably go through this document and add links to our other detail pages, when time permits. +{% endcomment %} + +本节提供了 Python 和 Scala 编程语言之间的比较。 +它适用于懂得 Python 并希望了解 Scala 的程序员,特别是通过查看 Python 语言特性与 Scala 比较的示例。 + +## 介绍 + +在进入示例之前,第一部分提供了以下部分的相对简短的介绍和总结。 +这两种语言首先在高层次上进行比较,然后在日常编程层次上进行比较。 + +### 高层次相似性 + +在高层次上,Scala 与 Python 有这些*相似之处*: + +- 两者都是高级编程语言,您不必关心指针和手动内存管理等低级概念 +- 两者都有一个相对简单、简洁的语法 +- 两者都支持[函数式编程][fp-intro] +- 两者都是面向对象的编程 (OOP) 语言 +- 两者都有推导:Python 有列表推导,Scala 有 `for` 推导 +- 两种语言都支持 lambdas 和 [高阶函数][hofs] +- 两者都可以与 [Apache Spark](https://spark.apache.org) 一起用于大数据处理 +- 两者都有很多很棒的库 + +### 高层次差异 + +同样在高层次上,Python 和 Scala 之间的_差异_是: + +- Python 是动态类型的,Scala 是静态类型的 + - 虽然它是静态类型的,但 Scala 的类型推断等特性让它感觉像是一门动态语言 +- Python 被解释,Scala 代码被编译成 _.class_ 文件,并在 Java 虚拟机 (JVM) 上运行 +- 除了在 JVM 上运行之外,[Scala.js](https://www.scala-js.org) 项目允许您使用 Scala 作为 JavaScript 替代品 +- [Scala Native](https://scala-native.org/) 项目可让您编写“系统”级代码,并编译为本机可执行文件 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- Scala 习惯默认不变性:鼓励您使用不可变变量和不可变集合 +- Scala 对[并发和并行编程][concurrency]有很好的支持 + +### 编程层次相似性 + +本节介绍您在日常编写代码时会看到 Python 和 Scala 之间的相似之处: + +- Scala 的类型推断常常让人感觉像是一种动态类型语言 +- 两种语言都不使用分号来结束表达式 +- 两种语言都支持使用重要的缩进而不是大括号和圆括号 +- 定义方法的语法类似 +- 两者都有列表、字典(映射)、集合和元组 +- 两者都有映射和过滤的推导 +- 使用 Scala 3 的[顶级定义][toplevel],您可以将方法、字段和其他定义放在任何地方 + - 一个区别是 Python 甚至可以在不声明单个方法的情况下运行,而 Scala 3 不能在顶层做_所有事_;例如,启动 Scala 应用程序需要一个 [main 方法][main-method] (`@main def`) + +### 编程层次差异 + +同样在编程级别,这些是您在编写代码时每天都会看到的一些差异: + +- 在 Scala 中编程感觉非常一致: + - `val` 和 `var` 字段用于定义字段和参数 + - 列表、映射、集合和元组都以类似方式创建和访问;例如,括号用于创建所有类型---`List(1,2,3)`, `Set(1,2,3)`, `Map(1->"one")`---就像创建任何其他 Scala 类 + - [集合类][collections-classes] 通常具有大部分相同的高阶函数 + - 模式匹配在整个语言中一致使用 + - 用于定义传递给方法的函数的语法与用于定义匿名函数的语法相同 +- Scala 变量和参数使用 `val`(不可变)或 `var`(可变)关键字定义 +- Scala 习惯用法更喜欢不可变的数据结构 +- Scala 通过 IntelliJ IDEA 和 Microsoft VS Code 提供了极好的 IDE 支持 +- 注释:Python 使用 `#` 表示注释; Scala 使用 C、C++ 和 Java 样式:`//`、`/*...*/` 和 `/**...*/` +- 命名约定:Python 标准是使用下划线,例如 `my_list`; Scala 使用 `myList` +- Scala 是静态类型的,因此您可以声明方法参数、方法返回值和其他地方的类型 +- 模式匹配和 `match` 表达式在 Scala 中被广泛使用(并且会改变你编写代码的方式) +- Scala 中大量使用 trait;接口和抽象类在 Python 中使用较少 +- Scala 的 [上下文抽象][contextual] 和 _术语推导_提供了一系列不同的特性: + - [扩展方法][extension-method]让您使用清晰的语法轻松地向类添加新功能 + - [多元等式][multiversal] 让您限制相等比较---在编译时——只有那些有意义的比较 +- Scala 拥有最先进的开源函数式编程库(参见 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)) +- 借助对象、按名称参数、中缀表示法、可选括号、扩展方法、高阶函数等功能,您可以创建自己的“控制结构”和 DSL +- Scala 代码可以在 JVM 中运行,甚至可以编译为原生代码(使用 [Scala Native](https://github.com/scala-native/scala-native) 和 [GraalVM](https://www.graalvm) .org)) 实现高性能 +- 许多其他好东西:样例类、伴生类和对象、宏、[联合][union-types] 和 [交集][intersection-types] 类型、[顶级定义][toplevel]、数字字面量、多参数列表和更多的 + +### 特性比较示例 + +鉴于该介绍,以下部分提供了 Python 和 Scala 编程语言功能的并排比较。 + +{% comment %} +TODO: Update the Python examples to use four spaces. I started to do this, but then thought it would be better to do that in a separate PR. +{% endcomment %} + +## 注释 + +Python 使用 `#` 表示注释,而 Scala 的注释语法与 C、C++ 和 Java 等语言相同: + + + + + + + + + + +
+ # a comment +
+ // a comment +
/* ... */ +
/** ... */
+
+ +## 变量赋值 + +这些例子演示了如何在 Python 和 Scala 中创建变量。 + +### 创建整数和字符串变量: + + + + + + + + + + +
+ x = 1 +
x = "Hi" +
y = """foo +
       bar +
       baz"""
+
+ val x = 1 +
val x = "Hi" +
val y = """foo +
           bar +
           baz"""
+
+ +### 列表: + + + + + + + + + + +
+ x = [1,2,3] +
+ val x = List(1,2,3) +
+ +### 字典/映射: + + + + + + + + + + +
+ x = { +
  "Toy Story": 8.3, +
  "Forrest Gump": 8.8, +
  "Cloud Atlas": 7.4 +
}
+
+ val x = Map( +
  "Toy Story" -> 8.3, +
  "Forrest Gump" -> 8.8, +
  "Cloud Atlas" -> 7.4 +
)
+
+ +### 集合: + + + + + + + + + + +
+ x = {1,2,3} +
+ val x = Set(1,2,3) +
+ +### 元组: + + + + + + + + + + +
+ x = (11, "Eleven") +
+ val x = (11, "Eleven") +
+ +如果 Scala 字段是可变的,请使用 `var` 而不是 `val` 来定义变量: + +```scala +var x = 1 +x += 1 +``` + +然而,Scala 中的经验法则是始终使用 `val`,除非变量确实需要被改变。 + +## OOP 风格的类和方法 + +本节比较了与 OOP 风格的类和方法相关的特性。 + +### OOP 风格类,主构造函数: + + + + + + + + + + +
+ class Person(object): +
  def __init__(self, name): +
    self.name = name +
+
  def speak(self): +
    print(f'Hello, my name is {self.name}')
+
+ class Person (var name: String): +
  def speak() = println(s"Hello, my name is $name")
+
+ +### 创建和使用实例: + + + + + + + + + + +
+ p = Person("John") +
p.name   # John +
p.name = 'Fred' +
p.name   # Fred +
p.speak()
+
+ val p = Person("John") +
p.name   // John +
p.name = "Fred" +
p.name   // Fred +
p.speak()
+
+ +### 单行方法: + + + + + + + + + + +
+ def add(a, b): return a + b +
+ def add(a: Int, b: Int): Int = a + b +
+ +### 多行方法: + + + + + + + + + + +
+ def walkThenRun(): +
  print('walk') +
  print('run')
+
+ def walkThenRun() = +
  println("walk") +
  println("run")
+
+ +## 接口,traits,和继承 + +如果您熟悉 Java 8 和更新版本,Scala trait 类似于那些 Java 接口。 +Traits 在 Scala 中一直使用,而 Python 接口和抽象类的使用频率要低得多。 +因此,与其试图比较两者,不如用这个例子来展示如何使用 Scala trait来构建一个模拟数学问题的小解决方案: + +```scala +trait Adder: + def add(a: Int, b: Int) = a + b + +trait Multiplier: + def multiply(a: Int, b: Int) = a * b + +// create a class from the traits +class SimpleMath extends Adder, Multiplier +val sm = new SimpleMath +sm.add(1,1) // 2 +sm.multiply(2,2) // 4 +``` + +还有[许多其他方法可以将 trait 与类和对象一起使用][modeling-intro],但这让您了解如何使用它们将概念组织成行为的逻辑组,然后根据需要将它们合并以创建一个完整的解决方案。 + +## 控制结构 + +本节比较 Python 和 Scala 中的[控制结构][control-structures]。 +两种语言都有类似 `if`/`else`、`while`、`for` 循环和 `try` 的结构。 +Scala 也有 `match` 表达式。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if x == 1: print(x) +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if x == 1: +
  print("x is 1, as you can see:") +
  print(x)
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if x < 0: +
  print("negative") +
elif x == 0: +
  print("zero") +
else: +
  print("positive")
+
+ if x < 0 then +
  println("negative") +
else if x == 0 then +
  println("zero") +
else +
  println("positive")
+
+ +### 从 `if` 返回值: + + + + + + + + + + +
+ min_val = a if a < b else b +
+ val minValue = if a < b then a else b +
+ +### `if` 作为方法体: + + + + + + + + + + +
+ def min(a, b): +
  return a if a < b else b
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +### `while` 循环: + + + + + + + + + + +
+ i = 1 +
while i < 3: +
  print(i) +
  i += 1
+
+ var i = 1 +
while i < 3 do +
  println(i) +
  i += 1
+
+ +### 有范围的 `for` 循环: + + + + + + + + + + +
+ for i in range(0,3): +
  print(i)
+
+ // preferred +
for i <- 0 until 3 do println(i) +
+
// also available +
for (i <- 0 until 3) println(i) +
+
// multiline syntax +
for +
  i <- 0 until 3 +
do +
  println(i)
+
+ +### 列表的 `for` 循环: + + + + + + + + + + +
+ for i in ints: print(i) +
+
for i in ints: +
  print(i)
+
+ for i <- ints do println(i) +
+ +### `for` 循环,多行: + + + + + + + + + + +
+ for i in ints: +
  x = i * 2 +
  print(f"i = {i}, x = {x}")
+
+ for +
  i <- ints +
do +
  val x = i * 2 +
  println(s"i = $i, x = $x")
+
+ +### 多“范围”生成器: + + + + + + + + + + +
+ for i in range(1,3): +
  for j in range(4,6): +
    for k in range(1,10,3): +
      print(f"i = {i}, j = {j}, k = {k}")
+
+ for +
  i <- 1 to 2 +
  j <- 4 to 5 +
  k <- 1 until 10 by 3 +
do +
  println(s"i = $i, j = $j, k = $k")
+
+ +### 带守卫(`if` 表达式)的生成器: + + + + + + + + + + +
+ for i in range(1,11): +
  if i % 2 == 0: +
    if i < 5: +
      print(i)
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### 每行有多个 `if` 条件: + + + + + + + + + + +
+ for i in range(1,11): +
  if i % 2 == 0 and i < 5: +
    print(i)
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 && i < 5 +
do +
  println(i)
+
+ +### 推导: + + + + + + + + + + +
+ xs = [i * 10 for i in range(1, 4)] +
# xs: [10,20,30]
+
+ val xs = for i <- 1 to 3 yield i * 10 +
// xs: Vector(10, 20, 30)
+
+ +### `match` 表达式: + + + + + + + + + + +
+ # From 3.10, Python supports structural pattern matching +
# You can also use dictionaries for basic “switch” functionality +
match month: +
  case 1: +
    monthAsString = "January" +
  case 2: +
    monthAsString = "February" +
  case _: +
    monthAsString = "Other"
+
+ val monthAsString = month match +
  case 1 => "January" +
  case 2 => "February" +
  _ => "Other"
+
+ +### switch/match: + + + + + + + + + + +
+ # Only from Python 3.10 +
match i: +
  case 1 | 3 | 5 | 7 | 9: +
    numAsString = "odd" +
  case 2 | 4 | 6 | 8 | 10: +
    numAsString = "even" +
  case _: +
    numAsString = "too big"
+
+ val numAsString = i match +
  case 1 | 3 | 5 | 7 | 9 => "odd" +
  case 2 | 4 | 6 | 8 | 10 => "even" +
  case _ => "too big"
+
+ +### try, catch, finally: + + + + + + + + + + +
+ try: +
  print(a) +
except NameError: +
  print("NameError") +
except: +
  print("Other") +
finally: +
  print("Finally")
+
+ try +
  writeTextToFile(text) +
catch +
  case ioe: IOException => +
    println(ioe.getMessage) +
  case fnf: FileNotFoundException => +
    println(fnf.getMessage) +
finally +
  println("Finally")
+
+ +匹配表达式和模式匹配是 Scala 编程体验的重要组成部分,但这里只展示了几个 `match` 表达式功能。 有关更多示例,请参见 [控制结构][control-structures] 页面。 + +## 集合类 + +本节比较 Python 和 Scala 中可用的 [集合类][collections-classes],包括列表、字典/映射、集合和元组。 + +### 列表 + +Python 有它的列表,Scala 有几个不同的专门的可变和不可变序列类,具体取决于您的需要。 +因为 Python 列表是可变的,所以它最直接地与 Scala 的 `ArrayBuffer` 进行比较。 + +### Python 列表 & Scala序列: +Match expressions and pattern matching are a big part of the Scala programming experience, but only a few `match` expression features are shown here. See the [Control Structures][control-structures] page for many more examples. + +## Collections classes + +This section compares the [collections classes][collections-classes] that are available in Python and Scala, including lists, dictionaries/maps, sets, and tuples. + +### Lists + +Where Python has its list, Scala has several different specialized mutable and immutable sequence classes, depending on your needs. +Because the Python list is mutable, it most directly compares to Scala’s `ArrayBuffer`. + +### Python list & Scala sequences: + + + + + + + + + + +
+ a = [1,2,3] +
+ // use different sequence classes +
// as needed +
val a = List(1,2,3) +
val a = Vector(1,2,3) +
val a = ArrayBuffer(1,2,3)
+
+ +### 获取列表元素: + + + + + + + + + +
+ a[0]
a[1]
+
+ a(0)
a(1)
// just like all other method calls +
+ +### 更新列表元素: + + + + + + + + + + +
+ a[0] = 10 +
a[1] = 20
+
+ // ArrayBuffer is mutable +
a(0) = 10 +
a(1) = 20
+
+ +### 合并两个列表: + + + + + + + + + + +
+ c = a + b +
+ val c = a ++ b +
+ +### 遍历列表: + + + + + + + + + + +
+ for i in ints: print(i) +
+
for i in ints: +
  print(i)
+
+ // preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +Scala 的主要序列类是 `List`、`Vector` 和 `ArrayBuffer`。 +`List` 和 `Vector` 是当你想要一个不可变序列时使用的主要类,而 `ArrayBuffer` 是当你想要一个可变序列时使用的主要类。 +(Scala 中的“缓冲区”是一个可以增长和缩小的序列。) + +### 字典/映射 + +Python 字典就像_可变的_ Scala `Map` 类。 +但是,默认的 Scala 映射是_不可变的_,并且有许多转换方法可以让您轻松创建新映射。 + +#### 字典/映射 创建: + + + + + + + + + + +
+ my_dict = { +
  'a': 1, +
  'b': 2, +
  'c': 3 +
}
+
+ val myMap = Map( +
  "a" -> 1, +
  "b" -> 2, +
  "c" -> 3 +
)
+
+ +#### 获取字典/映射元素: + + + + + + + + + + +
+ my_dict['a']   # 1 +
+ myMap("a")   // 1 +
+ +#### 带 `for` 循环的字典/映射: + + + + + + + + + + +
+ for key, value in my_dict.items(): +
  print(key) +
  print(value)
+
+ for (key,value) <- myMap do +
  println(key) +
  println(value)
+
+ +Scala 有其他专门的 `Map` 类来满足不同的需求。 + +### 集合 + +Python 集合类似于_可变的_ Scala `Set` 类。 + +#### 集合创建: + + + + + + + + + + +
+ set = {"a", "b", "c"} +
+ val set = Set(1,2,3) +
+ +#### 重复元素: + + + + + + + + + + +
+ set = {1,2,1} +
# set: {1,2}
+
+ val set = Set(1,2,1) +
// set: Set(1,2)
+
+ +Scala 有其他专门的 `Set` 类来满足不同的需求。 + +### 元组 + +Python 和 Scala 元组也很相似。 + +#### 元组创建: + + + + + + + + + + +
+ t = (11, 11.0, "Eleven") +
+ val t = (11, 11.0, "Eleven") +
+ +#### 获取元组元素: + + + + + + + + + + +
+ t[0]   # 11 +
t[1]   # 11.0
+
+ t(0)   // 11 +
t(1)   // 11.0
+
+ +## 集合类上的方法 + +Python 和 Scala 有几个相同的常用函数方法可供它们使用: + +- `map` +- `filter` +- `reduce` + +如果您习惯于在 Python 中将这些方法与 lambda 表达式一起使用,您会发现 Scala 在其集合类中使用了类似的方法。 +为了演示此功能,这里有两个示例列表: + +```scala +numbers = (1,2,3) // python +val numbers = List(1,2,3) // scala +``` + +下表中使用了这些列表,显示了如何对其应用映射和过滤算法。 + +### 映射与推导: + + + + + + + + + + +
+ x = [i * 10 for i in numbers] +
+ val x = for i <- numbers yield i * 10 +
+ +### 有推导的过滤: + + + + + + + + + + +
+ evens = [i for i in numbers if i % 2 == 0] +
+ val evens = numbers.filter(_ % 2 == 0) +
// or +
val evens = for i <- numbers if i % 2 == 0 yield i
+
+ +### 映射 & 有推导的过滤: + + + + + + + + + + +
+ x = [i * 10 for i in numbers if i % 2 == 0] +
+ val x = numbers.filter(_ % 2 == 0).map(_ * 10) +
// or +
val x = for i <- numbers if i % 2 == 0 yield i * 10
+
+ +### 映射: + + + + + + + + + + +
+ x = map(lambda x: x * 10, numbers) +
+ val x = numbers.map(_ * 10) +
+ +### 过滤: + + + + + + + + + + +
+ f = lambda x: x > 1 +
x = filter(f, numbers)
+
+ val x = numbers.filter(_ > 1) +
+ + +### Scala 集合方法: + +Scala 集合类有超过 100 种功能方法来简化您的代码。 +除了 `map`、`filter` 和 `reduce`,下面列出了其他常用的方法。 +在这些方法示例中: + +- `c` 指的是一个集合 +- `p` 是谓词 +- `f` 是一个函数、匿名函数或方法 +- `n` 指的是一个整数值 + +以下是一些可用的过滤方法: + +| 方法 | 说明 | +| -------------- | ------------- | +| `c1.diff(c2) ` | 返回 `c1` 和 `c2` 中元素的差异。 | +| `c.distinct` | 返回 `c` 中的唯一元素。 | +| `c.drop(n)` | 返回集合中除前 `n` 个元素之外的所有元素。 | +| `c.filter(p) ` | 返回集合中谓词为 `true` 的所有元素。 | +| `c.head` | 返回集合的第一个元素。 (如果集合为空,则抛出 `NoSuchElementException`。) | +| `c.tail` | 返回集合中除第一个元素之外的所有元素。 (如果集合为空,则抛出 `UnsupportedOperationException`。) | +| `c.take(n)` | 返回集合 `c` 的前 `n` 个元素。 | + +以下是一些转换方法: + +| 方法 | 说明 | +| --------------- | ------------- | +| `c.flatten` | 将集合的集合(例如列表列表)转换为单个集合(单个列表)。 | +| `c.flatMap(f)` | 通过将 `f` 应用于集合 `c` 的所有元素(如 `map`)返回一个新集合,然后将结果集合的元素展平。 | +| `c.map(f)` | 通过将 `f` 应用于集合 `c` 的所有元素来创建一个新集合。 | +| `c.reduce(f)` | 将 `reduction` 函数 `f` 应用于 `c` 中的连续元素以产生单个值。 | +| `c.sortWith(f)` | 返回由比较函数 `f` 排序的 `c` 版本。 | + +一些常见的分组方法: + +| 方法 | 说明 | +| ---------------- | ------------- | +| `c.groupBy(f)` | 根据 `f` 将集合划分为集合的 `Map`。 | +| `c.partition(p)` | 根据谓词 `p` 返回两个集合。 | +| `c.span(p)` | 返回两个集合的集合,第一个由 `c.takeWhile(p)` 创建,第二个由 `c.dropWhile(p)` 创建。 | +| `c.splitAt(n)` | 通过在元素 `n` 处拆分集合 `c` 来返回两个集合的集合。 | + +一些信息和数学方法: + +| 方法 | 说明 | +| -------------- | ------------- | +| `c1.containsSlice(c2)` | 如果 `c1` 包含序列 `c2`,则返回 `true`。 | +| `c.count(p)` | 计算 `c` 中元素的数量,其中 `p` 为 `true`。 | +| `c.distinct` | 返回 `c` 中的不重复的元素。 | +| `c.exists(p)` | 如果集合中任何元素的 `p` 为 `true` ,则返回 `true` 。 | +| `c.find(p)` | 返回匹配 `p` 的第一个元素。该元素以 `Option[A]` 的形式返回。 | +| `c.min` | 返回集合中的最小元素。 (可以抛出_java.lang.UnsupportedOperationException_。)| +| `c.max` | 返回集合中的最大元素。 (可以抛出_java.lang.UnsupportedOperationException_。)| +| `c slice(from, to)` | 返回从元素 `from` 开始到元素 `to` 结束的元素间隔。 | +| `c.sum` | 返回集合中所有元素的总和。 (需要为集合中的元素定义 `Ordering`。) | + +以下是一些示例,展示了这些方法如何在列表上工作: + +```scala +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.filter(_ > 100) // List() +a.find(_ > 20) // Some(30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +a.takeWhile(_ < 30) // List(10, 20) +``` + +这些方法展示了 Scala 中的一个常见模式:对象上可用的函数式方法。 +这些方法都不会改变初始列表“a”; 相反,它们都返回的数据在注释后显示。 + +还有更多可用的方法,但希望这些描述和示例能让您体验到预建集合方法的强大功能。 + +## 枚举 + +本节比较 Python 和 Scala 3 中的枚举。 + +### 创建枚举: + + + + + + + + + + +
+ from enum import Enum, auto +
class Color(Enum): +
    RED = auto() +
    GREEN = auto() +
    BLUE = auto()
+
+ enum Color: +
  case Red, Green, Blue
+
+ +### 值和比较: + + + + + + + + + + +
+ Color.RED == Color.BLUE  # False +
+ Color.Red == Color.Blue  // false +
+ +### 参数化枚举: + + + + + + + + + + +
+ N/A +
+ enum Color(val rgb: Int): +
  case Red   extends Color(0xFF0000) +
  case Green extends Color(0x00FF00) +
  case Blue  extends Color(0x0000FF)
+
+ +### 用户定义枚举成员: + + + + + + + + + + +
+ N/A +
+ enum Planet( +
    mass: Double, +
    radius: Double +
  ): +
  case Mercury extends +
    Planet(3.303e+23, 2.4397e6) +
  case Venus extends +
    Planet(4.869e+24, 6.0518e6) +
  case Earth extends +
    Planet(5.976e+24, 6.37814e6) +
  // more planets ... +
+
  // fields and methods +
  private final val G = 6.67300E-11 +
  def surfaceGravity = G * mass / +
    (radius * radius) +
  def surfaceWeight(otherMass: Double) +
    = otherMass * surfaceGravity
+
+ +## Scala 独有的概念 + +Scala 中的有些概念目前在 Python 中没有等效的功能。 +请点击以下链接了解更多详情: + + + +- 大多数与[上下文抽象][contextual]相关的概念,如[扩展方法][extension-methods]、类型类、隐式值 +- Scala 允许多参数列表,从而实现部分应用函数等特性,以及创建自己的 DSL 的能力 +- 样例类,对于函数式编程和模式匹配非常有用 +- 创建自己的控制结构和 DSL 的能力 +- 模式匹配和 `match` 表达式 +- [多重等式][multiversal]:在编译时控制哪些等式比较有意义的能力 +- 中缀方法 +- 宏和元编程 + +## Scala 和虚拟环境 + +在 Scala 中,无需显式设置 Python 虚拟环境的等价物。默认情况下,Scala 构建工具管理项目依赖项,因此用户不必考虑手动安装包。例如,使用 `sbt` 构建工具,我们在 `libraryDependencies` 设置下的 `build.sbt` 文件中指定依赖关系,然后执行 + +``` +cd myapp +sbt compile +``` + +自动解析该特定项目的所有依赖项。 下载依赖的位置很大程度上是构建工具的一个实现细节,用户不必直接与这些下载的依赖交互。 例如,如果我们删除整个 sbt 依赖项缓存,则在项目的下一次编译时,sbt 会自动重新解析并再次下载所有必需的依赖项。 + +这与 Python 不同,默认情况下,依赖项安装在系统范围或用户范围的目录中,因此要在每个项目的基础上获得隔离环境,必须创建相应的虚拟环境。 例如,使用 `venv` 模块,我们可以像这样为特定项目创建一个 + +``` +cd myapp +python3 -m venv myapp-env +source myapp-env/bin/activate +pip install -r requirements.txt +``` + +这会在项目的 `myapp/myapp-env` 目录下安装所有依赖项,并更改 shell 环境变量 `PATH` 以从 `myapp-env` 查找依赖项。 +在 Scala 中,这些手动过程都不是必需的。 + + +[collections-classes]: {% link _zh-cn/overviews/scala3-book/collections-classes.md %} +[concurrency]: {% link _zh-cn/overviews/scala3-book/concurrency.md %} +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control-structures]: {% link _zh-cn/overviews/scala3-book/control-structures.md %} +[extension-methods]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} +[fp-intro]: {% link _zh-cn/overviews/scala3-book/fp-intro.md %} +[hofs]: {% link _zh-cn/overviews/scala3-book/fun-hofs.md %} +[intersection-types]: {% link _zh-cn/overviews/scala3-book/types-intersection.md %} +[main-method]: {% link _zh-cn/overviews/scala3-book/methods-main-methods.md %} +[modeling-intro]: {% link _zh-cn/overviews/scala3-book/domain-modeling-intro.md %} +[multiversal]: {% link _zh-cn/overviews/scala3-book/ca-multiversal-equality.md %} +[toplevel]: {% link _zh-cn/overviews/scala3-book/taste-toplevel-definitions.md %} +[union-types]: {% link _zh-cn/overviews/scala3-book/types-union.md %} +
diff --git a/_zh-cn/overviews/scala3-book/scala-tools.md b/_zh-cn/overviews/scala3-book/scala-tools.md new file mode 100644 index 0000000000..822c3d428a --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-tools.md @@ -0,0 +1,20 @@ +--- +title: Scala 工具 +type: chapter +description: This chapter looks at two commonly-used Scala tools, sbt and ScalaTest. +language: zh-cn +num: 69 +previous-page: concurrency +next-page: tools-sbt + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章介绍编写和运行 Scala 程序的两种方法: + +- 通过创建Scala项目,可能包含多个文件,并定义一个程序入口点, +- 通过与练习册交互,练习册是在单个文件中定义的程序,逐行执行。 diff --git a/_zh-cn/overviews/scala3-book/scala4x.css b/_zh-cn/overviews/scala3-book/scala4x.css new file mode 100644 index 0000000000..c7d34705bb --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala4x.css @@ -0,0 +1,59 @@ + + + diff --git a/_zh-cn/overviews/scala3-book/string-interpolation.md b/_zh-cn/overviews/scala3-book/string-interpolation.md new file mode 100644 index 0000000000..372eccc5e0 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/string-interpolation.md @@ -0,0 +1,356 @@ +--- +title: 字符串插值 +type: chapter +description: This page provides more information about creating strings and using 字符串插值. +language: zh-cn +num: 18 +previous-page: first-look-at-types +next-page: control-structures +redirect_from: + - /zh-cn/overviews/core/string-interpolation.html + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +## 介绍 + +字符串插值提供了一种在字符串中使用变量的方法。 +比如: + +{% tabs example-1 %} +{% tab 'Scala 2 and 3' for=example-1 %} +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` +{% endtab %} +{% endtabs %} + +字符串插值由在字符串引号前面的 `s` 和任何有前缀 `$` 的变量组成。 + +### 其它插值 + +把 `s` 放在字符串前,只是 Scala 提供的插值的一种可能。 + +Scala 内置三种字符串插值方法: `s`, `f` 和 `raw`. +进一步,字符串插值器只是一种特殊的方法,所以你也可以定义自己的插值器。例如, +有些数据库的函数库定义了 `sql` 插值器,这个插值器可以返回数据库查询。 + +## `s` 插值器 (`s`-字符串) + +在任何字符串字面量加上 `s` 前缀,就可以让你在字符串中直接使用变量。你已经在这里见过这个例子: + +{% tabs example-2 %} +{% tab 'Scala 2 and 3' for=example-2 %} +```scala +val name = "James" +val age = 30 +println(s"$name is $age years old") // "James is 30 years old" +``` +{% endtab %} +{% endtabs %} + +这里字符串中的 `$name` 和 `$age` 占位符相应地被调用 `name.toString` 和 `age.toString` 的结果所替换。 +`s`-字符串可以获取当前作用域的所有变量。 + +虽然这看着很明显,但它很重要,需要在这指出来,字符串插值在普通字符串字面量中_不_起作用: + +{% tabs example-3 %} +{% tab 'Scala 2 and 3' for=example-3 %} +```scala +val name = "James" +val age = 30 +println("$name is $age years old") // "$name is $age years old" +``` +{% endtab %} +{% endtabs %} + +字符串插值器可以使用任何表达式。例如: + +{% tabs example-4 %} +{% tab 'Scala 2 and 3' for=example-4 %} +```scala +println(s"2 + 2 = ${2 + 2}") // "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // "x.abs = 1" +``` +{% endtab %} +{% endtabs %} + +任何表达式可以嵌入 `${}` 中. + +有些特殊字符在嵌入到字符串内时需要转义。 +当需要显示真实的美元符号时,你可以把美元符号双写 `$$`, 像这样: + +{% tabs example-5 %} +{% tab 'Scala 2 and 3' for=example-5 %} +```scala +println(s"New offers starting at $$14.99") // "New offers starting at $14.99" +``` +{% endtab %} +{% endtabs %} + +双引号一样需要转义。这个可以使用三引号来达到,如下: + +{% tabs example-6 %} +{% tab 'Scala 2 and 3' for=example-6 %} +```scala +println(s"""{"name":"James"}""") // `{"name":"James"}` +``` +{% endtab %} +{% endtabs %} + +最后,可以在所有多行字符串内插值 + +{% tabs example-7 %} +{% tab 'Scala 2 and 3' for=example-7 %} +```scala +println(s"""name: "$name", + |age: $age""".stripMargin) +``` + +This will print as follows: + +``` +name: "James" +age: 30 +``` +{% endtab %} +{% endtabs %} + +## `f` 插值器 (`f`-字符串) + +在任何字符串字面量加上 `f` 前缀,允许你创建简单的格式化字符串,像其它语言中的 `printf`。当使用 `f` 插值器时, +所有变量的引用应该遵循 `printf` 风格的格式化字符串,像 `%d`。让我们看以下例子: + +{% tabs example-8 %} +{% tab 'Scala 2 and 3' for=example-8 %} +```scala +val height = 1.9d +val name = "James" +println(f"$name%s is $height%2.2f meters tall") // "James is 1.90 meters tall" +``` +{% endtab %} +{% endtabs %} + +`f` 插值器是类型安全的。如果你尝试把一个双精度数传递给一个只能处理整数的格式化字符串,编译器会发出一个错误信息。 +例如: + +{% tabs f-interpolator-error class=tabs-scala-version %} + +{% tab 'Scala 2' for=f-interpolator-error %} +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +:9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ +``` +{% endtab %} + +{% tab 'Scala 3' for=f-interpolator-error %} +```scala +val height: Double = 1.9d + +scala> f"$height%4d" +-- Error: ---------------------------------------------------------------------- +1 |f"$height%4d" + | ^^^^^^ + | Found: (height : Double), Required: Int, Long, Byte, Short, BigInt +1 error found + +``` +{% endtab %} +{% endtabs %} + +`f` 插值器使用来自 Java 的字符串格式化工具。格式化允许在 `%` 字符后,在[Formatter javadoc][java-format-docs] 中有说明。 +如果没有 `%` 字符在变量之后,那么 `%s` (`String`) 作为缺省的格式化工具。 + +最后,像在 Java 里,使用 `%%` 来让 `%` 字符出现在输出字符中: + +{% tabs literal-percent %} +{% tab 'Scala 2 and 3' for=literal-percent %} +```scala +println(f"3/19 is less than 20%%") // "3/19 is less than 20%" +``` +{% endtab %} +{% endtabs %} + +### `raw` 插值器 + +raw 插值器 和 `s` 插值器很像,除了它不动字符串内的转义符。这里有一个处理字符串的例子: + +{% tabs example-9 %} +{% tab 'Scala 2 and 3' for=example-9 %} +```scala +scala> s"a\nb" +res0: String = +a +b +``` +{% endtab %} +{% endtabs %} + +这里 `s` 字符串插值器把 `\n` 替换成回车字符。`raw` 插值器不会做那些。 + +{% tabs example-10 %} +{% tab 'Scala 2 and 3' for=example-10 %} +```scala +scala> raw"a\nb" +res1: String = a\nb +``` +{% endtab %} +{% endtabs %} + +当你希望避免像 `\n` 这样的表达式转义成回车符,raw 插值器会有用。 + +除了这三种自带的字符串插值器外,你还可以定义自己的插值器。 + +## 高级用法 + +字面量 `s"Hi $name"` 被 Scala 当成 _过程_ 字符串字面量来进行分析。 +这意味着编译器对这个字面量额外做了一些工作。具体的处理后的字符串 +和字符串插入器可以在 [SIP-11][sip-11] 中找到描述,但这里用一个快速的例子来展示它 +是如何工作的。 + +### 定制插值器 + +在 Scala 中,所有被处理过的字符串字面量都是简单的代码转换。任何时候当编译器遇到 +形式如下,处理过的字符串字面量时: + +{% tabs example-11 %} +{% tab 'Scala 2 and 3' for=example-11 %} +```scala +id"string content" +``` +{% endtab %} +{% endtabs %} + +编译器把这段代码转换成 在 [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html) 实例之上调用 (`id`) 方法. +该方法也在隐式作用域有效。 +为了定义我们自己的字符串插值器,我们需要创建一个 implicit class (Scala 2) 或者一个 `extension` 方法(Scala 3)方法,这样可以把新方法添加到 `StringContext` 中。 + +作为一个小例子,让我们假定 `Point` 类,并假定我们想创建一个定制化的插值器,它把 `p"a,b"` into a 转换成 `Point` 对象。 + +{% tabs custom-interpolator-1 %} +{% tab 'Scala 2 and 3' for=custom-interpolator-1 %} +```scala +case class Point(x: Double, y: Double) + +val pt = p"1,-2" // Point(1.0,-2.0) +``` +{% endtab %} +{% endtabs %} + +我们首先实现一个如下的 `StringContext` 扩展来创建一个定制的 `p`-插值器: + +{% tabs custom-interpolator-2 class=tabs-scala-version %} + +{% tab 'Scala 2' for=custom-interpolator-2 %} +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Any*): Point = ??? +} +``` + +**注意:** 在 Scala 2.x 中重要的一点是继承自 `AnyVal` ,从而防止运行时对每个插值器进行实例化。 +更多内容见 [值类][value-classes] 文档。 + +{% endtab %} + +{% tab 'Scala 3' for=custom-interpolator-2 %} +```scala +extension (sc: StringContext) + def p(args: Any*): Point = ??? +``` +{% endtab %} + +{% endtabs %} + +一旦这个扩展在作用域,当 Scala 编译器遇到 `p"some string"` 时,它将 +`some string` 中每一个嵌入到字符串中的变量转换为字符串和表达式参数。 + +例如 `p"1, $someVar"` 将转变成: + +{% tabs extension-desugaring class=tabs-scala-version %} + +{% tab 'Scala 2' for=extension-desugaring %} +```scala +new StringContext("1, ", "").p(someVar) +``` + +隐式类将用来重写成以下的样子: + +```scala +new PointHelper(new StringContext("1, ", "")).p(someVar) +``` +{% endtab %} + +{% tab 'Scala 3' for=extension-desugaring %} +```scala +StringContext("1, ","").p(someVar) +``` + +{% endtab %} + +{% endtabs %} + +作为结果,每一个处理过的字符串片段都暴露在 `StringContext.parts` 成员内,而在字符串中的任何 +表达式的值都传递给该方法的 `args` 参数。 + +### 实现的例子 + +我们这个天真的 Point 插值器方法的实现看着像以下的代码, +但是更复杂的方法可能会用更加精确的控制用于处理字符串 `parts` 和 表达式 `args`, +这样可以替代目前重用 `s`-插值器。 + +{% tabs naive-implementation class=tabs-scala-version %} + +{% tab 'Scala 2' for=naive-implementation %} +```scala +implicit class PointHelper(val sc: StringContext) extends AnyVal { + def p(args: Double*): Point = { + // reuse the `s`-interpolator and then split on ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } +} + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` +{% endtab %} + +{% tab 'Scala 3' for=naive-implementation %} +```scala +extension (sc: StringContext) + def p(args: Double*): Point = { + // reuse the `s`-interpolator and then split on ',' + val pts = sc.s(args: _*).split(",", 2).map { _.toDoubleOption.getOrElse(0.0) } + Point(pts(0), pts(1)) + } + +val x=12.0 + +p"1, -2" // Point(1.0, -2.0) +p"${x/5}, $x" // Point(2.4, 12.0) +``` +{% endtab %} +{% endtabs %} + +虽然字符串插值器刚开始是用来创建某种字符串形式,但使用上面的定制插值器可以有强大的句法简写, +并且社区已经制造了一些语法便捷的用途,如 ANSI 终端颜色扩展,可执行 SQL 查询,神奇的 +`$"identifier"` 表达,还有更多其它的。 + +[java-format-docs]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#detail +[value-classes]: {% link _overviews/core/value-classes.md %} +[sip-11]: {% link _sips/sips/string-interpolation.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-collections.md b/_zh-cn/overviews/scala3-book/taste-collections.md new file mode 100644 index 0000000000..966872f1a3 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-collections.md @@ -0,0 +1,156 @@ +--- +title: 集合 +type: section +description: This page provides a high-level overview of the main features of the Scala 3 programming language. +language: zh-cn +num: 13 +previous-page: taste-objects +next-page: taste-contextual-abstractions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 库具有一组丰富的集合类,这些类具有一组丰富的方法。 +集合类有不可变和可变两种形式。 + +## 创建列表 + +为了让您了解这些类的工作原理,下面是一些使用 `List` 类的示例,该类是不可变的链接列表类。 +这些示例显示了创建填充的 `List` 的不同方法: + +{% tabs collection_1 %} +{% tab 'Scala 2 and 3' for=collection_1 %} + +```scala +val a = List(1, 2, 3) // a: List[Int] = List(1, 2, 3) + +// Range methods +val b = (1 to 5).toList // b: List[Int] = List(1, 2, 3, 4, 5) +val c = (1 to 10 by 2).toList // c: List[Int] = List(1, 3, 5, 7, 9) +val e = (1 until 5).toList // e: List[Int] = List(1, 2, 3, 4) +val f = List.range(1, 5) // f: List[Int] = List(1, 2, 3, 4) +val g = List.range(1, 10, 3) // g: List[Int] = List(1, 4, 7) +``` + +{% endtab %} +{% endtabs %} + +## `List`方法 + +拥有填充的列表后,以下示例将显示可以对其调用的一些方法。 +请注意,这些都是函数式方法,这意味着它们不会改变调用的集合,而是返回包含更新元素的新集合。 +每个表达式返回的结果显示在每行的注释中: + +{% tabs collection_2 %} +{% tab 'Scala 2 and 3' for=collection_2 %} + +```scala +// a sample list +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.drop(2) // List(30, 40, 10) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeWhile(_ < 30) // List(10, 20) + +// flatten +val a = List(List(1,2), List(3,4)) +a.flatten // List(1, 2, 3, 4) + +// map, flatMap +val nums = List("one", "two") +nums.map(_.toUpperCase) // List("ONE", "TWO") +nums.flatMap(_.toUpperCase) // List('O', 'N', 'E', 'T', 'W', 'O') +``` + +{% endtab %} +{% endtabs %} + +这些示例显示了如何使用 `foldLeft` 和 `reduceLeft` 方法来对整数序列中的值求和: + +{% tabs collection_3 %} +{% tab 'Scala 2 and 3' for=collection_3 %} + +```scala +val firstTen = (1 to 10).toList // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +firstTen.reduceLeft(_ + _) // 55 +firstTen.foldLeft(100)(_ + _) // 155 (100 is a “seed” value) +``` + +{% endtab %} +{% endtabs %} + +Scala 集合类还有更多可用的方法,它们在[集合章节][collections]和 [API 文档][api]中进行了演示。 + +## 元组 + +Scala _元组_ 是一种类型,可让您轻松地将不同类型的集合放在同一个容器中。 +例如,给定以下 `Person` 样例类: + +{% tabs collection_4 %} +{% tab 'Scala 2 and 3' for=collection_4 %} + +```scala +case class Person(name: String) +``` + +{% endtab %} +{% endtabs %} + +这是演示你如创建一个元组,这个元组包含 `Int`, `String`, 和定制的 `Person` 值: + +{% tabs collection_5 %} +{% tab 'Scala 2 and 3' for=collection_5 %} + +```scala +val t = (11, "eleven", Person("Eleven")) +``` + +{% endtab %} +{% endtabs %} + +有元组后,可以通过将其值绑定到变量来访问,也可以通过数字访问它们: + +{% tabs collection_6 %} +{% tab 'Scala 2 and 3' for=collection_6 %} + +```scala +t(0) // 11 +t(1) // "eleven" +t(2) // Person("Eleven") +``` + +{% endtab %} +{% endtabs %} + +您还可以使用以下 _解构_ 的办法将元组字段分配变量名: + +{% tabs collection_7 %} +{% tab 'Scala 2 and 3' for=collection_7 %} + +```scala +val (num, str, person) = t + +// result: +// val num: Int = 11 +// val str: String = eleven +// val person: Person = Person(Eleven) +``` + +{% endtab %} +{% endtabs %} + +有些情况更适合使用元组, 那就是当你想要将异构类型的集合放在一个小的类似集合的结构中。 +有关更多元组详细信息,请参阅 [参考文档][reference]。 + +[collections]: {% link _zh-cn/overviews/scala3-book/collections-intro.md %} +[api]: https://scala-lang.org/api/3.x/ +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md b/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md new file mode 100644 index 0000000000..cfa5db2f45 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md @@ -0,0 +1,77 @@ +--- +title: 上下文抽象 +type: section +description: This section provides an introduction to Contextual Abstractions in Scala 3. +language: zh-cn +num: 14 +previous-page: taste-collections +next-page: taste-toplevel-definitions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% comment %} +TODO: Now that this is a separate section, it needs a little more content. +{% endcomment %} + +在某些情况下,可以省略在方法调用中你认为是重复的的某些参数。 + +这些参数之所以称为 _上下文参数_,是因为它们是由编译器从方法调用周围的上下文中推断出来的。 + +例如,考虑一个程序,该程序按两个条件对地址列表进行排序:城市名称,然后是街道名称。 + +{% tabs contextual_1 %} +{% tab 'Scala 2 and 3' for=contextual_1 %} + +```scala +val addresses: List[Address] = ... + +addresses.sortBy(address => (address.city, address.street)) +``` +{% endtab %} +{% endtabs %} + +`sortBy` 方法调用一个函数,该函数为每个地址返回值,这个值会用来与其他地址比较。 +在本例中,我们传递一个函数,该函数返回一对,该对包含城市名称和街道名称。 + +请注意,我们只指示 _怎么_ 比较的,而不是 _如何_ 来执行比较。 +排序算法如何知道如何比较 `String` 对的? + +实际上,`sortBy` 方法采用第二个参数---一个上下文参数---该参数由编译器推断。 +它不会出现在上面的示例中,因为它是由编译器提供的。 + +第二个参数实现 _如何_ 进行比较。 +省略它很方便,因为我们知道 `String` 通常使用词典顺序进行比较。 + +但是,也可以显式传递它: + +{% tabs contextual_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=contextual_2 %} + +```scala +addresses.sortBy(address => (address.city, address.street))(Ordering.Tuple2(Ordering.String, Ordering.String)) +``` + +{% endtab %} +{% tab 'Scala 3' for=contextual_2 %} + +```scala +addresses.sortBy(address => (address.city, address.street))(using Ordering.Tuple2(Ordering.String, Ordering.String)) +``` +{% endtab %} +{% endtabs %} + +在本例中,`Ordering.Tuple2(Ordering.String, Ordering.String)` 实例正是编译器以其他方式推断的实例。 +换句话说,这两个示例生成相同的程序。 + +_上下文抽象_ 用于避免重复代码。 +它们帮助开发人员编写可扩展且同时简洁的代码段。 + +有关更多详细信息,请参阅本书的[上下文抽象章节][contextual],以及[参考文档][reference]。 + +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/taste-control-structures.md b/_zh-cn/overviews/scala3-book/taste-control-structures.md new file mode 100644 index 0000000000..a6d0196ee5 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-control-structures.md @@ -0,0 +1,546 @@ +--- +title: 控制结构 +type: section +description: This section demonstrates Scala 3 control structures. +language: zh-cn +num: 8 +previous-page: taste-vars-data-types +next-page: taste-modeling + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 具有您在其他编程语言中可以找到的控制结构,并且还具有强大的 `for` 表达式和 `match` 表达式: + +- `if`/`else` +- `for` 循环和表达式 +- `match` 表达式 +- `while` 循环 +- `try`/`catch` + +这些结构在以下示例中进行了说明。 + +## `if`/`else` + +Scala 的 `if`/`else` 控制结构看起来与其他语言相似: + +{% tabs if-else class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else %} + +```scala +if (x < 0) { + println("negative") +} else if (x == 0) { + println("zero") +} else { + println("positive") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else %} + +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` + +{% endtab %} +{% endtabs %} + +请注意,这确实是一个 _表达式_ ---不是一个 _语句_。 +这意味着它返回一个值,因此您可以将结果赋值给一个变量: + +{% tabs if-else-expression class=tabs-scala-version %} +{% tab 'Scala 2' for=if-else-expression %} + +```scala +val x = if (a < b) { a } else { b } +``` + +{% endtab %} + +{% tab 'Scala 3' for=if-else-expression %} + +```scala +val x = if a < b then a else b +``` + +{% endtab %} +{% endtabs %} + +正如您将在本书中看到的那样,_所有_ Scala 控制结构都可以用作表达式。 + +> 表达式返回结果,而语句不返回。 +> 语句通常用于它们的副作用,例如使用 `println` 打印到控制台。 + +## `for` 循环和表达式 + +`for` 关键字用于创建 `for` 循环。 +这个例子展示了如何打印 `List` 中的每个元素: + +{% tabs for-loop class=tabs-scala-version %} +{% tab 'Scala 2' for=for-loop %} + +```scala +val ints = List(1, 2, 3, 4, 5) + +for (i <- ints) println(i) +``` + +> 代码 `i <- ints` 被称为_生成器_,在括号内的生成器后面是_循环体_。 + +{% endtab %} + +{% tab 'Scala 3' for=for-loop %} + + +```scala +val ints = List(1, 2, 3, 4, 5) + +for i <- ints do println(i) +``` + +> 代码 `i <- ints` 被称为 _生成器_,紧随 `do` 关键字的代码是 _循环体_。 + +{% endtab %} +{% endtabs %} + +### 守卫 + +您还可以在 `for` 循环中使用一个或多个 `if` 表达式。 +这些被称为 _守卫_。 +此示例打印 `ints` 中大于 `2` 的所有数字: + +{% tabs for-guards class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards %} + +```scala +for (i <- ints if i > 2) + println(i) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards %} + +```scala +for + i <- ints + if i > 2 +do + println(i) +``` + +{% endtab %} +{% endtabs %} + +您可以使用多个生成器和守卫。 +此循环遍历数字 `1` 到 `3`,并且对于每个数字,它还遍历字符 `a` 到 `c`。 +然而,它也有两个守卫,所以唯一一次调用 print 语句是当 `i` 的值为 `2` 并且 `j` 是字符 `b` 时: + +{% tabs for-guards-multi class=tabs-scala-version %} +{% tab 'Scala 2' for=for-guards-multi %} + +```scala +for { + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +} { + println(s"i = $i, j = $j") // prints: "i = 2, j = b" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-guards-multi %} + +```scala +for + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +do + println(s"i = $i, j = $j") // prints: "i = 2, j = b" +``` + +{% endtab %} +{% endtabs %} + +### `for` 表达式 + +`for` 关键字更强大:当您使用 `yield` 关键字代替 `do` 时,您会创建 `for` _表达式_用于计算和产生结果。 + +几个例子演示了这一点。 +使用与上一个示例相同的 `ints` 列表,此代码创建一个新列表,其中新列表中每个元素的值是原始列表中元素值的两倍: + +{% tabs for-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expression_1 %} + +```` +scala> val doubles = for (i <- ints) yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +{% endtab %} + +{% tab 'Scala 3' for=for-expression_1 %} + +```` +scala> val doubles = for i <- ints yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +{% endtab %} +{% endtabs %} + +Scala 的控制结构语法很灵活,`for` 表达式可以用其他几种方式编写,具体取决于您的偏好: + +{% tabs for-expressioni_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_2 %} + +```scala +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_2 %} + +```scala +val doubles = for i <- ints yield i * 2 // 如上所示的风格 +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +{% endtab %} +{% endtabs %} + +此示例显示如何将列表中每个字符串的第一个字符大写: + +{% tabs for-expressioni_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_3 %} + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for (name <- names) yield name.capitalize +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_3 %} + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for name <- names yield name.capitalize +``` + +{% endtab %} +{% endtabs %} + +最后,这个 `for` 表达式遍历一个字符串列表,并返回每个字符串的长度,但前提是该长度大于 `4`: + +{% tabs for-expressioni_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=for-expressioni_4 %} + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = + for (f <- fruits if f.length > 4) yield f.length + +// fruitLengths: List[Int] = List(5, 6, 6) +``` + +{% endtab %} + +{% tab 'Scala 3' for=for-expressioni_4 %} + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = for + f <- fruits + if f.length > 4 +yield + // 在这里你可以 + // 使用多行代码 + f.length + +//fruitLengths: List[Int] = List(5, 6, 6) +``` + +{% endtab %} +{% endtabs %} + +`for` 循环和表达式更多细节在本书的 [控制结构部分][control] 中,和 [参考文档]({{ site.scala3ref }}/other-new-features/control-syntax.html) 中。 + +## `match` 表达式 + +Scala 有一个 `match` 表达式,它最基本的用途类似于 Java `switch` 语句: + +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' for=match %} + +```scala +val i = 1 + +// later in the code ... +i match { + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match %} + +```scala +val i = 1 + +// later in the code ... +i match + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +``` + +{% endtab %} +{% endtabs %} + +但是,`match` 确实是一个表达式,这意味着它会根据模式匹配返回一个结果,您可以将其绑定到一个变量: + +{% tabs match-expression_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_1 %} + +```scala +val result = i match { + case 1 => "one" + case 2 => "two" + case _ => "other" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_1 %} + + +```scala +val result = i match + case 1 => "one" + case 2 => "two" + case _ => "other" +``` + +{% endtab %} +{% endtabs %} + +`match` 不仅限于使用整数值,它可以用于任何数据类型: + +{% tabs match-expression_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_2 %} + +```scala +val p = Person("Fred") + +// later in the code +p match { + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_2 %} + +```scala +val p = Person("Fred") + +// later in the code +p match + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +``` + +{% endtab %} +{% endtabs %} + +事实上,`match` 表达式可以用许多模式的不同类型来测试变量。 +此示例显示 (a) 如何使用 `match` 表达式作为方法的主体,以及 (b) 如何匹配显示的所有不同类型: + +{% tabs match-expression_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=match-expression_3 %} + +```scala +// getClassAsString is a method that takes a single argument of any type. +def getClassAsString(x: Any): String = x match { + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[_] => "List" + case _ => "Unknown" +} + +// examples +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +因为 `getClassAsString` 方法获取 `Any` 类型的参数值,所以它可以解耦任意模式类型。 + +{% endtab %} + +{% tab 'Scala 3' for=match-expression_3 %} + +```scala +// getClassAsString 是一个接受任何类型的单个参数的方法。 +def getClassAsString(x: Matchable): String = x match + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[_] => "List" + case _ => "Unknown" + +// examples +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +`getClassAsString` 方法将 [Matchable]({{ site.scala3ref }}/other-new-features/matchable.html) 类型的值作为参数,它可以是 +任何支持模式匹配的类型(某些类型不支持模式匹配,因为这可能打破封装)。 + +{% endtab %} +{% endtabs %} + +Scala 中的模式匹配还有 _更多_ 内容。 +模式可以嵌套,模式的结果可以绑定,模式匹配甚至可以是用户自定义的。 +有关详细信息,请参阅 [控制结构章节][control] 中的模式匹配示例。 + +## `try`/`catch`/`finally` + +Scala 的 `try`/`catch`/`finally` 控制结构让你捕获异常。 +它类似于 Java,但其语法与 `match` 表达式一致: + +{% tabs try class=tabs-scala-version %} +{% tab 'Scala 2' for=try %} + +```scala +try { + writeTextToFile(text) +} catch { + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +} finally { + println("Clean up your resources here.") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=try %} + +```scala +try + writeTextToFile(text) +catch + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +finally + println("Clean up your resources here.") +``` + +{% endtab %} +{% endtabs %} + +## `while` 循环 + +Scala 还有一个 `while` 循环结构。 +它的单行语法如下所示: + +{% tabs while_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_1 %} + +```scala +while (x >= 0) { x = f(x) } +``` + +{% endtab %} + +{% tab 'Scala 3' for=while_1 %} + +```scala +while x >= 0 do x = f(x) +``` +为了兼容性,Scala 3 仍然支持 Scala 2 语法。 + +{% endtab %} +{% endtabs %} + +`while` 循环多行语法如下所示: + +{% tabs while_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=while_2 %} + +```scala +var x = 1 + +while (x < 3) { + println(x) + x += 1 +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=while_2 %} + +```scala +var x = 1 + +while + x < 3 +do + println(x) + x += 1 +``` + +{% endtab %} +{% endtabs %} + +## 自定义控制结构 + +由于传名参数、中缀表示法、流畅接口、可选括号、扩展方法和高阶函数等功能,您还可以创建自己的代码,就像控制结构一样工作。 +您将在 [控制结构][control] 部分了解更多信息。 + +[control]: {% link _zh-cn/overviews/scala3-book/control-structures.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-functions.md b/_zh-cn/overviews/scala3-book/taste-functions.md new file mode 100644 index 0000000000..71cea44649 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-functions.md @@ -0,0 +1,87 @@ +--- +title: 头等函数 +type: section +description: This page provides an introduction to functions in Scala 3. +language: zh-cn +num: 11 +previous-page: taste-methods +next-page: taste-objects + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala具有函数式编程语言中您期望的大多数功能,包括: + +- Lambdas(匿名函数) +- 高阶函数 (HOFs) +- 标准库中的不可变集合 + +Lambdas(也称为 _匿名函数_)是保持代码简洁但可读性的重要组成部分。 + +`List` 类的 `map` 方法是高阶函数的典型示例---一个将函数作为参数的函数。 + +这两个示例是等效的,并演示如何通过将 lambda 传递到 `map` 方法中,将列表中的每个数字乘以 `2`: + + +{% tabs function_1 %} +{% tab 'Scala 2 and 3' for=function_1 %} + +```scala +val a = List(1, 2, 3).map(i => i * 2) // List(2,4,6) +val b = List(1, 2, 3).map(_ * 2) // List(2,4,6) +``` + +{% endtab %} +{% endtabs %} + +这些示例也等效于以下代码,该代码使用 `double` 方法而不是lambda: + +{% tabs function_2 %} +{% tab 'Scala 2 and 3' for=function_2 %} + +```scala +def double(i: Int): Int = i * 2 + +val a = List(1, 2, 3).map(i => double(i)) // List(2,4,6) +val b = List(1, 2, 3).map(double) // List(2,4,6) +``` + +{% endtab %} +{% endtabs %} + +> 如果您以前从未见过 `map` 方法,它会将给定的函数应用于列表中的每个元素,从而生成一个包含结果值的新列表。 + +将 lambda 传递给集合类上的高阶函数(如 `List`)是 Scala 体验的一部分,您每天都会这样做。 + +## 不可变集合 + +当您使用不可变集合(如 `List`,`Vector`)以及不可变的 `Map` 和 `Set` 类时,重要的是要知道这些函数不会改变它们被调用的集合;相反,它们返回包含更新数据的新集合。 +因此,以“流式”的风格将它们链接在一起以解决问题也很常见。 + +例如,此示例演示如何对一个集合进行两次筛选,然后将其余集合中的每个元素乘某个数: + +{% tabs function_3 %} +{% tab 'Scala 2 and 3' for=function_3 %} + +```scala +// a sample list +val nums = (1 to 10).toList // List(1,2,3,4,5,6,7,8,9,10) + +// methods can be chained together as needed +val x = nums.filter(_ > 3) + .filter(_ < 7) + .map(_ * 10) + +// result: x == List(40, 50, 60) +``` + +{% endtab %} +{% endtabs %} + +除了在整个标准库中使用的高阶函数外,您还可以[创建自己的][higher-order] 高阶函数。 + +[higher-order]: {% link _zh-cn/overviews/scala3-book/fun-hofs.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-hello-world.md b/_zh-cn/overviews/scala3-book/taste-hello-world.md new file mode 100644 index 0000000000..cebe30f7fa --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-hello-world.md @@ -0,0 +1,202 @@ +--- +title: Hello, World! +type: section +description: This section demonstrates a Scala 3 'Hello, World!' example. +language: zh-cn +num: 5 +previous-page: taste-intro +next-page: taste-repl + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +> **提示**:在以下示例中,尝试选择您喜欢的 Scala 版本。 +> + +## 你的第一个 Scala 程序 + +Scala “Hello, world!” 例子展示如下。 +首先,把以下代码写入 _Hello.scala_: + + +{% tabs hello-world-demo class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-demo %} +```scala +object hello { + def main(args: Array[String]) = { + println("Hello, World!") + } +} +``` +> 代码中,在名为 `hello` 的 Scala `object` 中,我们定义了一个名称为 `main` 的方法。 +> 在 Scala 中 `object` 类似 `class`,但定义了一个可以传递的单例实例。 +> `main` 用名为 `args` 的输入参数,该参数必须是 `Array[String]` 类型(暂时忽略 `args`)。 + +{% endtab %} + +{% tab 'Scala 3' for=hello-world-demo %} +```scala +@main def hello() = println("Hello, World!") +``` +> 代码中, `hello` 是方法。 +> 它使用 `def` 定义,并用 `@main` 注释的手段把它声明为“main”方法。 +> 使用 `println` 方法,它在标准输出 (STDOUT)中打印了 `"Hello, world!"` 字符串。 + +{% endtab %} + +{% endtabs %} + + +下一步,用 `scalac` 编译代码: + +```bash +$ scalac Hello.scala +``` + +如果你是从 Java 转到 Scala,`scalac` 就像 `javac`,所以该命令会创建几个文件: + + +{% tabs hello-world-outputs class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-outputs %} +```bash +$ ls -1 +hello$.class +hello.class +hello.scala +``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-outputs %} +```bash +$ ls -1 +hello$package$.class +hello$package.class +hello$package.tasty +hello.scala +hello.class +hello.tasty +``` +{% endtab %} + +{% endtabs %} + + +与 Java 一样,_.class_ 文件是字节码文件,它们已准备好在 JVM 中运行。 + +现在您可以使用 `scala` 命令运行 `hello` 方法: + +```bash +$ scala hello +Hello, world! +``` + +假设它运行成功,那么恭喜,您刚刚编译并运行了您的第一个 Scala 应用程序。 + +> 在 [Scala 工具][scala_tools] 章节中可以找到 sbt 和其他使 Scala 开发更容易的工具相关的更多信息。 + +## 要求用户输入 + +在下一个示例中,让我们在问候用户之前询问用户名! + +有几种方法可以从命令行读取输入,但一种简单的方法是使用 +_scala.io.StdIn_ 对象中的 `readline` 方法。要使用它,您需要先导入它,如下所示: + +{% tabs import-readline %} +{% tab 'Scala 2 and 3' for=import-readline %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +为了演示其工作原理,让我们创建一个小示例。将此源代码放在名为 _helloInteractive.scala_ 的文件里: + + +{% tabs hello-world-interactive class=tabs-scala-version %} + +{% tab 'Scala 2' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +object helloInteractive { + + def main(args: Array[String]) = { + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") + } + +} +``` +{% endtab %} + +{% tab 'Scala 3' for=hello-world-interactive %} +```scala +import scala.io.StdIn.readLine + +@main def helloInteractive() = + println("Please enter your name:") + val name = readLine() + + println("Hello, " + name + "!") +``` +{% endtab %} + +{% endtabs %} + + +在此代码中,我们将 `readLine` 的结果保存到一个名为 `name` 的变量中,然后 +使用字符串上的 `+` 运算符将 `“Hello, ”` 与 `name` 和 `"!"` 连接起来,生成单一字符串值。 + +> 您可以通过阅读[变量和数据类型](/zh-cn/scala3/book/taste-vars-data-types.html)来了解有关使用 `val` 的更多信息。 + +然后使用 `scalac` 编译代码: + +```bash +$ scalac helloInteractive.scala +``` + +然后用 `scala helloInteractive` 运行它,这次程序将在询问您的名字后暂停并等待, +直到您键入一个名称,然后按键盘上的回车键,如下所示: + +```bash +$ scala helloInteractive +Please enter your name: +▌ +``` + +当您在提示符下输入您的姓名时,最终的交互应如下所示: + +```bash +$ scala helloInteractive +Please enter your name: +Alvin Alexander +Hello, Alvin Alexander! +``` + +### 关于导入的说明 + +正如您在此应用程序中看到的,有时某些方法或我们稍后将看到的其他类型的定义不可用, +除非您使用如下所示的 `导入` 子句: + +{% tabs import-readline-2 %} +{% tab 'Scala 2 and 3' for=import-readline-2 %} +```scala +import scala.io.StdIn.readLine +``` +{% endtab %} +{% endtabs %} + +导入可通过多种方式帮助您编写代码: + - 您可以将代码放在多个文件中,以帮助避免混乱,并帮助导航大型项目。 + - 您可以使用包含有用功能的代码库,该库可能是由其他人编写 + - 您可以知道某个定义的来源(特别是如果它没有写入当前文件)。 + +[scala_tools]: {% link _zh-cn/overviews/scala3-book/scala-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-intro.md b/_zh-cn/overviews/scala3-book/taste-intro.md new file mode 100644 index 0000000000..0d2f0fdf50 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-intro.md @@ -0,0 +1,27 @@ +--- +title: Scala 的味道 +type: chapter +description: This chapter provides a high-level overview of the main features of the Scala 3 programming language. +language: zh-cn +num: 4 +previous-page: why-scala-3 +next-page: taste-hello-world + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本章提供的快速导览涉及 Scala 3 编程语言的主要特性。 +在本导览之后,本书的其余部分提供了有关这些特性的更多详细信息,而[参考文档][reference]提供了_许多_ 细节。 + +## 设置 Scala + +在本章和本书的其余部分,我们鼓励您通过复制或手动键入来尝试这些示例。遵循示例所需的工具你可以按照我们的[入门指南][get-started]在自己的计算机上安装。 + +> 或者,您可以使用 [Scastie](https://scastie.scala-lang.org) 在 Web 浏览器中运行这些示例,这是一个完全在线的编辑器和 Scala 的代码运行器。 + +[reference]: {{ site.scala3ref }}/overview.html +[get-started]: {% link _overviews/getting-started/install-scala.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-methods.md b/_zh-cn/overviews/scala3-book/taste-methods.md new file mode 100644 index 0000000000..521c093a8e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-methods.md @@ -0,0 +1,217 @@ +--- +title: 方法 +type: section +description: This section provides an introduction to defining and using methods in Scala 3. +language: zh-cn +num: 10 +previous-page: taste-modeling +next-page: taste-functions + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +## Scala 方法 + +Scala 类、样例类、traits、枚举和对象都可以包含方法。 +简单方法的语法如下所示: + +{% tabs method_1 %} +{% tab 'Scala 2 and 3' for=method_1 %} + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +``` + +{% endtab %} +{% endtabs %} + +这有一些例子: + +{% tabs method_2 %} +{% tab 'Scala 2 and 3' for=method_2 %} + +```scala +def sum(a: Int, b: Int): Int = a + b +def concatenate(s1: String, s2: String): String = s1 + s2 +``` + +{% endtab %} +{% endtabs %} + +您不必声明方法的返回类型,因此如果您愿意,可以像这样编写这些方法: + +{% tabs method_3 %} +{% tab 'Scala 2 and 3' for=method_3 %} + +```scala +def sum(a: Int, b: Int) = a + b +def concatenate(s1: String, s2: String) = s1 + s2 +``` + +{% endtab %} +{% endtabs %} + +这是你如何调用这些方法: + +{% tabs method_4 %} +{% tab 'Scala 2 and 3' for=method_4 %} + +```scala +val x = sum(1, 2) +val y = concatenate("foo", "bar") +``` + +{% endtab %} +{% endtabs %} + +这是一个多行的方法: + +{% tabs method_5 class=tabs-scala-version %} +{% tab 'Scala 2' for=method_5 %} + +```scala +def getStackTraceAsString(t: Throwable): String = { + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +} +``` + +{% endtab %} +{% tab 'Scala 3' for=method_5 %} + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +``` + +{% endtab %} +{% endtabs %} + +方法参数也可以具有默认值。 +在此示例中,`timeout` 参数的默认值为 `5000`: + +{% tabs method_6 %} +{% tab 'Scala 2 and 3' for=method_6 %} + +```scala +def makeConnection(url: String, timeout: Int = 5000): Unit = + println(s"url=$url, timeout=$timeout") +``` + +{% endtab %} +{% endtabs %} + +由于方法声明中提供了默认的 `超时` 值,因此可以通过以下两种方式调用该方法: + +{% tabs method_7 %} +{% tab 'Scala 2 and 3' for=method_7 %} + +```scala +makeConnection("https://localhost") // url=http://localhost, timeout=5000 +makeConnection("https://localhost", 2500) // url=http://localhost, timeout=2500 +``` + +{% endtab %} +{% endtabs %} + +Scala 还支持在调用方法时使用 _命名参数_,因此如果您愿意,也可以像这样调用该方法: + +{% tabs method_8 %} +{% tab 'Scala 2 and 3' for=method_8 %} + +```scala +makeConnection( + url = "https://localhost", + timeout = 2500 +) +``` + +{% endtab %} +{% endtabs %} + +当多个方法参数具有相同的类型时,命名参数特别有用。 +乍一看,使用此方法,您可能想知道哪些参数设置为 `true` 或 `false`: + +{% tabs method_9 %} +{% tab 'Scala 2 and 3' for=method_9 %} + +```scala +engage(true, true, true, false) +``` + +{% endtab %} +{% endtabs %} + +如果没有IDE的帮助,那段代码可能很难阅读,但这个代码要明显得多: + +{% tabs method_10 %} +{% tab 'Scala 2 and 3' for=method_10 %} + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` +{% endtab %} +{% endtabs %} + +## 扩展方法 + +_扩展方法_ 允许您向封闭类添加新方法。 +例如,如果要将两个名为 `hello` 和 `aloha` 的方法添加到 `String` 类中,请将它们声明为扩展方法: + +{% tabs extension0 %} +{% tab 'Scala 3 Only' %} + +```scala +extension (s: String) + def hello: String = s"Hello, ${s.capitalize}!" + def aloha: String = s"Aloha, ${s.capitalize}!" + +"world".hello // "Hello, World!" +"friend".aloha // "Aloha, Friend!" +``` + +{% endtab %} +{% endtabs %} + +`extension` 关键字声明了括号内的参数将定义一个或多个扩展方法。 +如此示例所示,可以在扩展方法体中使用 `String` 类型的参数 `s`。 + +下一个示例演示如何将 `makeInt` 方法添加到 `String` 类。 +在这里,`makeInt` 采用一个名为 `radix` 的参数。 +该代码不考虑可能的字符串到整数转换错误,但跳过细节,示例显示了它的工作原理: + +{% tabs extension %} +{% tab 'Scala 3 Only' %} + +```scala +extension (s: String) + def makeInt(radix: Int): Int = Integer.parseInt(s, radix) + +"1".makeInt(2) // Int = 1 +"10".makeInt(2) // Int = 2 +"100".makeInt(2) // Int = 4 +``` + +{% endtab %} +{% endtabs %} + +## 参见 + +Scala方法可以更强大:它们可以采用类型参数和上下文参数。 +它们在[领域建模][data-1]一节中有详细介绍。 + +[data-1]: {% link _zh-cn/overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-modeling.md b/_zh-cn/overviews/scala3-book/taste-modeling.md new file mode 100644 index 0000000000..ca39aafe34 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-modeling.md @@ -0,0 +1,428 @@ +--- +title: 领域建模 +type: section +description: This section provides an introduction to data modeling in Scala 3. +language: zh-cn +num: 9 +previous-page: taste-control-structures +next-page: taste-methods + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% comment %} +NOTE: I kept the OOP section first, assuming that most readers will be coming from an OOP background. +{% endcomment %} + +Scala 同时支持函数式编程 (FP) 和面向对象编程 (OOP),以及这两种范式的融合。 +本节简要概述了 OOP 和 FP 中的数据建模。 + +## OOP 领域建模 + +以 OOP 风格编写代码时,用于数据封装的两个主要工具是 _traits_ 和 _classes_。 + +{% comment %} +NOTE: Julien had a comment, “in OOP we don’t really model data. +It’s more about modeling operations, imho.” + +How to resolve? Is there a good DDD term to use here? +{% endcomment %} + +### Traits + +Scala trait 可以用作简单的接口,但它们也可以包含抽象和具体的方法和字段,并且它们可以有参数,就像类一样。 +它们为您提供了一种将行为组织成小型模块化单元的好方法。 +稍后,当您想要创建属性和行为的具体实现时,类和对象可以扩展特征,根据需要混合尽可能多的特征以实现所需的行为。 + +作为如何将 traits 用作接口的示例,以下是三个 traits,它们为狗和猫等动物定义了结构良好并且模块化的行为: + +{% tabs traits class=tabs-scala-version %} +{% tab 'Scala 2' for=traits %} + +```scala +trait Speaker { + def speak(): String // has no body, so it’s abstract +} + +trait TailWagger { + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") +} + +trait Runner { + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits %} + + +```scala +trait Speaker: + def speak(): String // has no body, so it’s abstract + +trait TailWagger: + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") + +trait Runner: + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +``` + +{% endtab %} +{% endtabs %} + +鉴于这些特征,这里有一个 `Dog` 类,它扩展了所有这些特征,同时为抽象 `speak` 方法提供了一种行为: + +{% tabs traits-class class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-class %} + +```scala +class Dog(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Woof!" +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-class %} + +```scala +class Dog(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Woof!" +``` + +{% endtab %} +{% endtabs %} + +请注意该类如何使用 `extends` 关键字扩展 traits。 + +类似地,这里有一个 `Cat` 类,它实现了这些相同的 traits,同时还覆盖了它继承的两个具体方法: + +{% tabs traits-override class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-override %} + +```scala +class Cat(name: String) extends Speaker with TailWagger with Runner { + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-override %} + +```scala +class Cat(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +``` + +{% endtab %} +{% endtabs %} + +这些示例显示了如何使用这些类: + +{% tabs traits-use class=tabs-scala-version %} +{% tab 'Scala 2' for=traits-use %} + +```scala +val d = new Dog("Rover") +println(d.speak()) // prints "Woof!" + +val c = new Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} + +{% tab 'Scala 3' for=traits-use %} + +```scala +val d = Dog("Rover") +println(d.speak()) // prints "Woof!" + +val c = Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +{% endtab %} +{% endtabs %} + +如果该代码有意义---太好了,您把 traits 作为接口感到舒服。 +如果没有,请不要担心,它们在 [Domain Modeling][data-1] 章节中有更详细的解释。 + +### 类 + +Scala _classes_ 用于 OOP 风格的编程。 +这是一个模拟“人”的类的示例。在 OOP 中,字段通常是可变的,所以 `firstName` 和 `lastName` 都被声明为 `var` 参数: + +{% tabs class_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_1 %} + +```scala +class Person(var firstName: String, var lastName: String) { + def printFullName() = println(s"$firstName $lastName") +} + +val p = new Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_1 %} + +```scala +class Person(var firstName: String, var lastName: String): + def printFullName() = println(s"$firstName $lastName") + +val p = Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +{% endtab %} +{% endtabs %} + +请注意,类声明创建了一个构造函数: + +{% tabs class_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=class_2 %} + +```scala +// this code uses that constructor +val p = new Person("John", "Stephens") +``` + +{% endtab %} + +{% tab 'Scala 3' for=class_2 %} + +```scala +// 此代码使用该构造函数 +val p = Person("约翰", "斯蒂芬斯") +``` + +{% endtab %} +{% endtabs %} + +[Domain Modeling][data-1] 章节中介绍了构造函数和其他与类相关的主题。 + +## FP 领域建模 + +{% comment %} +NOTE: Julien had a note about expecting to see sealed traits here. +I didn’t include that because I didn’t know if enums are intended +to replace the Scala2 “sealed trait + case class” pattern. How to resolve? +{% endcomment %} + +以 FP 风格编写代码时,您将使用以下结构: + +- 代数数据类型(ADT)来定义数据 +- Traits 来定义数据上的功能 + +### 枚举和 Sum Types + +Sum types 是在 Scala 中给代数数据类型(ADT)建模的一种方法。 + +`enum` 构造是在 Scala 3 中对代数数据类型 (ADT) 进行建模的好方法。 + +例如,披萨具有三个主要属性: + +- 面饼大小 +- 面饼类型 +- 馅料 + +这些是用枚举简洁地建模的,它们是只包含单例值的 sum types: + +{% tabs enum_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_1 %} + +在 Scala 2 中,用 `sealed` 类和 `case object` 组合在一起来定义枚举: + +```scala +sealed abstract class CrustSize +object CrustSize { + case object Small extends CrustSize + case object Medium extends CrustSize + case object Large extends CrustSize +} + +sealed abstract class CrustType +object CrustType { + case object Thin extends CrustType + case object Thick extends CrustType + case object Regular extends CrustType +} + +sealed abstract class Topping +object Topping { + case object Cheese extends Topping + case object Pepperoni extends Topping + case object BlackOlives extends Topping + case object GreenOlives extends Topping + case object Onions extends Topping +} +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_1 %} + +Scala 3 提供了 `enum` 结构来定义枚举: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +{% endtab %} +{% endtabs %} + +一旦你有了一个枚举,你就可以按照你通常使用特征、类或对象的所有方式来使用枚举: + +{% tabs enum_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_2 %} + +```scala +import CrustSize._ +val currentCrustSize = Small + +// enums in a `match` expression +currentCrustSize match { + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") +} + +// enums in an `if` statement +if (currentCrustSize == Small) println("Small crust size") +``` + +{% endtab %} +{% tab 'Scala 3' for=enum_2 %} + +```scala +import CrustSize.* +val currentCrustSize = Small + +// enums in a `match` expression +currentCrustSize match + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") + +// enums in an `if` statement +if currentCrustSize == Small then println("Small crust size") +``` + +{% endtab %} +{% endtabs %} + +下面是另一个如何在 Scala 中创建 sum type 的示例,它不能被叫作枚举,因为 `succ` 样例类有参数:的示例: + +{% tabs enum_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=enum_3 %} + +```scala +sealed abstract class Nat +object Nat { + case object Zero extends Nat + case class Succ(pred: Nat) extends Nat +} +``` + +Sum Types 在本书的[领域建模]({% link _overviews/scala3-book/domain-modeling-tools.md %})部分有详细的介绍。 + +{% endtab %} +{% tab 'Scala 3' for=enum_3 %} + +```scala +enum Nat: + case Zero + case Succ(pred: Nat) +``` + +枚举在本书的 [领域建模]({% link _overviews/scala3-book/domain-modeling-tools.md %})部分和 [参考文档]({{ site.scala3ref }}/enums/enums.html) 中有详细介绍。 + +{% endtab %} +{% endtabs %} + +### Product Types + +product type 是代数数据类型(ADT),它只含有一个形状,例如一个单例对象,在Scala 中用 `case` 对象来代表;或者是一个可以获取字段的不可变结构,用 `case` 类来代表。 + +`case` 类具有 `class` 的所有功能,还包含其他功能,使它们对函数式编程很有用。 +当编译器在 `class` 前面看到 `case` 关键字时,它具有以下效果和好处: + +- 样例类构造函数参数默认为 public `val` 字段,因此字段是不可变的,并且为每个参数生成访问器方法。 +- 生成一个 `unapply` 方法,它允许您在 `match` 表达式中以更多方式使用 样例类。 +- 在类中生成一个 `copy` 方法。 + 这提供了一种在不更改原始对象的情况下创建对象的更新副本的方法。 +- 生成 `equals` 和 `hashCode` 方法来实现结构相等等式。 +- 生成一个默认的 `toString` 方法,有助于调试。 + +{% comment %} +NOTE: Julien had a comment about how he decides when to use case classes vs classes. Add something here? +{% endcomment %} + +您_可以_自己手动将所有这些方法添加到一个类中,但是由于这些功能在函数式编程中非常常用,因此使用“case”类要方便得多。 + +这段代码演示了几个 `case` 类的特性: + +{% tabs case-class %} +{% tab 'Scala 2 and 3' for=case-class %} + +```scala +// define a case class +case class Person( + name: String, + vocation: String +) + +// create an instance of the case class +val p = Person("Reginald Kenneth Dwight", "Singer") + +// a good default toString method +p // : Person = Person(Reginald Kenneth Dwight,Singer) + +// can access its fields, which are immutable +p.name // "Reginald Kenneth Dwight" +p.name = "Joe" // error: can’t reassign a val field + +// when you need to make a change, use the `copy` method +// to “update as you copy” +val p2 = p.copy(name = "Elton John") +p2 // : Person = Person(Elton John,Singer) +``` + +{% endtab %} +{% endtabs %} + +有关 `case` 类的更多详细信息,请参阅 [领域建模][data-1] 部分。 + +[data-1]: {% link _zh-cn/overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-objects.md b/_zh-cn/overviews/scala3-book/taste-objects.md new file mode 100644 index 0000000000..3d3ec5457e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-objects.md @@ -0,0 +1,172 @@ +--- +title: 单例对象 +type: section +description: This section provides an introduction to the use of singleton objects in Scala 3. +language: zh-cn +num: 12 +previous-page: taste-functions +next-page: taste-collections + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在 Scala 中,`object` 关键字创建一个单例对象。 +换句话说,对象定义了一个只有一个实例的类。 + +对象有多种用途: + +- 它们用于创建实用程序方法的集合。 +- _伴生对象_ 与一个类同名二者在同一个文件里。 + 在此情况下,该类也称为 _伴生类_。 +- 它们用于实现 traits,再用 traits 来创建 _模块_。 + +## “实用工具”方法 + +因为 `object` 是单例,所以它的方法可以像 Java 类中的 `static` 方法一样被访问。 +例如,此 `StringUtils` 对象包含一个与字符串相关的方法的小型集合: + +{% tabs object_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_1 %} + +```scala +object StringUtils { + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +} +``` + +{% endtab %} +{% tab 'Scala 3' for=object_1 %} + +```scala +object StringUtils: + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +``` + +{% endtab %} +{% endtabs %} + +由于 `StringUtils` 是一个单例,因此可以直接在对象上调用其方法: + +{% tabs object_2 %} +{% tab 'Scala 2 and 3' for=object_2 %} + +```scala +val x = StringUtils.isNullOrEmpty("") // true +val x = StringUtils.isNullOrEmpty("a") // false +``` + +{% endtab %} +{% endtabs %} + +## 伴生对象 + +伴生类或对象可以访问其伙伴的私有成员。 +对不特定于伴生类实例的方法和值使用伴生对象。 + +此示例演示了伴生类中的 `area` 方法如何访问其伴生对象中的私有 `calculateArea` 方法: + +{% tabs object_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_3 %} + +```scala +import scala.math._ + +class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` + +{% endtab %} +{% tab 'Scala 3' for=object_3 %} + +```scala +import scala.math.* + +class Circle(radius: Double): + import Circle.* + def area: Double = calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` + +{% endtab %} +{% endtabs %} + +## 从 traits 创建模块 + +对象还可用于实现创建模块的 trait。 +这种技术需要两个traits,并将它们结合起来创建一个具体的 `object`: + +{% tabs object_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=object_4 %} + +```scala +trait AddService { + def add(a: Int, b: Int) = a + b +} + +trait MultiplyService { + def multiply(a: Int, b: Int) = a * b +} + +// implement those traits as a concrete object +object MathService extends AddService with MultiplyService + +// use the object +import MathService._ +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` + +{% endtab %} +{% tab 'Scala 3' for=object_4 %} + +```scala +trait AddService: + def add(a: Int, b: Int) = a + b + +trait MultiplyService: + def multiply(a: Int, b: Int) = a * b + +// implement those traits as a concrete object +object MathService extends AddService, MultiplyService + +// use the object +import MathService.* +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` + +{% endtab %} +{% endtabs %} + +{% comment %} +NOTE: I don’t know if this is worth keeping, but I’m leaving it here as a comment for now. + +> You may read that objects are used to _reify_ traits into modules. +> _Reify_ means, “to take an abstract concept and turn it into something concrete.” This is what happens in these examples, but “implement” is a more familiar word for most people than “reify.” +{% endcomment %} + + diff --git a/_zh-cn/overviews/scala3-book/taste-repl.md b/_zh-cn/overviews/scala3-book/taste-repl.md new file mode 100644 index 0000000000..2b043bef03 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-repl.md @@ -0,0 +1,92 @@ +--- +title: The REPL +type: section +description: This section provides an introduction to the Scala REPL. +language: zh-cn +num: 6 +previous-page: taste-hello-world +next-page: taste-vars-data-types + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala REPL(“Read-Evaluate-Print-Loop”)是一个命令行解释器,您可以将其用作“游乐场”区域来测试 Scala 代码。 +你可以通过运行 `scala` 或 `scala3` 命令来启动一个 REPL 会话,具体取决于您在操作系统命令行中的安装,您将看到如下所示的“欢迎”提示: + +{% tabs command-line class=tabs-scala-version %} + +{% tab 'Scala 2' for=command-line %} +```bash +$ scala +Welcome to Scala {{site.scala-version}} (OpenJDK 64-Bit Server VM, Java 1.8.0_342). +Type in expressions for evaluation. Or try :help. + +scala> _ +``` +{% endtab %} + +{% tab 'Scala 3' for=command-line %} +```bash +$ scala +Welcome to Scala {{site.scala-3-version}} (1.8.0_322, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> _ +``` +{% endtab %} + +{% endtabs %} + +REPL 是一个命令行解释器,所以它就在那里等着你输入一些东西。 +现在您可以输入 Scala 表达式来查看它们是如何工作的: + +{% tabs expression-one %} +{% tab 'Scala 2 and 3' for=expression-one %} +```` +scala> 1 + 1 +val res0: Int = 2 + +scala> 2 + 2 +val res1: Int = 4 +```` +{% endtab %} +{% endtabs %} + +如输出所示,如果您不为表达式的结果分配变量,REPL 会为您创建名为 `res0`、`res1` 等的变量。 +您可以在后续表达式中使用这些变量名称: + +{% tabs expression-two %} +{% tab 'Scala 2 and 3' for=expression-two %} +```` +scala> val x = res0 * 10 +val x: Int = 20 +```` +{% endtab %} +{% endtabs %} + +请注意,REPL 输出还显示了表达式的结果。 + +您可以在 REPL 中运行各种实验。 +这个例子展示了如何创建然后调用一个 `sum` 方法: + +{% tabs expression-three %} +{% tab 'Scala 2 and 3' for=expression-three %} +```` +scala> def sum(a: Int, b: Int): Int = a + b +def sum(a: Int, b: Int): Int + +scala> sum(2, 2) +val res2: Int = 4 +```` +{% endtab %} +{% endtabs %} + +如果您更喜欢基于浏览器的游乐场环境,也可以使用 [scastie.scala-lang.org](https://scastie.scala-lang.org)。 + +如果您更喜欢在文本编辑器中而不是在控制台提示符中编写代码,您可以使用 [worksheet]。 + +[worksheet]: {% link _zh-cn/overviews/scala3-book/tools-worksheets.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-summary.md b/_zh-cn/overviews/scala3-book/taste-summary.md new file mode 100644 index 0000000000..1fee593727 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-summary.md @@ -0,0 +1,35 @@ +--- +title: 总结 +type: section +description: This page provides a summary of the previous 'Taste of Scala' sections. +language: zh-cn +num: 16 +previous-page: taste-toplevel-definitions +next-page: first-look-at-types + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在前面的部分中,您看到了: + +- 如何使用 Scala REPL +- 如何使用 `val` 和 `var` 创建变量 +- 一些常见的数据类型 +- 控制结构 +- 如何使用 OOP 和 FP 样式对现实世界进行建模 +- 如何创建和使用方法 +- 如何使用lambdas(匿名函数)和高阶函数 +- 如何将对象用于多种目的 +- [上下文抽象][contextual]的介绍 + +我们还提到,如果您更喜欢使用基于浏览器的游乐场环境而不是 Scala REPL,您还可以使用[Scastie](https://scastie.scala-lang.org)。 + +Scala还有更多功能在这次旋风之旅中没有涵盖。 +有关更多详细信息,请参阅本书的其余部分和[参考文档][reference]。 + +[reference]: {{ site.scala3ref }}/overview.html +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md b/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md new file mode 100644 index 0000000000..7deb81d9de --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md @@ -0,0 +1,80 @@ +--- +title: 顶层定义 +type: section +description: This page provides an introduction to top-level definitions in Scala 3 +language: zh-cn +num: 15 +previous-page: taste-contextual-abstractions +next-page: taste-summary + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在 Scala 3 中,各种定义都可以在源代码文件的 “顶层” 编写。 +例如,您可以创建一个名为 _MyCoolApp.scala_ 的文件,并将以下内容放入其中: + +{% tabs toplevel_1 %} +{% tab 'Scala 3 only' for=toplevel_1 %} + +```scala +import scala.collection.mutable.ArrayBuffer + +enum Topping: + case Cheese, Pepperoni, Mushrooms + +import Topping.* +class Pizza: + val toppings = ArrayBuffer[Topping]() + +val p = Pizza() + +extension (s: String) + def capitalizeAllWords = s.split(" ").map(_.capitalize).mkString(" ") + +val hwUpper = "hello, world".capitalizeAllWords + +type Money = BigDecimal + +// more definitions here as desired ... + +@main def myApp = + p.toppings += Cheese + println("show me the code".capitalizeAllWords) +``` + +{% endtab %} +{% endtabs %} + +如代码中展示的,无需将这些定义放在 `package`, `class` 或其他构造中。 + +## 替换包对象 + +如果你熟悉Scala 2,这种方法可以取代 _包对象_。 +但是,虽然更易于使用,但它们的工作方式类似:当您将定义放在名为 _foo_ 的包中时,您可以在 _foo_ 包内的所有其他包内访问该定义,例如在此示例中的 _foo.bar_ 包中: + +{% tabs toplevel_2 %} +{% tab 'Scala 3 only' for=toplevel_2 %} + +```scala +package foo { + def double(i: Int) = i * 2 +} + +package foo { + package bar { + @main def fooBarMain = + println(s"${double(1)}") // this works + } +} +``` + +{% endtab %} +{% endtabs %} + +本示例中使用大括号来强调包嵌套。 + +这种方法的好处是,您可以将定义放在名为 _com.acme.myapp_ 的包下,然后可以在 _com.acme.myapp.model_、_com.acme.myapp.controller_ 等中引用这些定义。 diff --git a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md new file mode 100644 index 0000000000..2c5a80e5a0 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md @@ -0,0 +1,293 @@ +--- +title: 变量和数据类型 +type: section +description: This section demonstrates val and var variables, and some common Scala data types. +language: zh-cn +num: 7 +previous-page: taste-repl +next-page: taste-control-structures + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +本节介绍 Scala 变量和数据类型。 + +## 两种类型的变量 + +当你在 Scala 中创建一个新变量时,你声明该变量是不可变的还是可变的: + + + + + + + + + + + + + + + + + + +
变量类型说明
val创建一个不可变变量——类似于 Java 中的 final。 您应该始终使用 val 创建一个变量,除非有理由使用可变变量。
var创建一个可变变量,并且只应在变量的内容随时间变化时使用。
+ +这些示例展示了如何创建 `val` 和 `var` 变量: + +{% tabs var-express-1 %} +{% tab 'Scala 2 and 3' %} + +```scala +// immutable +val a = 0 + +// mutable +var b = 1 +``` + +{% endtab %} +{% endtabs %} + +在应用程序中,不能重新给一个 `val` 变量赋值。 +如果您尝试重新赋值一个 `val` 变量,将导致编译器错误: + +{% tabs var-express-2 %} +{% tab 'Scala 2 and 3' %} + +```scala +val msg = "Hello, world" +msg = "Aloha" // "reassignment to val" error; this won’t compile +``` + +{% endtab %} +{% endtabs %} + +相反,可以给 `var` 变量重新赋值: + +{% tabs var-express-3 %} +{% tab 'Scala 2 and 3' %} + +```scala +var msg = "Hello, world" +msg = "Aloha" // 因为可以重新分配 var,所以可以编译 +``` + +{% endtab %} +{% endtabs %} + +## 声明变量类型 + +创建变量时,您可以显式声明其类型,或让编译器推断类型: + +{% tabs var-express-4 %} +{% tab 'Scala 2 and 3' %} + +```scala +val x: Int = 1 // 显式 +val x = 1 // 隐式的;编译器推断类型 +``` + +{% endtab %} +{% endtabs %} + +第二种形式称为 _类型推断_,它是帮助保持此类代码简洁的好方法。 +Scala 编译器通常可以为您推断数据类型,如以下 REPL 示例的输出所示: + +{% tabs var-express-5 %} +{% tab 'Scala 2 and 3' %} + +```scala +scala> val x = 1 +val x: Int = 1 + +scala> val s = "a string" +val s: String = a string + +scala> val nums = List(1, 2, 3) +val nums: List[Int] = List(1, 2, 3) +``` + +{% endtab %} +{% endtabs %} + +如果您愿意,您始终可以显式声明变量的类型,但在像这样的简单赋值中,不须要这样: + +{% tabs var-express-6 %} +{% tab 'Scala 2 and 3' %} + +```scala +val x: Int = 1 +val s: String = "a string" +val p: Person = Person("Richard") +``` + +{% endtab %} +{% endtabs %} + +请注意,使用这种方法会感觉代码太啰嗦。 + +{% comment %} +TODO: Jonathan had an early comment on the text below: “While it might feel like this, I would be afraid that people automatically assume from this statement that everything is always boxed.” Suggestion on how to change this? +{% endcomment %} + +## 内置数据类型 + +Scala 带有你所期望的标准数值数据类型,它们都是类的成熟(full-blown)实例。 +在 Scala 中,一切都是对象。 + +这些示例展示了如何声明数值类型的变量: + +{% tabs var-express-7 %} +{% tab 'Scala 2 and 3' %} + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` + +{% endtab %} +{% endtabs %} + +因为 `Int` 和 `Double` 是默认的数字类型,所以您通常创建它们而不显式声明数据类型: + +{% tabs var-express-8 %} +{% tab 'Scala 2 and 3' %} + +```scala +val i = 123 // 默认为 Int +val j = 1.0 // 默认为 Double +``` + +{% endtab %} +{% endtabs %} + +在您的代码中,您还可以将字符 `L`、`D` 和 `F`(或者它们对应的小写字母)加到数字后面以指定它们是 `Long`、`Double` 或 `Float` 值: + +{% tabs var-express-9 %} +{% tab 'Scala 2 and 3' %} + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = 3.3F // val z: Float = 3.3 +``` + +{% endtab %} +{% endtabs %} + +当您需要非常大的数字时,请使用 `BigInt` 和 `BigDecimal` 类型: + +{% tabs var-express-10 %} +{% tab 'Scala 2 and 3' %} + +```scala +var a = BigInt(1_234_567_890_987_654_321L) +var b = BigDecimal(123_456.789) +``` + +{% endtab %} +{% endtabs %} + +其中 `Double` 和 `Float` 是近似十进制数,`BigDecimal` 用于精确算术。 + +Scala 还有 `String` 和 `Char` 数据类型: + +{% tabs var-express-11 %} +{% tab 'Scala 2 and 3' %} + +```scala +val name = "Bill" // String +val c = 'a' // Char +``` + +{% endtab %} +{% endtabs %} + +### 字符串 + +Scala 字符串类似于 Java 字符串,但它们有两个很棒的附加特性: + +- 他们支持字符串插值 +- 创建多行字符串很容易 + +#### 字符串插值 + +字符串插值提供了一种非常易读的方式在字符串中使用变量。 +例如,给定这三个变量: + +{% tabs var-express-12 %} +{% tab 'Scala 2 and 3' %} + +```scala +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +{% endtab %} +{% endtabs %} + +您可以将这些变量组合在一个字符串中,如下所示: + +{% tabs var-express-13 %} +{% tab 'Scala 2 and 3' %} + +```scala +println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" +``` + +{% endtab %} +{% endtabs %} + +只需在字符串前面加上字母 `s`,然后在字符串中的变量名之前放置一个 `$` 符号。 + +要将任意表达式嵌入字符串中,请将它们括在花括号中: + +{% tabs var-express-14 %} +{% tab 'Scala 2 and 3' %} + +``` scala +println(s"2 + 2 = ${2 + 2}") // 打印 "2 + 2 = 4" + +val x = -1 +println(s"x.abs = ${x.abs}") // 打印 "x.abs = 1" +``` + +{% endtab %} +{% endtabs %} + +放在字符串前面的 `s` 只是一种可能的插值器。 +如果使用 `f` 而不是 `s`,则可以在字符串中使用 `printf` 样式的格式化语法。 +此外,字符串插值器只是一种特殊方法,可以定义自己的方法。 +例如,有一些数据库方向的类库定义了非常强大的 `sql` 插值器。 + +#### 多行字符串 + +多行字符串是通过将字符串包含在三个双引号内来创建的: + +{% tabs var-express-15 %} +{% tab 'Scala 2 and 3' %} + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +{% endtab %} +{% endtabs %} + +> 有关字符串插值器和多行字符串的更多详细信息,请参阅[“类型初探”章节][first-look]。 + +[first-look]: {% link _zh-cn/overviews/scala3-book/first-look-at-types.md %} diff --git a/_zh-cn/overviews/scala3-book/tools-sbt.md b/_zh-cn/overviews/scala3-book/tools-sbt.md new file mode 100644 index 0000000000..2b1720c2c9 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/tools-sbt.md @@ -0,0 +1,496 @@ +--- +title: 使用 sbt 构建和测试 Scala 项目 +type: section +description: This section looks at a commonly-used build tool, sbt, and a testing library, ScalaTest. +language: zh-cn +num: 70 +previous-page: scala-tools +next-page: tools-worksheets + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +在本节中,您将看到 Scala 项目中常用的两个工具: + +- [sbt](https://www.scala-sbt.org) 构建工具 +- [ScalaTest](https://www.scalatest.org),一个源代码测试框架 + +我们将首先展示如何使用 sbt 构建您的 Scala 项目,然后我们将展示如何一起使用 sbt 和 ScalaTest 来测试您的 Scala 项目。 + +> 如果您想了解帮助您将 Scala 2 代码迁移到 Scala 3 的工具,请参阅我们的 [Scala 3 迁移指南](/scala3/guides/migration/compatibility-intro.html)。 + +## 使用 sbt 构建 Scala 项目 + +您可以使用多种不同的工具来构建您的 Scala 项目,包括 Ant、Maven、Gradle、Mill 等。 +但是名为 _sbt_ 的工具是第一个专门为 Scala 创建的构建工具。 + +> 要安装 sbt,请参阅 [其下载页面](https://www.scala-sbt.org/download.html) 或我们的 [Getting Started][getting_started] 页面。 + +### 创建一个 “Hello, world” 项目 + +只需几个步骤,您就可以创建一个 sbt “Hello, world” 项目。 +首先,创建一个工作目录,然后进入该目录: + +```bash +$ mkdir hello +$ cd hello +``` + +在 `hello` 目录下,创建一个子目录 `project`: + +```bash +$ mkdir project +``` + +在 `project` 目录中创建一个名为 _build.properties_ 的文件,其中 +以下内容: + +```text +sbt.version=1.10.11 +``` + +然后在包含此行的项目根目录中创建一个名为 _build.sbt_ 的文件: + +```scala +scalaVersion := "{{ site.scala-3-version }}" +``` + +现在创建一个名为 _Hello.scala_ 的文件——名称的第一部分无关紧要——使用这一行: + +```scala +@main def helloWorld = println("Hello, world") +``` + +这就是你所要做的。 + +您应该具有如下的项目结构: + +~~~ bash +$ tree +. +├── build.sbt +├── Hello.scala +└── project + └── build.properties +~~~ + +现在使用 `sbt` 命令运行项目: + +```bash +$ sbt run +``` + +您应该会看到如下所示的输出,包括程序中的 `"Hello, world"`: + +```bash +$ sbt run +[info] welcome to sbt 1.5.4 (AdoptOpenJDK Java 11.x) +[info] loading project definition from project ... +[info] loading settings for project from build.sbt ... +[info] compiling 1 Scala source to target/scala-3.0.0/classes ... +[info] running helloWorld +Hello, world +[success] Total time: 2 s +``` + +sbt 启动器——`sbt` 命令行工具——加载文件 _project/build.properties_ 中设置的 sbt 版本,它加载文件 _build.sbt_ 中设置的 Scala 编译器版本,编译 _Hello.scala_ 文件中的代码,并运行生成的字节码。 + +当你查看你的目录时,你会看到 sbt 有一个名为 _target_ 的目录。 +这些是 sbt 使用的工作目录。 + +如您所见,使用 sbt 创建和运行一个小的 Scala 项目只需要几个简单的步骤。 + +### 在大型项目中使用 sbt + +对于一个小项目,这就是 sbt 运行所需的全部内容。 +对于需要许多源代码文件、依赖项或 sbt 插件的大型项目,您需要创建一个有组织的目录结构。 +本节的其余部分演示了 sbt 使用的结构。 + +### sbt 目录结构 + +与 Maven 一样,sbt 使用标准的项目目录结构。 +这样做的一个很好的好处是,一旦你对它的结构感到满意,它就可以很容易地处理其他 Scala/sbt 项目。 + +首先要知道的是,在项目的根目录下,sbt 需要一个如下所示的目录结构: + +```text +. +├── build.sbt +├── project/ +│ └── build.properties +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ ├── resources/ +│ │ └── scala/ +│ └── test/ +│ ├── java/ +│ ├── resources/ +│ └── scala/ +└── target/ +``` + +如果您想将非托管依赖项---JAR 文件---添加到您的项目中,您还可以在根目录下添加一个 _lib_ 目录。 + +如果您要创建一个包含 Scala 源代码文件和测试的项目,但不会使用任何 Java 源代码文件,并且不需要任何“资源”——例如嵌入式图像、配置文件、等等---这就是你在_src_目录下真正需要的: + +```text +. +└── src/ + ├── main/ + │ └── scala/ + └── test/ + └── scala/ +``` + +### 带有 sbt 目录结构的 “Hello, world” + +{% comment %} +LATER: using something like `sbt new scala/scala3.g8` may eventually + be preferable, but that seems to have a few bugs atm (creates + a 'target' directory above the root; renames the root dir; + uses 'dottyVersion'; 'name' doesn’t match the supplied name; + config syntax is a little hard for beginners.) +{% endcomment %} + +创建这个目录结构很简单。 +有一些工具可以为你做到这一点,但假设你使用的是 Unix/Linux 系统,你可以使用这些命令来创建你的第一个 sbt 项目目录结构: + +```bash +$ mkdir HelloWorld +$ cd HelloWorld +$ mkdir -p src/{main,test}/scala +$ mkdir project target +``` + +在运行这些命令后运行 `find .` 命令时,您应该会看到以下结果: + +```bash +$ find . +. +./project +./src +./src/main +./src/main/scala +./src/test +./src/test/scala +./target +``` + +如果你看到上面那样,那么没有问题,可以进行下一步了。 + +> 还有其他方法可以为 sbt 项目创建文件和目录。 +> 一种方法是使用 `sbt new` 命令,[在 scala-sbt.org 上有文档](https://www.scala-sbt.org/1.x/docs/Hello.html)。 +> 该方法未在此处显示,因为它创建的某些文件比像这样的介绍所必需的要复杂。 + +### 创建第一个 build.sbt 文件 + +此时,您只需要另外两件事来运行 “Hello, world” 项目: + +- 一个 _build.sbt_ 文件 +- 一个 _Hello.scala_ 文件 + +对于像这样的小项目,_build.sbt_ 文件只需要一个 `scalaVersion` 条目,但我们将添加您通常看到的三行: + +```scala +name := "HelloWorld" +version := "0.1" +scalaVersion := "{{ site.scala-3-version }}" +``` + +因为 sbt 项目使用标准的目录结构,所以 sbt 可以找到它需要的所有其他内容。 + +现在你只需要添加一个小小的“Hello, world”程序。 + +### “Hello, world” 程序 + +在大型项目中,您所有的 Scala 源代码文件都将放在 _src/main/scala_ 和 _src/test/scala_ 目录下,但是对于像这样的小示例项目,您可以将源代码文件放在您项目的根目录下。 +因此,在根目录中创建一个名为 _HelloWorld.scala_ 的文件,其中包含以下内容: + +```scala +@main def helloWorld = println("Hello, world") +``` + +该代码定义了一个 Scala 3 “main” 方法,该方法在运行时打印 `"Hello, world"`。 + +现在,使用 `sbt run` 命令编译并运行您的项目: + +```bash +$ sbt run + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition +[info] loading settings for project root from build.sbt ... +[info] Compiling 1 Scala source ... +[info] running helloWorld +Hello, world +[success] Total time: 4 s +``` + +第一次运行 `sbt` 时,它会下载所需的所有内容,这可能需要一些时间才能运行,但之后它会变得更快。 + +此外,一旦你完成了这第一步,你会发现以交互方式运行 sbt 会快得多。 +为此,首先单独运行 `sbt` 命令: + +```bash +$ sbt + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition ... +[info] loading settings for project root from build.sbt ... +[info] sbt server started at + local:///${HOME}/.sbt/1.0/server/7d26bae822c36a31071c/sock +sbt:hello-world> _ +``` + +然后在这个 sbt shell 中,执行它的 `run` 命令: + +```` +sbt:hello-world> run + +[info] running helloWorld +Hello, world +[success] Total time: 0 s +```` + +这要快得多。 + +如果您在 sbt 命令提示符下键入 `help`,您将看到可以运行的其他命令的列表。 +但现在,只需键入 `exit`(或按 `CTRL-D`)离开 sbt shell。 + +### 使用项目模板 + +手动创建项目结构可能很乏味。谢天谢地,sbt 可以基于模板为你创建项目。 + +要从模板创建 Scala 3 项目,请在 shell 中运行以下命令: + +~~~ +$ sbt new scala/scala3.g8 +~~~ + +Sbt 将加载模板,提出一些问题,并在子目录中创建项目文件: + +~~~ +$ tree scala-3-project-template +scala-3-project-template +├── build.sbt +├── project +│ └── build.properties +├── README.md +└── src + ├── main + │ └── scala + │ └── Main.scala + └── test + └── scala + └── Test1.scala +~~~ + +> 如果要创建与 Scala 2 交叉编译的 Scala 3 项目,请使用模板 `scala/scala3-cross.g8`: +> +> ~~~ +> $ sbt new scala/scala3-cross.g8 +> ~~~ + +在 [sbt 文档](https://www.scala-sbt.org/1.x/docs/sbt-new-and-Templates.html#sbt+new+) 中了解有关 `sbt new` 和项目模板的更多信息。 + +### Scala 的其他构建工具 + +虽然 sbt 被广泛使用,但您还可以使用其他工具来构建 Scala 项目: + +- [ant](https://ant.apache.org/) +- [Gradle](https://gradle.org/) +- [Maven](https://maven.apache.org/) +- [mill](https://com-lihaoyi.github.io/mill/) + +#### Coursier + +在相关说明中,[Coursier](https://get-coursier.io/docs/overview) 是一个“依赖解析器”,在功能上类似于 Maven 和 Ivy。 +它是用 Scala 从头开始编写的,“包含函数式编程原则”,并且可以并行下载工件以实现快速下载。 +sbt 使用它来处理大多数依赖关系解析,并且作为一个命令行工具,它可以用于在您的系统上轻松安装 sbt、Java 和 Scala 等工具,如我们的 [Getting Started][getting_started] 页面所示。 + +来自 `launch` 网页的这个示例显示了 `cs launch` 命令可用于从依赖项启动应用程序: + +```scala +$ cs launch org.scalameta::scalafmt-cli:2.4.2 -- --help +scalafmt 2.4.2 +Usage: scalafmt [options] [...] + + -h, --help prints this usage text + -v, --version print version + more ... +``` + +有关详细信息,请参阅 Coursier 的 [启动页面](https://get-coursier.io/docs/cli-launch)。 + +## 使用 sbt 和 ScalaTest + +[ScalaTest](https://www.scalatest.org) 是 Scala 项目的主要测试库之一。 +在本节中,您将看到创建使用 ScalaTest 的 Scala/sbt 项目所需的步骤。 + +### 1) 创建项目目录结构 + +与上一课一样,使用以下命令为名为 _HelloScalaTest_ 的项目创建一个 sbt 项目目录结构: + +```bash +$ mkdir HelloScalaTest +$ cd HelloScalaTest +$ mkdir -p src/{main,test}/scala +$ mkdir project +``` + +### 2) 创建 build.properties 和 build.sbt 文件 + +接下来,把下面这行代码用于在项目的 _project/_ 子目录中创建一个 _build.properties_ 文件: + +```text +sbt.version=1.10.11 +``` + +接下来,在项目的根目录中创建一个 _build.sbt_ 文件,其中包含以下内容: + +```scala +name := "HelloScalaTest" +version := "0.1" +scalaVersion := "{{site.scala-3-version}}" + +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.19" % Test +) +``` + +该文件的前三行与第一个示例基本相同。 +`libraryDependencies` 行告诉 sbt 包含包含 ScalaTest 所需的依赖项(JAR 文件)。 + +> ScalaTest 文档一直很优秀,您始终可以在 [安装 ScalaTest](https://www.scalatest.org/install) 页面上找到有关这些行应该是什么样子的最新信息。 + +### 3) 创建一个 Scala 源代码文件 + +接下来,创建一个可用于演示 ScalaTest 的 Scala 程序。 +首先,在 _src/main/scala_ 下创建一个名为 _math_ 的目录: + +```bash +$ mkdir src/main/scala/math + ---- +``` + +然后,在该目录中,使用以下内容创建一个名为 _MathUtils.scala_ 的文件: + +```scala +package math + +object MathUtils: + def double(i: Int) = i * 2 +``` + +该方法提供了一种演示 ScalaTest 的简单方法。 + +{% comment %} +Because this project doesn’t have a `main` method, we don’t try to run it with `sbt run`; we just compile it with `sbt compile`: + +```` +$ sbt compile + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition ... +[info] loading settings for project ... +[info] Executing in batch mode. For better performance use sbt's shell +[success] Total time: 1 s +```` + +With that compiled, let’s create a ScalaTest file to test the `double` method. +{% endcomment %} + +### 4) 创建你的第一个 ScalaTest 测试 + +ScalaTest 非常灵活,并提供了几种不同的方式来编写测试。 +一个简单的入门方法是使用 ScalaTest `AnyFunSuite` 编写测试。 +首先,在 _src/test/scala_ 目录下创建一个名为 _math_ 的目录: + +```bash +$ mkdir src/test/scala/math + ---- +``` + +接下来,在该目录中创建一个名为 _MathUtilsTests.scala_ 的文件,其内容如下: + +```scala +package math + +import org.scalatest.funsuite.AnyFunSuite + +class MathUtilsTests extends AnyFunSuite: + + // test 1 + test("'double' should handle 0") { + val result = MathUtils.double(0) + assert(result == 0) + } + + // test 2 + test("'double' should handle 1") { + val result = MathUtils.double(1) + assert(result == 2) + } + + test("test with Int.MaxValue") (pending) + +end MathUtilsTests +``` + +此代码演示了 ScalaTest `AnyFunSuite` 方法。 +几个重要的点: + +- 你的测试类应该继承 `AnyFunSuite` +- 如图所示,您可以通过为每个 `test` 指定一个唯一的名称来创建测试 +- 在每个测试结束时,您应该调用 `assert` 来测试条件是否已满足 +- 当你知道你想写一个测试,但你现在不想写它时,将测试创建为“待定”,语法如上例所示 + +像这样使用 ScalaTest 类似于 JUnit,所以如果你是从 Java 转到 Scala 的,希望这看起来相似。 + +现在您可以使用 `sbt test` 命令运行这些测试。 +跳过前几行输出,结果如下所示: + +```` +sbt:HelloScalaTest> test + +[info] Compiling 1 Scala source ... +[info] MathUtilsTests: +[info] - 'double' should handle 0 +[info] - 'double' should handle 1 +[info] - test with Int.MaxValue (pending) +[info] Total number of tests run: 2 +[info] Suites: completed 1, aborted 0 +[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1 +[info] All tests passed. +[success] Total time: 1 s +```` + +如果一切正常,您将看到类似的输出。 +欢迎来到使用 sbt 和 ScalaTest 测试 Scala 应用程序的世界。 + +### 支持多种类型的测试 + +此示例演示了一种类似于 xUnit _测试驱动开发_(TDD) 样式测试的测试样式,并具有_行为驱动开发_(BDD) 样式的一些优点。 + +如前所述,ScalaTest 很灵活,您还可以用其它风格来编写测试,例如类似于 Ruby 的 RSpec 的风格。 +您还可以使用伪对象、基于属性的测试,并使用 ScalaTest 来测试 Scala.js 代码。 + +有关可用的不同测试风格的更多详细信息,请参阅 [ScalaTest 网站](https://www.scalatest.org) 上的用户指南。 + +## 从这往哪儿走 + +有关 sbt 和 ScalaTest 的更多信息,请参阅以下资源: + +- [sbt 文档](https://www.scala-sbt.org/1.x/docs/) +- [ScalaTest 网站](https://www.scalatest.org/) + + +[getting_started]: {{ site.baseurl }}/scala3/getting-started.html diff --git a/_zh-cn/overviews/scala3-book/tools-worksheets.md b/_zh-cn/overviews/scala3-book/tools-worksheets.md new file mode 100644 index 0000000000..214b744d8b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/tools-worksheets.md @@ -0,0 +1,65 @@ +--- +title: worksheet +type: section +description: This section looks at worksheets, an alternative to Scala projects. +language: zh-cn +num: 71 +previous-page: tools-sbt +next-page: interacting-with-java + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +工作表是在保存时评估的 Scala 文件,并把每个表达式的结果 +显示在程序右侧的列中。工作表就像是加了激素的[REPL 会话][REPL session],并且 +享受一流的编辑器支持:自动补全、超链接、交互式错误输入等。 +工作表使用扩展名 `.worksheet.sc` 。 + +下面,我们将展示如何在 IntelliJ 和 VS Code(带有 Metals 扩展)中使用工作表。 + +1. 打开一个 Scala 项目,或者创建一个。 + - 要在 IntelliJ 中创建项目,选择“File”->“New”->“Project...”, 在左侧栏中选择“Scala”, + 单击“下一步”设置项目名称和位置。 + - 要在 VS Code 中创建项目,请运行命令“Metals: New Scala project”,选择 + 种子 `scala/scala3.g8`,设置项目位置,在新的 VS Code 窗口中打开它,然后 + 导入其构建。 +1. 在 `src/main/scala/` 目录下创建一个名为 `hello.worksheet.sc` 的文件。 + - 在 IntelliJ 中,右键单击目录 `src/main/scala/`,然后选择“New”,然后 + 是“文件”。 + - 在 VS Code 中,右键单击目录`src/main/scala/`,然后选择“New File”。 +1. 在编辑器中粘贴以下内容: + ~~~ + println("Hello, world!") + + val x = 1 + x + x + ~~~ + +1. 评估工作表。 + - 在 IntelliJ 中,单击编辑器顶部的绿色箭头以评估工作表。 + - 在 VS Code 中,保存文件。 + + 您应该在右侧面板 (IntelliJ) 上看到每一行的评估结果,或者 + 作为注释(VS Code)。 + +![]({{ site.baseurl }}/resources/images/scala3-book/intellij-worksheet.png) + +在 IntelliJ 中评估的工作表。 + +![]({{ site.baseurl }}/resources/images/scala3-book/metals-worksheet.png) + +在 VS Code 中评估的工作表(带有 Metals 扩展)。 + +请注意,工作表将使用项目定义的 Scala 版本(通常在文件`build.sbt`中, +设置 `scalaVersion` 键)。 + +另请注意,工作表没有 [程序入口点][program entry point]。作为替代,顶级语句和表达式 +从上到下进行评估。 + + +[REPL session]: {% link _zh-cn/overviews/scala3-book/taste-repl.md %} +[program entry point]: {% link _zh-cn/overviews/scala3-book/methods-main-methods.md %} diff --git a/_zh-cn/overviews/scala3-book/types-adts-gadts.md b/_zh-cn/overviews/scala3-book/types-adts-gadts.md new file mode 100644 index 0000000000..4d1604e187 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-adts-gadts.md @@ -0,0 +1,213 @@ +--- +title: 代数数据类型 +type: section +description: This section introduces and demonstrates algebraic data types (ADTs) in Scala 3. +language: zh-cn +num: 53 +previous-page: types-union +next-page: types-variance + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +代数数据类型 (ADT) 可以使用 `enum` 构造创建,因此我们将在查看 ADT 之前简要回顾一下枚举。 + +## 枚举 + +_enumeration_ 用于定义由一组命名值组成的类型: + +```scala +enum Color: + case Red, Green, Blue +``` + +这可以看作是以下的简写: + +```scala +enum Color: + case Red extends Color + case Green extends Color + case Blue extends Color +``` + +#### 参数 + +枚举可以参数化: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +这样,每个不同的变体都有一个值成员 `rgb`,它被分配了相应的值: + +```scala +println(Color.Green.rgb) // prints 65280 +``` + +#### 自定义 + +枚举也可以有自定义: + +```scala +enum Planet(mass: Double, radius: Double): + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // 5 or 6 more planets ... +``` + +像类和 `case` 类一样,你也可以为枚举定义一个伴生对象: + +```scala +object Planet: + def main(args: Array[String]) = + val earthWeight = args(0).toDouble + val mass = earthWeight / Earth.surfaceGravity + for (p <- values) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") +``` + +## 代数数据类型 (ADTs) + +`enum` 概念足够通用,既支持_代数数据类型_(ADT)和它的通用版本(GADT)。 +本示例展示了如何将 `Option` 类型表示为 ADT: + +```scala +enum Option[+T]: + case Some(x: T) + case None +``` + +这个例子创建了一个带有协变类型参数 `T` 的 `Option` 枚举,它由两种情况组成, `Some` 和 `None`。 +`Some` 是_参数化_的,它带有值参数 `x`;它是从 `Option` 继承的 `case` 类的简写。 +由于 `None` 没有参数化,它被视为普通的 `enum` 值。 + +前面示例中省略的 `extends` 子句也可以显式给出: + +```scala +enum Option[+T]: + case Some(x: T) extends Option[T] + case None extends Option[Nothing] +``` + +与普通的 `enum` 值一样,`enum` 的情况是在 `enum` 的伴生对象中定义的,因此它们被引用为 `Option.Some` 和 `Option.None`(除非定义是在导入时单独列出): + +```scala +scala> Option.Some("hello") +val res1: t2.Option[String] = Some(hello) + +scala> Option.None +val res2: t2.Option[Nothing] = None +``` + +与其他枚举用途一样,ADT 可以定义更多的方法。 +例如,这里又是一个 `Option`,它的伴生对象中有一个 `isDefined` 方法和一个 `Option(...)` 构造函数: + +```scala +enum Option[+T]: + case Some(x: T) + case None + + def isDefined: Boolean = this match + case None => false + case Some(_) => true + +object Option: + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) +``` + +枚举和 ADT 共享相同的句法结构,因此它们可以 +被简单地视为光谱的两端,把二者混搭是完全可能的。 +例如,下面的代码给出了一个 +`Color` 的实现,可以使用三个枚举值或使用 +RGB 值的参数化情况: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +``` + +#### 递归枚举 + +到目前为止,我们定义的所有枚举都由值或样例类的不同变体组成。 +枚举也可以是递归的,如下面的自然数编码示例所示: + +```scala +enum Nat: + case Zero + case Succ(n: Nat) +``` + +例如,值 `Succ(Succ(Zero))` 表示一元编码中的数字 `2`。 +列表可以以非常相似的方式定义: + +```scala +enum List[+A]: + case Nil + case Cons(head: A, tail: List[A]) +``` + +## 广义代数数据类型 (GADT) + +上面的枚举表示法非常简洁,可以作为建模数据类型的完美起点。 +由于我们总是可以更明确,因此也可以表达更强大的类型:广义代数数据类型 (GADT)。 + +这是一个 GADT 示例,其中类型参数 (`T`) 指定存储在框中的内容: + +```scala +enum Box[T](contents: T): + case IntBox(n: Int) extends Box[Int](n) + case BoolBox(b: Boolean) extends Box[Boolean](b) +``` + +特定构造函数(`IntBox` 或 `BoolBox`)上的模式匹配可恢复类型信息: + +```scala +def extract[T](b: Box[T]): T = b match + case IntBox(n) => n + 1 + case BoolBox(b) => !b +``` + +只有在第一种情况下返回一个 `Int` 才是安全的,因为我们从 pattern 匹配输入是一个“IntBox”。 + +## 去除语法糖的枚举 + +_从概念上讲_,枚举可以被认为是定义一个密封类及其伴生对象。 +让我们看看上面的 `Color` 枚举的无语法糖版本: + +```scala +sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum +object Color: + case object Red extends Color(0xFF0000) { def ordinal = 0 } + case object Green extends Color(0x00FF00) { def ordinal = 1 } + case object Blue extends Color(0x0000FF) { def ordinal = 2 } + case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 } + + def fromOrdinal(ordinal: Int): Color = ordinal match + case 0 => Red + case 1 => Green + case 2 => Blue + case _ => throw new NoSuchElementException(ordinal.toString) +``` + +请注意,上面的去除语法糖被简化了,我们故意省略了[一些细节][desugar-enums]。 + +虽然枚举可以使用其他构造手动编码,但使用枚举更简洁,并且还附带了一些额外的实用程序(例如 `fromOrdinal` 方法)。 + +[desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html diff --git a/_zh-cn/overviews/scala3-book/types-dependent-function.md b/_zh-cn/overviews/scala3-book/types-dependent-function.md new file mode 100644 index 0000000000..37084f1647 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-dependent-function.md @@ -0,0 +1,175 @@ +--- +title: 依赖函数类型 +type: section +description: This section introduces and demonstrates dependent function types in Scala 3. +language: zh-cn +num: 57 +previous-page: types-structural +next-page: types-others + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +*依赖函数类型*描述函数类型,其中结果类型可能取决于函数的参数值。 +依赖类型和依赖函数类型的概念更高级,您通常只会在设计自己的库或使用高级库时遇到它。 + +## 依赖方法类型 + +让我们考虑以下可以存储不同类型值的异构数据库示例。 +键包含有关相应值的类型的信息: + +```scala +trait Key { type Value } + +trait DB { + def get(k: Key): Option[k.Value] // a dependent method +} +``` + +给定一个键,`get` 方法允许我们访问地图并可能返回类型为 `k.Value` 的存储值。 +我们可以将这个_路径依赖类型_解读为:“根据参数 `k` 的具体类型,我们返回一个匹配值”。 + +例如,我们可以有以下键: + +```scala +object Name extends Key { type Value = String } +object Age extends Key { type Value = Int } +``` + +以下对方法 `get` 的调用现在将键入检查: + +```scala +val db: DB = ... +val res1: Option[String] = db.get(Name) +val res2: Option[Int] = db.get(Age) +``` + +调用方法 `db.get(Name)` 返回一个 `Option[String]` 类型的值,而调用 `db.get(Age)` 返回一个 `Option[Int]` 类型的值。 +返回类型_依赖_于传递给 `get` 的参数的具体类型---因此名称为_依赖类型_。 + +## 依赖函数类型 + +如上所示,Scala 2 已经支持依赖方法类型。 +但是,创建 `DB` 类型的值非常麻烦: + +```scala +// a user of a DB +def user(db: DB): Unit = + db.get(Name) ... db.get(Age) + +// creating an instance of the DB and passing it to `user` +user(new DB { + def get(k: Key): Option[k.Value] = ... // implementation of DB +}) +``` + +我们需要手动创建一个匿名的 `DB` 内部类,实现 `get` 方法。 +对于依赖于创建许多不同的 `DB` 实例的代码,这是非常乏味的。 + + `DB` 只有一个抽象方法 `get` 。 +如果我们可以使用 lambda 语法,那不是很好吗? + +```scala +user { k => + ... // implementation of DB +``` + +事实上,现在这在 Scala 3 中是可能的!我们可以将 `DB` 定义为_依赖函数类型_: + +```scala +type DB = (k: Key) => Option[k.Value] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// A dependent function type +``` + +鉴于 `DB` 的这个定义,上面对 `user` 类型的调用按原样检查。 + +您可以在 [参考文档][ref] 中阅读有关依赖函数类型内部结构的更多信息。 + +## 案例研究:数值表达式 + +假设我们要定义一个抽象数字内部表示的模块。 +例如,这对于实现用于自动派生的库很有用。 + +我们首先为数字定义我们的模块: + +```scala +trait Nums: + // the type of numbers is left abstract + type Num + + // some operations on numbers + def lit(d: Double): Num + def add(l: Num, r: Num): Num + def mul(l: Num, r: Num): Num +``` + +> 我们省略了 `Nums` 的具体实现,但作为练习,您可以通过分配 `type Num = Double` 来实现 `Nums` 并相应地实现方法。 + +使用我们的数字抽象的程序现在具有以下类型: + +```scala +type Prog = (n: Nums) => n.Num => n.Num + +val ex: Prog = nums => x => nums.add(nums.lit(0.8), x) +``` + +计算诸如 `ex` 之类的程序的导数的函数的类型是: + +```scala +def derivative(input: Prog): Double +``` + +鉴于依赖函数类型的便利,用不同的程序调用这个函数非常方便: + +```scala +derivative { nums => x => x } +derivative { nums => x => nums.add(nums.lit(0.8), x) } +// ... +``` + +回想一下,上面编码中的相同程序将是: + +```scala +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = x +}) +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x) +}) +// ... +``` + +#### 上下文组合函数 + +扩展方法、[上下文函数][ctx-fun]和依赖函数的组合为库设计者提供了强大的工具。 +例如,我们可以从上面优化我们的库,如下所示: + +```scala +trait NumsDSL extends Nums: + extension (x: Num) + def +(y: Num) = add(x, y) + def *(y: Num) = mul(x, y) + +def const(d: Double)(using n: Nums): n.Num = n.lit(d) + +type Prog = (n: NumsDSL) ?=> n.Num => n.Num +// ^^^ +// prog is now a context function that implicitly +// assumes a NumsDSL in the calling context + +def derivative(input: Prog): Double = ... + +// notice how we do not need to mention Nums in the examples below? +derivative { x => const(1.0) + x } +derivative { x => x * x + const(2.0) } +// ... +``` + + +[ref]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[ctx-fun]: {{ site.scala3ref }}/contextual/context-functions.html diff --git a/_zh-cn/overviews/scala3-book/types-generics.md b/_zh-cn/overviews/scala3-book/types-generics.md new file mode 100644 index 0000000000..cdea2a2c4c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-generics.md @@ -0,0 +1,91 @@ +--- +title: 泛型 +type: section +description: This section introduces and demonstrates generics in Scala 3. +language: zh-cn +num: 50 +previous-page: types-inferred +next-page: types-intersection + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +泛型类(或 traits)把在方括号 `[...]` 中的类型作为_参数_进行调用。 +Scala 约定是使用单个字母(如 `A`)来命名这些类型参数。 +然后当需要时,该类型可以在类中用于方法实例参数,或返回类型: + +{% tabs stack class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +// here we declare the type parameter A +// v +class Stack[A] { + private var elements: List[A] = Nil + // ^ + // Here we refer to the type parameter + // v + def push(x: A): Unit = + elements = elements.prepended(x) + def peek: A = elements.head + def pop(): A = { + val currentTop = peek + elements = elements.tail + currentTop + } +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +// here we declare the type parameter A +// v +class Stack[A]: + private var elements: List[A] = Nil + // ^ + // Here we refer to the type parameter + // v + def push(x: A): Unit = { elements = elements.prepended(x) } + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` +{% endtab %} +{% endtabs %} + +`Stack` 类的这个实现采用任何类型作为参数。 +泛型的美妙之处在于您现在可以创建一个 `Stack[Int]`、`Stack[String]` 等,允许您将 `Stack` 的实现重复用于任意元素类型。 + +这是创建和使用 `Stack[Int]` 的方式: + +{% tabs stack-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val stack = new Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 +``` +{% endtab %} +{% endtabs %} + +> 有关如何用泛型类型表达可变的详细信息,请参阅[型变(Variance)部分][variance]。 + +[variance]: {% link _zh-cn/overviews/scala3-book/types-variance.md %} diff --git a/_zh-cn/overviews/scala3-book/types-inferred.md b/_zh-cn/overviews/scala3-book/types-inferred.md new file mode 100644 index 0000000000..aa6d3faf18 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-inferred.md @@ -0,0 +1,58 @@ +--- +title: 类型推断 +type: section +description: This section introduces and demonstrates inferred types in Scala 3 +language: zh-cn +num: 49 +previous-page: types-introduction +next-page: types-generics + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +与其他静态类型编程语言一样,在 Scala 中,您可以在创建新变量时_声明_类型: + +{% tabs xy %} +{% tab 'Scala 2 and 3' %} +```scala +val x: Int = 1 +val y: Double = 1 +``` +{% endtab %} +{% endtabs %} + +在这些示例中,类型分别_明确地_声明为 `Int` 和 `Double` 。 +但是,在 Scala 中,您通常不必在定义值绑定器时声明类型: + +{% tabs abm %} +{% tab 'Scala 2 and 3' %} +```scala +val a = 1 +val b = List(1, 2, 3) +val m = Map(1 -> "one", 2 -> "two") +``` +{% endtab %} +{% endtabs %} + +当你这样做时,Scala _推断_类型,如下面的 REPL 交互所示: + +{% tabs abm2 %} +{% tab 'Scala 2 and 3' %} +```scala +scala> val a = 1 +val a: Int = 1 + +scala> val b = List(1, 2, 3) +val b: List[Int] = List(1, 2, 3) + +scala> val m = Map(1 -> "one", 2 -> "two") +val m: Map[Int, String] = Map(1 -> one, 2 -> two) +``` +{% endtab %} +{% endtabs %} + +事实上,大多数变量都是这样定义的,而 Scala 自动推断类型的能力是使它_感觉_像一种动态类型语言的一个特性。 diff --git a/_zh-cn/overviews/scala3-book/types-intersection.md b/_zh-cn/overviews/scala3-book/types-intersection.md new file mode 100644 index 0000000000..db1d250a4c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-intersection.md @@ -0,0 +1,64 @@ +--- +title: 相交类型 +type: section +description: This section introduces and demonstrates intersection types in Scala 3. +language: zh-cn +num: 51 +previous-page: types-generics +next-page: types-union + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- +Scala 3 only + +用于类型,`&` 运算符创建一个所谓的_相交类型_。 +`A & B` 类型表示同时是 `A` 类型和 `B` 类型**两者**的值。 +例如,以下示例使用相交类型 `Resettable & Growable[String]`: + +{% tabs intersection-reset-grow %} +{% tab 'Scala 3 Only' %} +```scala +trait Resettable: + def reset(): Unit + +trait Growable[A]: + def add(a: A): Unit + +def f(x: Resettable & Growable[String]): Unit = + x.reset() + x.add("first") +``` +{% endtab %} +{% endtabs %} + +在本例中的方法 `f` 中,参数 `x` 必须*同时*既是 `Resettable` 也是 `Growable[String]`。 + +相交类型 `A & B` 的_成员_既有 `A` 的所有成员,也有 `B` 的所有成员。 +因此,如图所示,`Resettable & Growable[String]` 具有成员方法 `reset` 和 `add`。 + +相交类型可用于_结构性_地描述需求。 +也就是说,在我们的示例 `f` 中,我们直接表示只要 `x` 是 `Resettable` 和 `Growable` 的子类型的任意值, 我们就感到满意。 +我们**不**需要创建一个_通用_的辅助 trait,如下所示: + +{% tabs normal-trait class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait Both[A] extends Resettable with Growable[A] +def f(x: Both[String]): Unit +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +trait Both[A] extends Resettable, Growable[A] +def f(x: Both[String]): Unit +``` +{% endtab %} +{% endtabs %} + +定义 `f` 的两种选择之间有一个重要区别:虽然两者都允许使用 `Both` 的实例调用 `f`,但只有前者允许传递属于 `Resettable` 和 `Growable[String]` 子类型的实例,后者 `Both[String]` _不允许_。 + +> 请注意,`&` 是_可交换的_:`A & B` 与 `B & A` 的类型相同。 diff --git a/_zh-cn/overviews/scala3-book/types-introduction.md b/_zh-cn/overviews/scala3-book/types-introduction.md new file mode 100644 index 0000000000..dfe1b6b790 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-introduction.md @@ -0,0 +1,61 @@ +--- +title: 类型和类型系统 +type: chapter +description: This chapter provides an introduction to Scala 3 types and the type system. +language: zh-cn +num: 48 +previous-page: fp-summary +next-page: types-inferred + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 是一种独特的语言,因为它是静态类型的,但通常_感觉_它灵活和动态。 +例如,由于类型推断,您可以编写这样的代码而无需显式指定变量类型: + +{% tabs hi %} +{% tab 'Scala 2 and 3' %} +```scala +val a = 1 +val b = 2.0 +val c = "Hi!" +``` +{% endtab %} +{% endtabs %} + +这使代码感觉是动态类型的。 +并且由于新特性,例如 Scala 3 中的 [联合类型][union-types],您还可以编写如下代码,非常简洁地表达出期望哪些值作为参数,哪些值作为返回的类型: + +{% tabs union-example %} +{% tab 'Scala 3 Only' %} +```scala +def isTruthy(a: Boolean | Int | String): Boolean = ??? +def dogCatOrWhatever(): Dog | Plant | Car | Sun = ??? +``` +{% endtab %} +{% endtabs %} + +正如例子所暗示的,当使用联合类型时,这些类型不必共享一个公共层次结构,您仍然可以接受它们作为参数或从方法中返回它们。 + +如果您是应用程序开发人员,您将每天使用类型推断和每周使用泛型等功能。 +当您阅读 Scaladoc 中的类和方法时,您还需要对_可变的(variance)_有所了解。 +希望您会发现使用类型可以相当简单,而且使用类型可以为库开发人员提供了很多表达能力、灵活性和控制力。 + +## 类型的好处 + +静态类型的编程语言提供了许多好处,包括: + +- 帮助提供强大的 IDE 支持 +- 在编译时消除许多类的潜在错误 +- 协助重构 +- 提供强大的文档,因为它经过类型检查,所以不会过时 + +## Scala 类型系统的特性介绍 + +鉴于此简要介绍,以下部分将概述 Scala 类型系统的特性。 + +[union-types]: {% link _zh-cn/overviews/scala3-book/types-union.md %} diff --git a/_zh-cn/overviews/scala3-book/types-opaque-types.md b/_zh-cn/overviews/scala3-book/types-opaque-types.md new file mode 100644 index 0000000000..c8ba8405f5 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-opaque-types.md @@ -0,0 +1,160 @@ +--- +title: 不透明类型 +type: section +description: This section introduces and demonstrates opaque types in Scala 3. +language: zh-cn +num: 55 +previous-page: types-variance +next-page: types-structural + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala 3 _不透明类型别名_提供没有任何**开销**的类型抽象。 + +## 抽象开销 + +假设我们要定义一个提供数字算术运算的模块,这些数字由它们的对数表示。 +当涉及的数值非常大或接近于零时,使用对数有利于提高精度。 + +把“常规”双精度值与存储为对数的值区分开来很重要,我们引入了一个类 `Logarithm`: + +```scala +class Logarithm(protected val underlying: Double): + def toDouble: Double = math.exp(underlying) + def + (that: Logarithm): Logarithm = + // here we use the apply method on the companion + Logarithm(this.toDouble + that.toDouble) + def * (that: Logarithm): Logarithm = + new Logarithm(this.underlying + that.underlying) + +object Logarithm: + def apply(d: Double): Logarithm = new Logarithm(math.log(d)) +``` + +伴生对象上的 apply 方法让我们可以创建 `Logarithm` 类型的值,我们可用如下方式使用: + +```scala +val l2 = Logarithm(2.0) +val l3 = Logarithm(3.0) +println((l2 * l3).toDouble) // prints 6.0 +println((l2 + l3).toDouble) // prints 4.999... +``` + +虽然 `Logarithm` 类为以这种特殊对数形式存储的 `Double` 值提供了一个很好的抽象,但它带来了严重的性能开销:对于每一个数学运算,我们需要提取基础值,然后将其再次包装在一个 `Logarithm` 的新实例中。 + +## 模块抽象 + +让我们考虑另一种实现相同库的方法。 +这次我们没有将 `Logarithm` 定义为一个类,而是使用_类型别名_来定义它。 +首先,我们定义模块的抽象接口: + +```scala +trait Logarithms: + + type Logarithm + + // operations on Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm + def mul(x: Logarithm, y: Logarithm): Logarithm + + // functions to convert between Double and Logarithm + def make(d: Double): Logarithm + def extract(x: Logarithm): Double + + // extension methods to use `add` and `mul` as "methods" on Logarithm + extension (x: Logarithm) + def toDouble: Double = extract(x) + def + (y: Logarithm): Logarithm = add(x, y) + def * (y: Logarithm): Logarithm = mul(x, y) +``` + +现在,让我们通过说类型 `Logarithm` 等于 `Double` 来实现这个抽象接口: + +```scala +object LogarithmsImpl extends Logarithms: + + type Logarithm = Double + + // operations on Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble) + def mul(x: Logarithm, y: Logarithm): Logarithm = x + y + + // functions to convert between Double and Logarithm + def make(d: Double): Logarithm = math.log(d) + def extract(x: Logarithm): Double = math.exp(x) +``` + +在 `LogarithmsImpl` 的实现中,等式 `Logarithm = Double` 允许我们实现各种方法。 + +#### 暴露抽象 + +但是,这种抽象有点暴露。 +我们必须确保_只_针对抽象接口 `Logarithms` 进行编程,并且永远不要直接使用 `LogarithmsImpl`。 +直接使用 `LogarithmsImpl` 会使等式 `Logarithm = Double` 对用户可见,用户可能会意外使用 `Double`,而实际上是需要 对数双精度。 +例如: + +```scala +import LogarithmsImpl.* +val l: Logarithm = make(1.0) +val d: Double = l // type checks AND leaks the equality! +``` + +必须将模块分离为抽象接口和实现可能很有用,但只为了隐藏 `Logarithm` 的实现细节,就需要付出很多努力。 +针对抽象模块 `Logarithm` 进行编程可能非常乏味,并且通常需要使用像路径依赖类型这样的高级特性,如下例所示: + +```scala +def someComputation(L: Logarithms)(init: L.Logarithm): L.Logarithm = ... +``` + +#### 装箱的开销 + +类型抽象,例如 `type Logarithm` [抹去](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure) 到它们的界限(在我们的例子中是 `Any`)。 +也就是说,虽然我们不需要手动包装和解包 `Double` 值,但仍然会有一些与装箱原始类型 `Double` 相关的装箱开销。 + +## 不透明类型 + +我们可以简单地使用 Scala 3 中的不透明类型来实现类似的效果,而不是手动将我们的 `Logarithms` 组件拆分为抽象部分和具体实现: + +```scala +object Logarithms: +//vvvvvv this is the important difference! + opaque type Logarithm = Double + + object Logarithm: + def apply(d: Double): Logarithm = math.log(d) + + extension (x: Logarithm) + def toDouble: Double = math.exp(x) + def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) + def * (y: Logarithm): Logarithm = x + y +``` + +`Logarithm` 与 `Double` 相同的事实仅在定义 `Logarithm` 的范围内已知,在上面的示例中对应于对象 `Logarithms`。 +类型相等 `Logarithm = Double` 可用于实现方法(如 `*` 和 `toDouble`)。 + +然而,在模块之外, `Logarithm` 类型是完全封装的,或者说是“不透明的”。 对于 `Logarithm` 的用户来说,不可能发现 `Logarithm` 实际上是作为 `Double` 实现的: + +```scala +import Logarithms.* +val l2 = Logarithm(2.0) +val l3 = Logarithm(3.0) +println((l2 * l3).toDouble) // prints 6.0 +println((l2 + l3).toDouble) // prints 4.999... + +val d: Double = l2 // ERROR: Found Logarithm required Double +``` + +尽管我们抽象了 `Logarithm`,但抽象是免费的: +由于只有一种实现,在运行时对于像 `Double` 这样的原始类型将_没有装箱开销_。 + +### 不透明类型总结 + +不透明类型提供了对实现细节的合理抽象,而不会增加性能开销。 +如上图所示,不透明类型使用起来很方便,并且与 [扩展方法][extension] 功能很好地集成在一起。 + +[extension]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %} diff --git a/_zh-cn/overviews/scala3-book/types-others.md b/_zh-cn/overviews/scala3-book/types-others.md new file mode 100644 index 0000000000..eec6faada8 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-others.md @@ -0,0 +1,32 @@ +--- +title: 其他类型 +type: section +description: This section mentions other advanced types in Scala 3. +language: zh-cn +num: 58 +previous-page: types-dependent-function +next-page: ca-contextual-abstractions-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +Scala还有其他几种高级类型,本书中没有介绍,包括: + +- 类型 lambdas +- 匹配类型 +- 存在类型 +- 高等类型 +- 单例类型 +- 细化类型 +- 种类多态性 + +有关这些类型的更多详细信息,请参阅[参考文档][reference]。 +有关单例类型参见 Scala 3 规格中的[字面量类型](https://scala-lang.org/files/archive/spec/3.4/03-types.html#literal-types) 一节, +细化类型参见[细化类型](https://scala-lang.org/files/archive/spec/3.4/03-types.html) 一节。 + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/types-structural.md b/_zh-cn/overviews/scala3-book/types-structural.md new file mode 100644 index 0000000000..4c12a87901 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-structural.md @@ -0,0 +1,110 @@ +--- +title: 结构化类型 +type: section +description: This section introduces and demonstrates structural types in Scala 3. +language: zh-cn +num: 56 +previous-page: types-opaque-types +next-page: types-dependent-function + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +{% comment %} +NOTE: It would be nice to simplify this more. +{% endcomment %} + + +一些用例,例如建模数据库访问,在静态类型语言中比在动态类型语言中更尴尬。 +使用动态类型语言,很自然地将行建模为记录或对象,并使用简单的点表示法选择条目,例如 `row.columnName`。 + +要在静态类型语言中获得相同的体验,需要为数据库操作产生的每个可能的行定义一个类——包括连接和投影产生的行——并设置一个方案以在行和代表它的类之间进行映射。 + +这需要大量样板文件,这导致开发人员将静态类型的优势换成更简单的方案,其中列名表示为字符串并传递给其他运算符,例如 `row.select("columnName")`。 +这种方法即便放弃了静态类型的优点,也仍然不如动态类型的版本自然。 + +在您希望在动态上下文中支持简单的点表示法而又不失静态类型优势的情况下,结构化类型会有所帮助。 +它们允许开发人员使用点表示法并配置应如何解析字段和方法。 + +## 例子 + +这是一个结构化类型 `Person` 的示例: + +```scala +class Record(elems: (String, Any)*) extends Selectable: + private val fields = elems.toMap + def selectDynamic(name: String): Any = fields(name) + +type Person = Record { + val name: String + val age: Int +} +``` + +`Person` 类型在其父类型 `Record` 中添加了一个_精细的改进_,它定义了 `name` 和 `age` 字段。 +我们精细的改进是_构造的_,因为 `name` 和 `age` 没有在父类型中定义。 +但是它们仍然作为 `Person` 类的成员存在。 +例如,以下程序将打印 `"Emma is 42 years old."`: + +```scala +val person = Record( + "name" -> "Emma", + "age" -> 42 +).asInstanceOf[Person] + +println(s"${person.name} is ${person.age} years old.") +``` + +本例中的父类型 `Record` 是一个通用类,可以在其 `elems` 参数中表示任意记录。 +该参数是一个序列,该序列的元素是 `String` 类型的标签和 `Any` 类型的值组成的对。 +当您将 `Person` 创建为 `Record` 时,您必须使用类型转换断言该记录定义了正确类型的正确字段。 +`Record` 本身的类型太弱了,所以编译器在没有用户帮助的情况下无法知道这一点。 +实际上,结构化类型与其底层通用表示之间的连接很可能由数据库层完成,因此最终用户没必要关注。 + +`Record` 扩展了标记 trait `scala.Selectable` 并定义了一个方法 `selectDynamic`,它将字段名称映射到其值。 +通过调用此方法来选择结构化类型成员。 +Scala 编译器把选择 `person.name` 和 `person.age` 翻译成: + +```scala +person.selectDynamic("name").asInstanceOf[String] +person.selectDynamic("age").asInstanceOf[Int] +``` + +## 第二个例子 + +为了强化您刚刚看到的内容,这里有另一个名为 `Book` 的结构化类型,它表示您可能从数据库中读取的一本书: + +```scala +type Book = Record { + val title: String + val author: String + val year: Int + val rating: Double +} +``` + +与 `Person` 一样,这是创建 `Book` 实例的方式: + +```scala +val book = Record( + "title" -> "The Catcher in the Rye", + "author" -> "J. D. Salinger", + "year" -> 1951, + "rating" -> 4.5 +).asInstanceOf[Book] +``` + +## 可选类 + +除了 `selectDynamic` 之外,`Selectable`类有时还会定义 `applyDynamic` 方法。 +然后可以使用它来翻译是函数调用的结构成员。 +因此,如果 `a` 是 `Selectable` 的一个实例,则像 `a.f(b, c)` 这样的结构调用将转换为: + +```scala +a.applyDynamic("f")(b, c) +``` + diff --git a/_zh-cn/overviews/scala3-book/types-union.md b/_zh-cn/overviews/scala3-book/types-union.md new file mode 100644 index 0000000000..4e1ca79242 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-union.md @@ -0,0 +1,111 @@ +--- +title: 联合类型 +type: section +description: This section introduces and demonstrates union types in Scala 3. +language: zh-cn +num: 52 +previous-page: types-intersection +next-page: types-adts-gadts + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +scala3: true +versionSpecific: true +--- + + +用于类型,`|` 操作符创建一个所谓的_联合类型_。 +类型 `A | B` 表示**要么是** `A` 类型的值,**要么是** `B` 类型的值。 + +在下面的例子中,`help` 方法接受一个名为 `id` 的联合类型 `Username | Password`,可以是 `Useername` 或 `Password`: + +```scala +case class Username(name: String) +case class Password(hash: Hash) + +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // more code here ... +``` + +我们通过使用模式匹配区分二者,从而实现 `help` 方法。 + +此代码是一种灵活且类型安全的解决方案。 +如果您尝试传入`Useername` 或 `Password` 以外的类型,编译器会将其标记为错误: + +```scala +help("hi") // error: Found: ("hi" : String) + // Required: Username | Password +``` + +如果您尝试将 `case` 添加到与 `Username` 或 `Password` 类型不匹配的 `match` 表达式中,也会出现错误: + +```scala +case 1.0 => ??? // ERROR: this line won’t compile +``` + +### 联合类型的替代方案 + +如图所示,联合类型可用于替代几种不同的类型,而不要求这些类型是定制类层次结构的一部分,也不需要显式包装。 + +#### 预先规划类层次结构 + +其他语言需要预先规划类层次结构,如下例所示: + +{% tabs pre-planning %} +{% tab 'Scala 2 and 3' %} +```scala +trait UsernameOrPassword +case class Username(name: String) extends UsernameOrPassword +case class Password(hash: Hash) extends UsernameOrPassword +def help(id: UsernameOrPassword) = ... +``` +{% endtab %} +{% endtabs %} + +预先计划不能很好地扩展,例如,API 用户的需求可能无法预见。 +此外,使用诸如 `UsernameOrPassword` 之类的标记 trait 使类型层次结构混乱也会使代码更难阅读。 + +#### 标记联合 + +另一种选择是定义一个单独的枚举类型,如: + +```scala +enum UsernameOrPassword: + case IsUsername(u: Username) + case IsPassword(p: Password) +``` + +枚举 `UsernameOrPassword` 表示 `Username` 和 `Password` 的 _标记_联合。 +但是,这种联合建模方式需要_显式包装和展开_,例如,`Username` **不是** `UsernameOrPassword` 的子类型。 + +### 联合类型推断 + +_仅当_明确给出这种类型时,编译器才会将联合类型分配给表达式。 +例如,给定这些值: + +```scala +val name = Username("Eve") // name: Username = Username(Eve) +val password = Password(123) // password: Password = Password(123) +``` + +这个 REPL 示例展示了在将变量绑定到 `if`/`else` 表达式的结果时如何使用联合类型: + +```` +scala> val a = if (true) name else password +val a: Object = Username(Eve) + +scala> val b: Password | Username = if (true) name else password +val b: Password | Username = Username(Eve) +```` + +`a` 的类型是 `Object`,它是 `Username` 和 `Password` 的超类型,但不是二者*最小*的超类型 `Password | Username`。 +如果你想要最小的超类型,你必须明确地给出它,就像对 `b` 所做的那样。 + +> 联合类型是交集类型的对偶。 +> 和具有交集类型的 `&` 一样,`|` 也是可交换的:`A | B` 与 `B | A` 是同一类型。 + diff --git a/_zh-cn/overviews/scala3-book/types-variance.md b/_zh-cn/overviews/scala3-book/types-variance.md new file mode 100644 index 0000000000..3c774c1d52 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-variance.md @@ -0,0 +1,250 @@ +--- +title: 型变 +type: section +description: This section introduces and demonstrates variance in Scala 3. +language: zh-cn +num: 54 +previous-page: types-adts-gadts +next-page: types-opaque-types + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + + +类型参数_型变_控制参数化类型(如类或 traits)的子类型。 + +为了解释型变,让我们假设以下类型定义: + +{% tabs types-variance-1 %} +{% tab 'Scala 2 and 3' %} +```scala +trait Item { def productNumber: String } +trait Buyable extends Item { def price: Int } +trait Book extends Buyable { def isbn: String } +``` +{% endtab %} +{% endtabs %} + +我们还假设以下参数化类型: + +{% tabs types-variance-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-2 %} +```scala +// an example of an invariant type +trait Pipeline[T] { + def process(t: T): T +} + +// an example of a covariant type +trait Producer[+T] { + def make: T +} + +// an example of a contravariant type +trait Consumer[-T] { + def take(t: T): Unit +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-2 %} +```scala +// an example of an invariant type +trait Pipeline[T]: + def process(t: T): T + +// an example of a covariant type +trait Producer[+T]: + def make: T + +// an example of a contravariant type +trait Consumer[-T]: + def take(t: T): Unit +``` +{% endtab %} +{% endtabs %} + +一般来说,型变有三种模式: + +- **不变的**---默认值,写成 `Pipeline[T]` +- **协变**---用`+`注释,例如 `Producer[+T]` +- **逆变**---用`-`注释,如 `Consumer[-T]` + +我们现在将详细介绍此注释的含义以及我们使用它的原因。 + +### 不变类型 + +默认情况下,像 `Pipeline` 这样的类型在它们的类型参数中是不变的(本例中是 `T`)。 +这意味着像 `Pipeline[Item]`、`Pipeline[Buyable]` 和 `Pipeline[Book]` 这样的类型彼此之间_没有子类型关系_。 + +理所当然地!假设以下方法使用两个类型为`Pipeline[Buyable]` 的值,并根据价格将其参数 `b` 传递给其中一个: + +{% tabs types-variance-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-3 %} +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = { + val b1 = p1.process(b) + val b2 = p2.process(b) + if (b1.price < b2.price) + b1 + else + b2 + } +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-3 %} +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = + val b1 = p1.process(b) + val b2 = p2.process(b) + if b1.price < b2.price then b1 else b2 +``` +{% endtab %} +{% endtabs %} + +现在,回想一下,我们的类型之间存在以下_子类型关系_: + +{% tabs types-variance-4 %} +{% tab 'Scala 2 and 3' %} +```scala +Book <: Buyable <: Item +``` +{% endtab %} +{% endtabs %} + +我们不能将 `Pipeline[Book]` 传递给 `oneOf` 方法,因为在其实现中,我们调用的 `p1` 和 `p2` 是 `Buyable` 类型的值。 +`Pipeline[Book]` 需要的是 `Book`,这可能会导致运行时错误。 + +我们不能传递一个 `Pipeline[Item]` 因为在它上面调用 `process` 只会保证返回一个 `Item`;但是,我们应该返回一个 `Buyable` 。 + +#### 为什么是不变的? + +事实上,`Pipeline` 类型需要是不变的,因为它使用它的类型参数 `T` _既_作为参数类型,_又_作为返回类型。 +出于同样的原因,Scala 集合库中的某些类型——例如 `Array` 或 `Set` —— 也是_不变的_。 + +### 协变类型 + +与不变的 `Pipeline` 相比,`Producer` 类型通过在类型参数前面加上 `+` 前缀被标记为 **协变**。 +这是有效的,因为类型参数仅用于_返回的位置_。 + +将其标记为协变意味着当需要 `Producer[Buyable]` 时,我们可以传递(或返回)一个 `Producer[Book]`。 +事实上,这是合理的。 `Producer[Buyable].make` 的类型只承诺_返回_ `Buyable`。 +作为 `make` 的调用者,我们乐意接受作为 `Buyable` 的子类型的 `Book` 类型,---也就是说,它_至少_是一个 `Buyable`。 + +以下示例说明了这一点,其中函数 `makeTwo` 需要一个 `Producer[Buyable]`: + +{% tabs types-variance-5 %} +{% tab 'Scala 2 and 3' %} +```scala +def makeTwo(p: Producer[Buyable]): Int = + p.make.price + p.make.price +``` +{% endtab %} +{% endtabs %} + +通过书籍制作人是完全可以的: + +{% tabs types-variance-6 %} +{% tab 'Scala 2 and 3' %} +```scala +val bookProducer: Producer[Book] = ??? +makeTwo(bookProducer) +``` +{% endtab %} +{% endtabs %} + +在 `makeTwo` 中调用 `price` 对书籍仍然有效。 + +#### 不可变容器的协变类型 + +在处理不可变容器时,您会经常遇到协变类型,例如可以在标准库中找到的那些(例如 `List`、`Seq`、`Vector` 等)。 + +例如,`List` 和 `Vector` 大致定义为: + +{% tabs types-variance-7 %} +{% tab 'Scala 2 and 3' %} +```scala +class List[+A] ... +class Vector[+A] ... +``` +{% endtab %} +{% endtabs %} + +这样,您可以在需要 `List[Buyable]` 的地方使用 `List[Book]`。 +这在直觉上也是有道理的:如果您期望收藏可以购买的东西,那么给您收藏书籍应该没问题。 +在我们的示例中,它们有一个额外的 ISBN 方法,但您可以随意忽略这些额外的功能。 + +### 逆变类型 + +与标记为协变的类型 `Producer` 相比,类型 `Consumer` 通过在类型参数前加上 `-` 来标记为**逆变**。 +这是有效的,因为类型参数仅用于_参数位置_。 + +将其标记为逆变意味着如果我们想要 `Consumer[Buyable]` 时,可以传递(或返回) `Consumer[Item]`。 +也就是说,我们有子类型关系`Consumer[Item] <: Consumer[Buyable]`。 +请记住,对于类型 `Producer`,情况正好相反,我们有 `Producer[Buyable] <: Producer[Item]`。 + +事实上,这是合理的。 `Consumer[Item].take` 方法接受一个 `Item`。 +作为 `take` 的调用者,我们还可以提供 `Buyable`,它会被 `Consumer[Item]` 愉快地接受,因为 `Buyable` 是 `Item` 的一个子类型——也就是说,它_至少_是 `Item` 。 + +#### 消费者的逆变类型 + +逆变类型比协变类型少得多。 +在我们的示例中,您可以将它们视为“消费者”。你可能来的最重要的类型标记为逆变的 cross 是函数之一: + +{% tabs types-variance-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-8 %} +```scala +trait Function[-A, +B] { + def apply(a: A): B +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-variance-8 %} +```scala +trait Function[-A, +B]: + def apply(a: A): B +``` +{% endtab %} +{% endtabs %} + +它的参数类型 `A` 被标记为逆变的 `A` ——它消费 `A` 类型的值。 +相反,它的结果类型 `B` 被标记为协变——它产生 `B` 类型的值。 + +以下是一些示例,这些示例说明了由函数上可变注释引起的子类型关系: + +{% tabs types-variance-9 %} +{% tab 'Scala 2 and 3' %} +```scala +val f: Function[Buyable, Buyable] = b => b + +// OK to return a Buyable where a Item is expected +val g: Function[Buyable, Item] = f + +// OK to provide a Book where a Buyable is expected +val h: Function[Book, Buyable] = f +``` +{% endtab %} +{% endtabs %} + +## 概括 + +在本节中,我们遇到了三种不同的方差: + +- **生产者**通常是协变的,并用 `+` 标记它们的类型参数。 + 这也适用于不可变集合。 +- **消费者**通常是逆变的,并用 `-` 标记他们的类型参数。 +- **既是**生产者**又**是消费者的类型必须是不变的,并且不需要在其类型参数上进行任何标记。 + 像 `Array` 这样的可变集合就属于这一类。 diff --git a/_zh-cn/overviews/scala3-book/where-next.md b/_zh-cn/overviews/scala3-book/where-next.md new file mode 100644 index 0000000000..66c3afe639 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/where-next.md @@ -0,0 +1,20 @@ +--- +title: 下一步去哪 +type: chapter +description: Where to go next after reading the Scala Book +language: zh-cn +num: 76 +previous-page: scala-for-python-devs +next-page: + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +我们希望你能喜欢 Scala 编程语言的介绍,我们也希望你能分享一些这门语言的美丽之处。 + +如果你继续用 Scala 工作,你可以在我们[引导和概览部分][overviews]发现更多细节。 + +[overviews]: {% link _overviews/index.md %} diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md new file mode 100644 index 0000000000..b07b567fe7 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -0,0 +1,504 @@ +--- +title: 为什么是 Scala 3 ? +type: chapter +description: This page describes the benefits of the Scala 3 programming language. +language: zh-cn +num: 3 +previous-page: scala-features +next-page: taste-intro + +partof: scala3-book +overview-name: "Scala 3 — Book" +layout: multipage-overview +permalink: "/zh-cn/scala3/book/:title.html" +--- + +{% comment %} +TODO: Is “Scala 3 Benefits” a better title? +NOTE: Could mention “grammar” as a way of showing that Scala isn’t a large language; see this slide: https://www.slideshare.net/Odersky/preparing-for-scala-3#13 +{% endcomment %} + +使用 Scala 有很多好处,特别是 Scala 3。 +很难列出 Scala 的每一个好处,但“前十名”列表可能看起来像这样: + +1. Scala 融合了函数式编程(FP)和面向对象编程(OOP) +2. Scala 是静态类型的语言,但通常感觉像一种动态类型语言。 +3. Scala 的语法简洁,但仍然可读;它通常被称为 _易于表达_ +4. Scala 2 中的 _Implicits_ 是一个定义特性,它们在 Scala 3 中得到了改进和简化。 +5. Scala 与 Java 无缝集成,因此您可以创建混合了 Scala 和 Java 代码的项目,Scala 代码可以轻松使用成千上万个现有的 Java 库 +6. Scala 可以在服务器上使用,通过 [Scala.js](https://www.scala-js.org), Scala 也可以在浏览器中使用 +7. Scala 标准库具有数十种预构建的函数式方法,可节省您的时间,并大大减少编写自定义 `for` 循环和算法的需要 +8. Scala 内置了“最佳实践”,它支持不可变性,匿名函数,高阶函数,模式匹配,默认情况下无法扩展的类等 +9. Scala 生态系统提供世界上最现代化的 FP 库 +10. 强类型式系统 + +## 1) FP/OOP 融合 + +Scala 比任何其他语言都更支持 FP 和 OOP 范式的融合。 +正如 Martin Odersky 所说,Scala 的本质是在类型化环境中融合了函数式和面向对象编程,具有: + +- 函数用于编写逻辑 (局部) +- 对象用于构建模块化 (整体) + +模块化的一些最佳示例可能是标准库中的类。 +例如,`List` 被定义为一个类---从技术上讲,它是一个抽象类---并且像这样创建了一个新实例: + +{% tabs list %} +{% tab 'Scala 2 and 3' %} +```scala +val x = List(1, 2, 3) +``` +{% endtab %} +{% endtabs %} + +但是,在程序员看来是一个简单的 `List` 实际上是由几种特殊类型的组合构建的,包括名为`Iterable`, `Seq`, 和 `LinearSeq` 的 traits。 +这些类型同样由其他小型的模块化代码单元组成。 + +除了从一系列模块化 traits 构建/cases像 `List` 这样的类型之外,`List` API还包含数十种其他方法,其中许多是高阶函数: + +{% tabs list-methods %} +{% tab 'Scala 2 and 3' %} +```scala +val xs = List(1, 2, 3, 4, 5) + +xs.map(_ + 1) // List(2, 3, 4, 5, 6) +xs.filter(_ < 3) // List(1, 2) +xs.find(_ > 3) // Some(4) +xs.takeWhile(_ < 3) // List(1, 2) +``` +{% endtab %} +{% endtabs %} + +在这些示例中,无法修改列表中的值。 +`List` 类是不可变的,因此所有这些方法都返回新值,如每个注释中的数据所示。 + +## 2) 动态的感觉 + +Scala的 _类型推断_ 经常使语言感觉是动态类型的,即使它是静态类型的。 +对于变量声明,情况确实如此: + +{% tabs dynamic %} +{% tab 'Scala 2 and 3' %} +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3,4,5) +val stuff = ("fish", 42, 1_234.5) +``` +{% endtab %} +{% endtabs %} + +当把匿名函数传递给高阶函数时,情况也是如此: + +{% tabs dynamic-hof %} +{% tab 'Scala 2 and 3' %} +```scala +list.filter(_ < 4) +list.map(_ * 2) +list.filter(_ < 4) + .map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +还有定义方法的时候: + +{% tabs list-method %} +{% tab 'Scala 2 and 3' %} +```scala +def add(a: Int, b: Int) = a + b +``` +{% endtab %} +{% endtabs %} + +这在Scala 3中比以往任何时候都更加真实,例如在使用[union types][union-types] 时: + +{% tabs union %} +{% tab 'Scala 3 Only' %} +```scala +// union type parameter +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // more code here ... + +// union type value +val b: Password | Username = if (true) name else password +``` +{% endtab %} +{% endtabs %} + +## 3) 简洁的语法 + +Scala是一种 low ceremony,“简洁但仍然可读”的语言。例如,变量声明是简洁的: + +{% tabs concise %} +{% tab 'Scala 2 and 3' %} +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3) +``` +{% endtab %} +{% endtabs %} + +创建类型如traits, 类和枚举都很简洁: + +{% tabs enum %} +{% tab 'Scala 3 Only' %} +```scala +trait Tail: + def wagTail(): Unit + def stopTail(): Unit + +enum Topping: + case Cheese, Pepperoni, Sausage, Mushrooms, Onions + +class Dog extends Animal, Tail, Legs, RubberyNose + +case class Person( + firstName: String, + lastName: String, + age: Int +) +``` +{% endtab %} +{% endtabs %} + +简洁的高阶函数: + +{% tabs list-hof %} +{% tab 'Scala 2 and 3' %} +```scala +list.filter(_ < 4) +list.map(_ * 2) +``` +{% endtab %} +{% endtabs %} + +所有这些表达方式以及更多表达方式都很简洁,并且仍然非常易读:我们称之为 _富有表现力_。 + +## 4) 隐式,简化 + +Scala 2 中的隐式是一个主要明显的设计特征。 +它们代表了抽象上下文的基本方式,具有服务于各种用例的统一范式,其中包括: + +- 实现 [type classes]({% link _zh-cn/overviews/scala3-book/ca-type-classes.md %}) +- 建立背景 +- 依赖注入 +- 表达能力 + +从那以后,其他语言也采用了类似的概念,所有这些都是 _术语推断_ 核心思想的变体:给定一个类型,编译器合成一个具有该类型的“规范”术语。 + +虽然隐式是 Scala 2 中的一个定义特性,但它们的设计在 Scala 3 中得到了极大的改进: + +- 定义“given”值的方法只有一种 +- 只有一种方法可以引入隐式参数和参数 +- 有一种单独的方式来导入 givens,不允许它们隐藏在正常导入的海洋中 +- 只有一种定义隐式转换的方法,它被清楚地标记为这样,并且不需要特殊的语法 + +这些变化的好处包括: + +- 新设计避免了特性交叉,使语言更加一致 +- 它使隐式更容易学习和不容易滥用 +- 它极大地提高了 95% 使用隐式的 Scala 程序的清晰度 +- 它有可能以一种易于理解和友好的原则方式进行术语推断 + +这些功能在其他部分有详细描述,因此请参阅 [上下文抽象介绍][context] 和 [`given` 和 `using` 子句][given] 部分了解更多详细信息。 + +## 5) 与 Java 无缝集成 + +Scala/Java 交互在许多方面都是无缝的。 +例如: + +- 您可以使用 Scala 项目中可用的所有数千个 Java 库 +- Scala `String` 本质上是 Java `String`,添加了附加功能 +- Scala 无缝使用 Java 中 *java.time._* 包中的日期/时间类 + +您还可以在 Scala 中使用 Java 集合类,并为它们提供更多功能,Scala 包含方法,因此您可以将它们转换为 Scala 集合。 + +虽然几乎所有交互都是无缝的,但[“与 Java 交互”一章][java] 演示了如何更好地结合使用某些功能,包括如何使用: + +- Scala 中的 Java 集合 +- Scala 中的 Java `Optional` +- Scala 中的 Java 接口 +- Java 中的 Scala 集合 +- Java 中的 Scala `Option` +- Java 中的 Scala traits +- 在 Java 代码中引发异常的 Scala 方法 +- Java 中的 Scala 可变参数 + +有关这些功能的更多详细信息,请参见该章。 + +## 6) 客户 &服务器 + +Scala 可以通过非常棒的框架在服务器端使用: + +- [Play Framework](https://www.playframework.com) 可让您构建高度可扩展的服务器端应用程序和微服务 +- [Akka Actors](https://akka.io) 让你使用actor模型大大简化分布式和并发软件应用程序 + +Scala 也可以通过 [Scala.js 项目](https://www.scala-js.org) 在浏览器中使用,它是 JavaScript 的类型安全替代品。 +Scala.js 生态系统 [有几十个库](https://www.scala-js.org/libraries) 让您可以在浏览器中使用 React、Angular、jQuery 和许多其他 JavaScript 和 Scala 库。 + +除了这些工具之外,[Scala Native](https://github.com/scala-native/scala-native) 项目“是一个优化的提前编译器和专为 Scala 设计的轻量级托管运行时”。它允许您使用纯 Scala 代码构建“系统”风格的二进制可执行应用程序,还允许您使用较低级别的原语。 + +## 7) 标准库方法 + +您将很少需要再次编写自定义的 `for` 循环,因为 Scala 标准库中的数十种预构建函数方法既可以节省您的时间,又有助于使代码在不同应用程序之间更加一致。 + +下面的例子展示了一些内置的集合方法,除此之外还有很多。 +虽然这些都使用 `List` 类,但相同的方法适用于其他集合类,例如 `Seq`、`Vector`、`LazyList`、`Set`、`Map`、`Array` 和 `ArrayBuffer`。 + +这里有些例子: + +{% tabs list-more %} +{% tab 'Scala 2 and 3' %} +```scala +List.range(1, 3) // List(1, 2) +List.range(start = 1, end = 6, step = 2) // List(1, 3, 5) +List.fill(3)("foo") // List(foo, foo, foo) +List.tabulate(3)(n => n * n) // List(0, 1, 4) +List.tabulate(4)(n => n * n) // List(0, 1, 4, 9) + +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.filter(_ > 100) // List() +a.find(_ > 20) // Some(30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.map(_ * 2) // List(20, 40, 60, 80, 20) +a.slice(2, 4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +a.takeWhile(_ < 30) // List(10, 20) +a.filter(_ < 30).map(_ * 10) // List(100, 200, 100) + +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(10, 5, 8, 1, 7) +nums.sorted // List(1, 5, 7, 8, 10) +nums.sortWith(_ < _) // List(1, 5, 7, 8, 10) +nums.sortWith(_ > _) // List(10, 8, 7, 5, 1) +``` +{% endtab %} +{% endtabs %} + +## 8) 内置最佳实践 + +Scala 习语以多种方式鼓励最佳实践。 +对于不可变性,我们鼓励您创建不可变的 `val` 声明: + +{% tabs val %} +{% tab 'Scala 2 and 3' %} +```scala +val a = 1 // 不可变变量 +``` +{% endtab %} +{% endtabs %} + +还鼓励您使用不可变集合类,例如 `List` 和 `Map`: + +{% tabs list-map %} +{% tab 'Scala 2 and 3' %} +```scala +val b = List(1,2,3) // List 是不可变的 +val c = Map(1 -> "one") // Map 是不可变的 +``` +{% endtab %} +{% endtabs %} + +样例类主要用于 [领域建模]({% link _zh-cn/overviews/scala3-book/domain-modeling-intro.md %}),它们的参数是不可变的: + +{% tabs case-class %} +{% tab 'Scala 2 and 3' %} +```scala +case class Person(name: String) +val p = Person("Michael Scott") +p.name // Michael Scott +p.name = "Joe" // 编译器错误(重新分配给 val 名称) +``` +{% endtab %} +{% endtabs %} + +如上一节所示,Scala 集合类支持高阶函数,您可以将方法(未显示)和匿名函数传递给它们: + +{% tabs higher-order %} +{% tab 'Scala 2 and 3' %} +```scala +a.dropWhile(_ < 25) +a.filter(_ < 25) +a.takeWhile(_ < 30) +a.filter(_ < 30).map(_ * 10) +nums.sortWith(_ < _) +nums.sortWith(_ > _) +``` +{% endtab %} +{% endtabs %} + +`match` 表达式让您可以使用模式匹配,它们确实是返回值的 _表达式_: + +{% tabs match class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +val numAsString = i match { + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` +{% endtab %} +{% endtabs %} + +因为它们可以返回值,所以它们经常被用作方法的主体: + +{% tabs match-body class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" => false + case _ => true +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true +``` +{% endtab %} +{% endtabs %} + +## 9) 生态系统库 + +用于函数式编程的 Scala 库,如 [Cats](https://typelevel.org/cats) 和 [Zio](https://zio.dev) 是 FP 社区中的前沿库。 +所有流行语,如高性能、类型安全、并发、异步、资源安全、可测试、函数式、模块化、二进制兼容、高效、副作用/有副作用等,都可以用于这些库。 + +我们可以在这里列出数百个库,但幸运的是它们都列在另一个位置:有关这些详细信息,请参阅 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)。 + +## 10) 强类型系统 + +Scala 有一个强大的类型系统,它在 Scala 3 中得到了更多的改进。 +Scala 3 的目标很早就定义了,与类型系统相关的目标包括: + +- 简化 +- 消除不一致 +- 安全 +- 人体工程学 +- 性能 + +_简化_ 来自数十个更改和删除的特性。 +例如,从 Scala 2 中重载的 `implicit` 关键字到 Scala 3 中的术语 `given` 和 `using` 的变化使语言更加清晰,尤其是对于初学者来说。 + +_消除不一致_ 与Scala 3中的几十个[删除的特性][dropped]、[改变的特性][changed]和[增加的特性][add]有关。 +此类别中一些最重要的功能是: + +- 交集类型 +- 并集类型 +- 隐式函数类型 +- 依赖函数类型 +- trait 参数 +- 通用元组 + +{% comment %} +A list of types from the Dotty documentation: + +- Inferred types +- Generics +- Intersection types +- Union types +- Structural types +- Dependent function types +- Type classes +- Opaque types +- Variance +- Algebraic Data Types +- Wildcard arguments in types: ? replacing _ +- Type lambdas +- Match types +- Existential types +- Higher-kinded types +- Singleton types +- Refinement types +- Kind polymorphism +- Abstract type members and path-dependent types +- Dependent function types +- Bounds +{% endcomment %} + +_安全_ 与几个新的和改变的特性有关: + +- Multiversal equality +- Restricting implicit conversions +- Null safety +- Safe initialization + +_人体工程学_ 的好例子是枚举和扩展方法,它们以非常易读的方式添加到 Scala 3 中: + +{% tabs extension %} +{% tab 'Scala 3 Only' %} +```scala +// 枚举 +enum Color: + case Red, Green, Blue + +// 扩展方法 +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 + def diameter: Double = c.radius * 2 + def area: Double = math.Pi * c.radius * c.radius +``` +{% endtab %} +{% endtabs %} + +_性能_ 涉及几个方面。 +其中之一是 [不透明类型][opaque-types]。 +在 Scala 2 中,有几次尝试创建解决方案以与域驱动设计 (DDD) 实践相一致,即赋予值更有意义的类型。 +这些尝试包括: + +- 类型别名 +- 值类 +- 样例类 + +不幸的是,所有这些方法都有弱点,如 [_Opaque Types_ SIP](https://docs.scala-lang.org/sips/opaque-types.html) 中所述。 +相反,如 SIP 中所述,不透明类型的目标是“对这些包装器类型的操作不得在运行时产生任何额外开销,同时在编译时仍提供类型安全使用。” + +有关更多类型系统的详细信息,请参阅 [参考文档][reference]。 + +## 其他很棒的功能 + +Scala 有许多很棒的特性,选择十大列表可能是主观的。 +多项调查表明,不同的开发人员群体喜欢不同的特性。 + +[java]: {% link _zh-cn/overviews/scala3-book/interacting-with-java.md %} +[given]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %} +[contextual]: {% link _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }} +[dropped]: {{ site.scala3ref }}/dropped-features +[changed]: {{ site.scala3ref }}/changed-features +[added]:{{ site.scala3ref }}/other-new-features + +[union-types]: {% link _zh-cn/overviews/scala3-book/types-union.md %} +[opaque-types]: {% link _zh-cn/overviews/scala3-book/types-opaque-types.md %} diff --git a/_zh-cn/overviews/thanks.md b/_zh-cn/overviews/thanks.md index e1429b74f0..e2fac4be7a 100644 --- a/_zh-cn/overviews/thanks.md +++ b/_zh-cn/overviews/thanks.md @@ -1,7 +1,8 @@ --- -layout: inner-page-no-masthead +layout: singlepage-overview language: zh-cn title: 致谢名单 +orphanTranslation: true --- 2013年10月份起,CSDN CODE开始组织志愿者翻译Scala官方文档。计划翻译的文档主要为Scala官网上overview部分的内容,包含以下部分: diff --git a/_zh-cn/scala3/guides.md b/_zh-cn/scala3/guides.md deleted file mode 100644 index 5a656b5a4d..0000000000 --- a/_zh-cn/scala3/guides.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: inner-page-parent -title: Guides on Scala 3 -language: zh-cn -scala3: true - -guides: - - title: "从 Scala 2 到 Scala 3 的迁移" - icon: suitcase - url: "/scala3/guides/migration/compatibility-intro.html" - description: "迁移至 Scala 3 的兼容性须知" - - title: 宏 - by: Nicolas Stucki - icon: magic - url: "/scala3/guides/macros" - description: "覆盖 Scala 3 中涉及到编写宏的所有特性的详细导引" - label-text: 特性 - - title: TASTy 概览 - by: Alvin Alexander - icon: birthday-cake - url: "/scala3/guides/tasty-overview.html" - description: "针对 Scala 语言最终用户的 TASTy 格式概览" - - title: "贡献指南" - by: Jamie Thompson, Anatolii Kmetiuk - icon: cogs - url: "/scala3/guides/contribution/contribution-intro.html" - description: "Scala 3 编译器指南及如何贡献代码" - - title: Scaladoc - by: Krzysztof Romanowski, Aleksander Boruch-Gruszecki, Andrzej Ratajczak, Kacper Korban, Filip Zybała - icon: book - url: "/scala3/guides/scaladoc" - description: "Scala 的 API 文档生成工具" ---- - -
-
-
-
-

概览与导引

-

- Scala 3 语言及其特性的详细导引 -

-{% include scala3-guides-card-group.html %} -
-
-
-
diff --git a/_zh-cn/scala3/guides/tasty-overview.md b/_zh-cn/scala3/guides/tasty-overview.md new file mode 100644 index 0000000000..9ab7cc3124 --- /dev/null +++ b/_zh-cn/scala3/guides/tasty-overview.md @@ -0,0 +1,146 @@ +--- +layout: singlepage-overview +title: TASTy 概览 +--- +假定你创建了一个 Scala 3 源代码文件叫 _Hello.scala_: + +```scala +@main def hello = println("Hello, world") +``` + +然后用 `scalac` 编译了该文件: + +```bash +$ scalac Hello.scala +``` + +你会发现在 `scalac` 生成的其它文件结果中,有些文件是以 _.tasty_ 为扩展名: + +```bash +$ ls -1 +Hello$package$.class +Hello$package.class +Hello$package.tasty +Hello.scala +hello.class +hello.tasty +``` + +这自然地会引出一个问题,“什么是 tasty?” + +## 什么是 TASTy? + +TASTy 是从 _Typed Abstract Syntax Trees_ 这个术语的首字母缩写来的。它是 Scala 3 的高级交换格式,在本文档中,我们将它称为 _Tasty_ 。 + +首先要知道的是,Tasty 文件是由 `scalac` 编译器生成的,并且包含 _所有_ 有关源代码的信息,这些信息包括程序的语法结构,以及有关类型,位置甚至文档的 _所有_ 信息。Tasty 文件包含的信息比 _.class_ 文件多得多,后者是为在 JVM 上运行而生成的。(后面有详细介绍)。 + +在 Scala 3 中,编译流程像这样: + +```text + +-------------+ +-------------+ +-------------+ +$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class | + +-------------+ +-------------+ +-------------+ + ^ ^ ^ + | | | + 你的代码 TASTy 文件 Class 文件 + 用于 scalac 用于 JVM + (包括完整信息) (不完整信息) +``` + +您可以通过使用 `-print-tasty` 标志在 _.tasty_ 文件上运行编译器,以人类可读的形式查看 _.tasty_ 文件的内容。 +您还可以使用 `-decompile` 标志以类似于 Scala 源代码的形式查看反编译的内容。 + +```bash +$ scalac -print-tasty hello.tasty +$ scalac -decompile hello.tasty +``` + +### The issue with _.class_ files + +由于象 [类型擦除][erasure] 等问题,_.class_ 文件实际上是代码的不完整表示形式。 +演示这一点的一种简单方法是使用 `List` 示例。 + +_类型擦除_ 意味着当你编写这样的Scala代码,并假定它是在JVM上运行时: + +```scala +val xs: List[Int] = List(1, 2, 3) +``` + +该代码被编译为需要与 JVM 兼容的 _.class_ 文件。由于该兼容性要求,该类文件中的代码 --- 您可以使用 `javap` 命令看到它,--- 最终看起来像这样: + +```java +public scala.collection.immutable.List xs(); +``` + +该 `javap` 命令输出显示了类文件中包含的内容,该内容是 Java 的表示形式。请注意,在此输出中,`xs` _不是_ 定义为 `List[Int]` ;它真正表示的是 `List[java.lang.Object]` 。为了使您的 Scala 代码与 JVM 配合使用,`Int` 类型已被擦除。 + +稍后,当您在 Scala 代码中访问 `List[Int]` 的元素时,像这样: + +```scala +val x = xs(0) +``` + +生成的类文件对此行代码进行强制转换操作,您可以将其想象成: + +``` +int x = (Int) xs.get(0) // Java-ish +val x = xs.get(0).asInstanceOf[Int] // more Scala-like +``` + +同样,这样做是为了兼容性,因此您的 Scala 代码可以在 JVM 上运行。但是,我们已经有的整数列表的信息在类文件中丢失了。 +当尝试使用已编译的库来编译 Scala 程序时,会带来问题。为此,我们需要的信息比类文件中通常可用的信息更多。 + +此讨论仅涵盖类型擦除的主题。对于 JVM 没有意识到的所有其他 Scala 结构,也存在类似的问题,包括 unions, intersections, 带有参数的 traits 以及更多 Scala 3 特性。 + +### TASTy to the Rescue + +因此,TASTy 格式不是像 _.class_ 文件那样没有原始类型的信息,或者只有公共 API(如Scala 2.13 “Pickle” 格式),而是在类型检查后存储完整的抽象语法树(AST)。存储整个 AST 有很多优点:它支持单独编译,针对不同的 JVM 版本重新编译,程序的静态分析等等。 + +### 重点 + +因此,这是本节的第一个要点:您在 Scala 代码中指定的类型在 _.class_ 文件中没有完全准确地表示。 + +第二个关键点是要了解 _编译时_ 和 _运行时_ 提供的信息之间存在差异: + +- 在**编译时**,当 `scalac` 读取和分析你的代码时,它知道 `xs` 是一个 `List[Int]` +- 当编译器将你的代码写入类文件时,它会写 `xs` 是 `List[Object]` ,并在访问 `xs` 的任何地方添加转换信息 +- 然后在**运行时** --- 你的代码在 JVM 中运行,--- JVM 不知道你的列表是一个 `List[Int]` + +对于 Scala 3 和 Tasty,这里有一个关于编译时的重要说明: + +- 当您编写使用其他 Scala 3 库的 Scala 3 代码时,`scalac` 不必再读取其 _.class_ 文件;它可以读取其 _.tasty_ 文件,如前所述,这些文件是代码的 _准确_ 表示形式。这对于在 Scala 2.13 和 Scala 3 之间实现单独编译和兼容性非常重要。 + +## Tasty 的好处 + +可以想象,拥有代码的完整表示形式具有[许多好处][benefits]: + +- 编译器使用它来支持单独的编译。 +- Scala 基于 _Language Server Protocol_ 的语言服务器使用它来支持超链接、命令补全、文档以及全局操作,如查找引用和重命名。 +- Tasty 为新一代[基于反射的宏][macros]奠定了良好的基础。 +- 优化器和分析器可以使用它进行深度代码分析和高级代码生成。 + +在相关的说明中,Scala 2.13.6 有一个 TASTy 读取器,Scala 3 编译器也可以读取2.13“Pickle”格式。Scala 3 迁移指南中的 [类路径兼容性页面][compatibility-ref] 解释了此交叉编译功能的好处。 + +## 更多信息 + +总之,Tasty 是 Scala 3 的高级交换格式,_.tasty_ 文件包含源代码的完整表示形式,从而带来了上一节中概述的好处。 + +有关更多详细信息,请参阅以下资源: + +- 在 [此视频](https://www.youtube.com/watch?v=YQmVrUdx8TU) 中,Scala 中心的Jamie Thompson 对 Tasty 的工作原理及其优势进行了详尽的讨论 +- [库作者的二进制兼容性][binary] 讨论二进制兼容性、源代码兼容性和 JVM 执行模型 +- [Scala 3 Transition 的前向兼容性](https://www.scala-lang.org/blog/2020/11/19/scala-3-forward-compat.html) 演示了在同一项目中使用 Scala 2.13 和 Scala 3 的技术 + +这些文章提供了有关 Scala 3 宏的更多信息: + +- [Scala 宏库](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) +- [宏:Scala 3 的计划](https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html) +- [Quotes Reflect 的参考文档][quotes-reflect] +- [宏的参考文档][macros] + +[benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html +[erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure +[binary]: {% link _overviews/tutorials/binary-compatibility-for-library-authors.md %} +[compatibility-ref]: {% link _overviews/scala3-migration/compatibility-classpath.md %} +[quotes-reflect]: {{ site.scala3ref }}/metaprogramming/reflection.html +[macros]: {{ site.scala3ref }}/metaprogramming/macros.html diff --git a/_zh-cn/scala3/new-in-scala3.md b/_zh-cn/scala3/new-in-scala3.md new file mode 100644 index 0000000000..5f82276635 --- /dev/null +++ b/_zh-cn/scala3/new-in-scala3.md @@ -0,0 +1,122 @@ +--- +layout: singlepage-overview +title: Scala 3 里的新东西 +scala3: true +--- +令人振奋的新版 Scala 3 带来了许多改进和新功能。在这里,我们为你提供最重要的变更的快速概述。如果你想深入挖掘,还有一些参考资料供你使用: + +- [Scala 3 Book]({% link _overviews/scala3-book/introduction.md %}) 面向刚接触 Scala 语言的开发人员。 +- [Syntax Summary][syntax-summary] 为您提供了新语法的正式描述。 +- [Language Reference][reference] 对 Scala 2 到 Scala 3 的变化做了详细说明。 +- [Migration Guide][migration] 为你提供了从 Scala 2 迁移到 Scala 3 的所有必要信息。 +- [Scala 3 Contributing Guide][contribution] Scala 3 贡献指南,更深入地探讨了编译器,包括修复问题的指南。 + +## Scala 3 里有什么新东西 +Scala 3 是对 Scala 语言的一次彻底改造。在其核心部分,类型系统的许多方面都被改变了,变得更有原则性。虽然这也带来了令人兴奋的新功能(比如联合类型),但首先意味着类型系统变得(甚至)不那么碍事了,例如[类型推断][type-inference]和 overload resolution 都得到了很大的改善。 + +### 新的和闪亮的:语法 +除了许多(小的)清理工作,Scala 3 的语法还提供了以下改进: + +- 用于控制结构的新“quiet”语法,如 `if`、`while` 和 `for` 。 ([new control syntax][syntax-control]) +- `new` 关键字是可选的 (_aka_ [creator applications][creator]) +- [Optional braces][syntax-indentation]:可选的大括号,支持不受干扰、缩进敏感的编程风格 +- [类型级通配符][syntax-wildcard] 从 `_` 更改为 `?`。 +- implicit(和它们的语法)已被[大量修订][implicits]。 + +### Opinionated: 上下文抽象 +Scala的一个基本核心概念是(在某种程度上仍然是)为用户提供一小部分强大的功能,这些功能可以被组合成巨大的(有时甚至是不可预见的)表达能力。例如,_implicit_ 的特性被用来模拟上下文抽象、表达类型级计算、模拟类型类、执行隐式强制、编码扩展方法等等。从这些用例中学习,Scala 3 采取了一种略微不同的方法,专注于 **意图** 而非 **机制**。Scala 3 没有提供一个非常强大的功能,而是提供了多个定制的语言功能,让程序员直接表达他们的意图。 + +- **Abtracting over contextual information**. [Using clauses][contextual-using] 允许程序员对调用上下文中的信息进行抽象,这些信息应该以隐式方式传递。作为对 Scala 2 implicits 的改进,可以按类型指定 using 子句,从而将函数签名从从未显式引用的术语变量名中解放出来。 + +- **Providing Type-class instances**. [Given instances][contextual-givens] 允许程序员定义某个类型的 _规范值_ 。这使得使用类型类的编程更加简单,而不会泄露实现细节。 + +- **Retroactively extending classes**. 在 Scala 2 中,扩展方法必须使用隐式转换或隐式类进行编码。相比之下,在 Scala 3 中,[extension methods][contextual-extension]现在直接内置于语言中,从而产生更好的错误消息和改进的类型推断。 + +- **Viewing one type as another**. [隐式转换][contextual-conversions]已经被重新设计为类型类`Conversion`的实例。 + +- **Higher-order contextual abstractions**. [context functions][contextual-functions]的 _全新_ 功能使上下文抽象成为第一等公民。它们是库开发人员的一个重要工具,允许表达简洁的特定领域语言。 + +- **Actionable feedback from the compiler**. 如果一个隐式参数不能被编译器解决,它现在提供了可能解决这个问题的[import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html)。 + +### 表达真正的意思: 类型系统改进 +除了极大地改进了类型推断,Scala 3 类型系统还提供了许多新的功能,还为你提供了强大的工具来静态地表达类型中的不变量: + +- **Enumerations**. [枚举][enums]已经被重新设计,以便与样例类很好地融合,并形成表达[代数数据类型][enums-adts]的新标准。 + +- **Opaque Types**. 将实现细节隐藏在[opaque type aliases][types-opaque]的别名后面,而不需要在性能上付出代价! Opaque types 取代了值类,并允许你建立一个抽象的屏障,而不会造成额外的装箱开销。 + +- **Intersection and union types**. 将类型系统建立在新的基础上,引入了新的类型系统特性:[intersection types][types-intersection]的实例,如`A & B`,既是`A`的实例,也是`B`的实例;[union types][types-union]的实例,如`A | B`,是`A`或`B`的实例。这两种结构都允许程序员在继承层次结构之外灵活地表达类型约束。 + +- **Dependent function types**. Scala 2 已经允许返回类型依赖于(值)参数。在 Scala 3 中,现在可以对这种模式进行抽象,表达[dependent function types][types-dependent]。在类型`F = (e: Entry) => e.Key`中,结果类型取决于参数。 + +- **Polymorphic function types**. 与 dependent function types 一样,Scala 2 支持拥有类型参数的方法,但不允许程序员对这些方法进行抽象。在 Scala 3 中,像`[A] => List[A] => List[A]`这样的[polymorphic function types][types-polymorphic]可以抽象出除值参数外还接受 _类型参数_ 的函数。 + +- **Type lambdas**. 在 Scala 2 中需要用[编译器插件](https://github.com/typelevel/kind-projector)来表达的东西,现在在 Scala 3 中是原生支持的功能:类型lambdas是类型级别的函数,可以作为(高等类型的)类型参数传递,而不需要辅助类型定义。 + +- **Match types**. Scala 3 提供了对[matching on types][types-match]的直接支持,而不是使用隐式解析对类型级别的计算进行编码。将类型级计算整合到类型检查器中,可以改进错误信息,并消除对复杂编码的需求。 + +### Re-envisioned:面向对象的编程 +Scala 一直处于函数式编程和面向对象编程的前沿 -- 而 Scala 3 在这两个方向上都推动了边界的发展! 上述类型系统的变化和上下文抽象的重新设计使得 _函数式编程_ 比以前更容易。同时,以下的新特性使结构良好的 _面向对象设计_ 成为可能,并支持最佳实践。 + +- **Pass it on**. Trait 更接近于 class,现在也可以接受[参数][oo-trait-parameters],使其作为模块化软件分解的工具更加强大。 + +- **Plan for extension**. 在面向对象的设计中,扩展那些不打算扩展的类是一个长期存在的问题。为了解决这个问题,[open classes][oo-open]要求库设计者 _明确地_ 将类标记为 open(开放的)。 + +- **Hide implementation details**. 实现功能的工具性的traits有时不应该是推断类型的一部分。在 Scala 3 中,这些traits可以被标记为[transparent][oo-transparent],(在推断类型中)向用户隐藏继承信息。 + +- **Composition over inheritance**. 这句话经常被引用,但实现起来却很繁琐。Scala 3 的[export clauses][oo-export]则不然:与imports对应,export clauses 允许用户为对象的选定成员定义别名。 + +- **No more NPEs**. Scala 3 比以往任何时候都更安全:[explicit null][oo-explicit-null]将`null`移出了类型层次结构,帮助你静态地捕捉错误;[safe initialization][oo-safe-init]的额外检查可以检测对未初始化对象的访问。 + +### Batteries Included: 元编程 +Scala 2 中的宏只是一个实验性的功能,而 Scala 3 则为元编程提供了强大的工具库。[宏教程]({% link _overviews/scala3-macros/tutorial/index.md %})中包含了关于不同设施的详细信息。特别是,Scala 3 为元编程提供了以下功能: + +- **Inline**. [inline feature][meta-inline]允许在编译时化简值和方法。这个简单的功能已经涵盖了许多使用情况,同时也为更高级的功能提供了入口。 +- **Compile-time operations**. 包[`scala.compiletime`][meta-compiletime]中包含了额外的功能,可以用来实现内联方法。 +- **Quoted code blocks**. Scala 3为代码增加了[quasi-quotation][meta-quotes]的新功能,这为构建和分析代码提供了方便的高级接口。构建加一加一的代码就像`'{ 1 + 1 }`一样简单。 +- **Reflection API**. 对于更高级的用例,[quotes.reflect][meta-reflection]提供了更详细的控制来检查和生成程序树。 + +如果你想进一步了解 Scala 3 中的元编程,我们邀请你参阅我们的[教程][meta-tutorial]。 + +[enums]: {{ site.scala3ref }}/enums/enums.html +[enums-adts]: {{ site.scala3ref }}/enums/adts.html + +[types-intersection]: {{ site.scala3ref }}/new-types/intersection-types.html +[types-union]: {{ site.scala3ref }}/new-types/union-types.html +[types-dependent]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[types-lambdas]: {{ site.scala3ref }}/new-types/type-lambdas.html +[types-polymorphic]: {{ site.scala3ref }}/new-types/polymorphic-function-types.html +[types-match]: {{ site.scala3ref }}/new-types/match-types.html +[types-opaque]: {{ site.scala3ref }}/other-new-features/opaques.html + +[type-inference]: {{ site.scala3ref }}/changed-features/type-inference.html +[overload-resolution]: {{ site.scala3ref }}/changed-features/overload-resolution.html +[reference]: {{ site.scala3ref }}/overview.html +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[migration]: {% link _overviews/scala3-migration/compatibility-intro.md %} +[contribution]: {% link _overviews/scala3-contribution/contribution-intro.md %} + +[implicits]: {{ site.scala3ref }}/contextual +[contextual-using]: {{ site.scala3ref }}/contextual/using-clauses.html +[contextual-givens]: {{ site.scala3ref }}/contextual/givens.html +[contextual-extension]: {{ site.scala3ref }}/contextual/extension-methods.html +[contextual-conversions]: {{ site.scala3ref }}/contextual/conversions.html +[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html + +[syntax-summary]: {{ site.scala3ref }}/syntax.html +[syntax-control]: {{ site.scala3ref }}/other-new-features/control-syntax.html +[syntax-indentation]: {{ site.scala3ref }}/other-new-features/indentation.html +[syntax-wildcard]: {{ site.scala3ref }}/changed-features/wildcards.html + +[meta-tutorial]: {% link _overviews/scala3-macros/tutorial/index.md %} +[meta-inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[meta-compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[meta-quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[meta-reflection]: {% link _overviews/scala3-macros/tutorial/reflection.md %} + +[oo-explicit-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html +[oo-safe-init]: {{ site.scala3ref }}/other-new-features/safe-initialization.html +[oo-trait-parameters]: {{ site.scala3ref }}/other-new-features/trait-parameters.html +[oo-open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[oo-transparent]: {{ site.scala3ref }}/other-new-features/transparent-traits.html +[oo-export]: {{ site.scala3ref }}/other-new-features/export.html diff --git a/_zh-cn/scala3/reference/README.md b/_zh-cn/scala3/reference/README.md new file mode 100644 index 0000000000..960554be5f --- /dev/null +++ b/_zh-cn/scala3/reference/README.md @@ -0,0 +1,5 @@ +https://docs.scala-lang.org/scala3/reference 的页面内容是从 [Scala 3 编译器库](https://github.com/scala/scala3) 生成的。 + +请到这里为 Scala 3 参考文档做贡献: + +https://github.com/scala/scala3/tree/main/docs/_docs diff --git a/_zh-cn/thanks.md b/_zh-cn/thanks.md index e1429b74f0..e2fac4be7a 100644 --- a/_zh-cn/thanks.md +++ b/_zh-cn/thanks.md @@ -1,7 +1,8 @@ --- -layout: inner-page-no-masthead +layout: singlepage-overview language: zh-cn title: 致谢名单 +orphanTranslation: true --- 2013年10月份起,CSDN CODE开始组织志愿者翻译Scala官方文档。计划翻译的文档主要为Scala官网上overview部分的内容,包含以下部分: diff --git a/_zh-cn/tour/automatic-closures.md b/_zh-cn/tour/automatic-closures.md deleted file mode 100644 index b51652c94e..0000000000 --- a/_zh-cn/tour/automatic-closures.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: tour -title: Automatic Closures -partof: scala-tour - -language: zh-cn ---- diff --git a/_zh-cn/tour/basics.md b/_zh-cn/tour/basics.md index c46e11c857..a7f9c3bf0a 100644 --- a/_zh-cn/tour/basics.md +++ b/_zh-cn/tour/basics.md @@ -14,16 +14,14 @@ previous-page: tour-of-scala ## 在浏览器上尝试Scala -你可以在浏览器上使用ScalaFiddle运行Scala。 +你可以在浏览器上使用Scastie运行Scala。 -1. 打开[https://scalafiddle.io](https://scalafiddle.io); +1. 打开[Scastie](https://scastie.scala-lang.org/); 2. 在左侧窗格中粘贴`println("Hello, world!")`; 3. 点击"Run"按钮,输出将展现在右侧窗格中。 这是一种简单的、零设置的方法来实践Scala的代码片段。 -这篇文档中的大部分代码示例与 ScalaFiddle 进行了集成,可以通过点击 “Run” 按钮即来直接运行 Scala 代码。 - ## 表达式 表达式是可计算的语句。 @@ -32,14 +30,12 @@ previous-page: tour-of-scala ``` 你可以使用`println`来输出表达式的结果。 -{% scalafiddle %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` -{% endscalafiddle %} ### 常量(`Values`) @@ -110,21 +106,17 @@ println({ 你也可以给函数命名。 -{% scalafiddle %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` -{% endscalafiddle %} 函数可带有多个参数。 -{% scalafiddle %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} 或者不带参数。 @@ -139,23 +131,19 @@ println(getTheAnswer()) // 42 方法由`def`关键字定义。`def`后面跟着一个名字、参数列表、返回类型和方法体。 -{% scalafiddle %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` -{% endscalafiddle %} 注意返回类型是怎么在函数列表和一个冒号`: Int`之后声明的。 方法可以接受多个参数列表。 -{% scalafiddle %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` -{% endscalafiddle %} 或者没有参数列表。 @@ -168,7 +156,6 @@ println("Hello, " + name + "!") 方法也可以有多行的表达式。 -{% scalafiddle %} ```scala mdoc def getSquareString(input: Double): String = { val square = input * input @@ -176,7 +163,6 @@ def getSquareString(input: Double): String = { } println(getSquareString(2.5)) // 6.25 ``` -{% endscalafiddle %} 方法体的最后一个表达式就是方法的返回值。(Scala中也有一个`return`关键字,但是很少使用) @@ -221,15 +207,15 @@ val yetAnotherPoint = Point(2, 2) ```scala mdoc if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") + println(s"$point and $anotherPoint are the same.") } else { - println(point + " and " + anotherPoint + " are different.") + println(s"$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.") + println(s"$point and $yetAnotherPoint are the same.") } else { - println(point + " and " + yetAnotherPoint + " are different.") + println(s"$point and $yetAnotherPoint are different.") } // Point(1,2) and Point(2,2) are different. ``` @@ -276,7 +262,6 @@ trait Greeter { 特质也可以有默认的实现。 -{% scalafiddle %} ```scala mdoc:reset trait Greeter { def greet(name: String): Unit = @@ -301,7 +286,6 @@ greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer? ``` -{% endscalafiddle %} 这里,`DefaultGreeter`仅仅继承了一个特质,它还可以继承多个特质。 diff --git a/_zh-cn/tour/case-classes.md b/_zh-cn/tour/case-classes.md index cbff24d6e9..15a2dbf4cb 100644 --- a/_zh-cn/tour/case-classes.md +++ b/_zh-cn/tour/case-classes.md @@ -1,6 +1,6 @@ --- layout: tour -title: 案例类(Case Classes) +title: 样例类(Case Classes) partof: scala-tour num: 10 @@ -11,18 +11,18 @@ next-page: pattern-matching previous-page: multiple-parameter-lists --- -案例类(Case classes)和普通类差不多,只有几点关键差别,接下来的介绍将会涵盖这些差别。案例类非常适合用于不可变的数据。下一节将会介绍他们在[模式匹配](pattern-matching.html)中的应用。 +样例类(Case classes)和普通类差不多,只有几点关键差别,接下来的介绍将会涵盖这些差别。样例类非常适合用于不可变的数据。下一节将会介绍他们在[模式匹配](pattern-matching.html)中的应用。 -## 定义一个案例类 -一个最简单的案例类定义由关键字`case class`,类名,参数列表(可为空)组成: +## 定义一个样例类 +一个最简单的样例类定义由关键字`case class`,类名,参数列表(可为空)组成: ```scala mdoc case class Book(isbn: String) val frankenstein = Book("978-0486282114") ``` -注意在实例化案例类`Book`时,并没有使用关键字`new`,这是因为案例类有一个默认的`apply`方法来负责对象的创建。 +注意在实例化样例类`Book`时,并没有使用关键字`new`,这是因为样例类有一个默认的`apply`方法来负责对象的创建。 -当你创建包含参数的案例类时,这些参数是公开(public)的`val` +当你创建包含参数的样例类时,这些参数是公开(public)的`val` ``` case class Message(sender: String, recipient: String, body: String) val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?") @@ -30,10 +30,10 @@ 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 ``` -你不能给`message1.sender`重新赋值,因为它是一个`val`(不可变)。在案例类中使用`var`也是可以的,但并不推荐这样。 +你不能给`message1.sender`重新赋值,因为它是一个`val`(不可变)。在样例类中使用`var`也是可以的,但并不推荐这样。 ## 比较 -案例类在比较的时候是按值比较而非按引用比较: +样例类在比较的时候是按值比较而非按引用比较: ``` case class Message(sender: String, recipient: String, body: String) @@ -44,7 +44,7 @@ val messagesAreTheSame = message2 == message3 // true 尽管`message2`和`message3`引用不同的对象,但是他们的值是相等的,所以`message2 == message3`为`true`。 ## 拷贝 -你可以通过`copy`方法创建一个案例类实例的浅拷贝,同时可以指定构造参数来做一些改变。 +你可以通过`copy`方法创建一个样例类实例的浅拷贝,同时可以指定构造参数来做一些改变。 ``` 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") diff --git a/_zh-cn/tour/extractor-objects.md b/_zh-cn/tour/extractor-objects.md index d6d75aaad8..c74d946482 100644 --- a/_zh-cn/tour/extractor-objects.md +++ b/_zh-cn/tour/extractor-objects.md @@ -18,7 +18,7 @@ import scala.util.Random object CustomerID { - def apply(name: String) = s"$name--${Random.nextLong}" + def apply(name: String) = s"$name--${Random.nextLong()}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") diff --git a/_zh-cn/tour/implicit-conversions.md b/_zh-cn/tour/implicit-conversions.md index 48049bc249..4c2d94cc0b 100644 --- a/_zh-cn/tour/implicit-conversions.md +++ b/_zh-cn/tour/implicit-conversions.md @@ -47,8 +47,8 @@ implicit def list2ordered[A](x: List[A]) ```scala mdoc import scala.language.implicitConversions -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) +implicit def int2Integer(x: Int): Integer = + Integer.valueOf(x) ``` 因为如果不加选择地使用隐式转换可能会导致陷阱,编译器会在编译隐式转换定义时发出警告。 diff --git a/_zh-cn/tour/implicit-parameters.md b/_zh-cn/tour/implicit-parameters.md index aa264c086d..e8e89451e6 100644 --- a/_zh-cn/tour/implicit-parameters.md +++ b/_zh-cn/tour/implicit-parameters.md @@ -18,7 +18,7 @@ Scala 将查找这些参数的位置分为两类: * Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。 * 然后,它在所有伴生对象中查找与隐式候选类型相关的有隐式标记的成员。 -更加详细的关于 Scala 到哪里查找隐式参数的指南请参考 [常见问题](//docs.scala-lang.org/tutorials/FAQ/finding-implicits.html) +更加详细的关于 Scala 到哪里查找隐式参数的指南请参考 [常见问题](/tutorials/FAQ/finding-implicits.html) 在下面的例子中,我们定义了一个方法 `sum`,它使用 Monoid 类的 `add` 和 `unit` 方法计算一个列表中元素的总和。 请注意,隐式值不能是顶级值。 diff --git a/_zh-cn/tour/inner-classes.md b/_zh-cn/tour/inner-classes.md index d8fede0742..a390c0b340 100644 --- a/_zh-cn/tour/inner-classes.md +++ b/_zh-cn/tour/inner-classes.md @@ -19,7 +19,7 @@ previous-page: lower-type-bounds class Graph { class Node { var connectedNodes: List[Node] = Nil - def connectTo(node: Node) { + def connectTo(node: Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -63,7 +63,7 @@ node1.connectTo(node3) // illegal! class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil - def connectTo(node: Graph#Node) { + def connectTo(node: Graph#Node): Unit = { if (!connectedNodes.exists(node.equals)) { connectedNodes = node :: connectedNodes } @@ -77,4 +77,3 @@ class Graph { } } ``` - diff --git a/_zh-cn/tour/pattern-matching.md b/_zh-cn/tour/pattern-matching.md index dce4cd0e69..36ad508a7c 100644 --- a/_zh-cn/tour/pattern-matching.md +++ b/_zh-cn/tour/pattern-matching.md @@ -42,9 +42,9 @@ matchTest(1) // one ``` 这个`match`表达式是String类型的,因为所有的情况(case)均返回String,所以`matchTest`函数的返回值是String类型。 -## 案例类(case classes)的匹配 +## 样例类(case classes)的匹配 -案例类非常适合用于模式匹配。 +样例类非常适合用于模式匹配。 ```scala mdoc abstract class Notification @@ -58,7 +58,7 @@ case class VoiceRecording(contactName: String, link: String) extends Notificatio ``` -`Notification` 是一个虚基类,它有三个具体的子类`Email`, `SMS`和`VoiceRecording`,我们可以在这些案例类(Case Class)上像这样使用模式匹配: +`Notification` 是一个虚基类,它有三个具体的子类`Email`, `SMS`和`VoiceRecording`,我们可以在这些样例类(Case Class)上像这样使用模式匹配: ``` def showNotification(notification: Notification): String = { @@ -147,5 +147,5 @@ def findPlaceToSit(piece: Furniture): String = piece match { ## 备注 -Scala的模式匹配语句对于使用[案例类(case classes)](case-classes.html)表示的类型非常有用,同时也可以利用[提取器对象(extractor objects)](extractor-objects.html)中的`unapply`方法来定义非案例类对象的匹配。 +Scala的模式匹配语句对于使用[样例类(case classes)](case-classes.html)表示的类型非常有用,同时也可以利用[提取器对象(extractor objects)](extractor-objects.html)中的`unapply`方法来定义非样例类对象的匹配。 diff --git a/_zh-cn/tour/tour-of-scala.md b/_zh-cn/tour/tour-of-scala.md index df7288f109..78b70dd115 100644 --- a/_zh-cn/tour/tour-of-scala.md +++ b/_zh-cn/tour/tour-of-scala.md @@ -13,7 +13,7 @@ next-page: basics ## 欢迎来到Scala之旅 本次 Scala 之旅教程包含了对于大多数 Scala 特性的简单介绍。主要针对 Scala 这门语言的初学者。 -这是个简化的教程,如果希望得到完整的话,可以考虑购买[书籍](/books.html)或者参考[其他资源](/learn.html)。 +这是个简化的教程,如果希望得到完整的话,可以考虑购买[书籍](/books.html)或者参考[其他资源](/online-courses.html)。 ## Scala是什么? Scala是一门现代的多范式语言,志在以简洁、优雅及类型安全的方式来表达常用的编程模型。它平滑地集成了面向对象和函数式语言的特性。 diff --git a/_zh-cn/tour/unified-types.md b/_zh-cn/tour/unified-types.md index b3d9055168..904684e4dc 100644 --- a/_zh-cn/tour/unified-types.md +++ b/_zh-cn/tour/unified-types.md @@ -59,7 +59,7 @@ true ```scala mdoc val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) +val y: Float = x.toFloat // 9.8765434E8 (note that some precision is lost in this case) val face: Char = '☺' val number: Int = face // 9786 @@ -69,7 +69,7 @@ val number: Int = face // 9786 ``` val x: Long = 987654321 -val y: Float = x // 9.8765434E8 +val y: Float = x.toFloat // 9.8765434E8 val z: Long = y // Does not conform ``` diff --git a/_zh-cn/tutorials/scala-for-java-programmers.md b/_zh-cn/tutorials/scala-for-java-programmers.md index 00559b596d..a3327a1963 100644 --- a/_zh-cn/tutorials/scala-for-java-programmers.md +++ b/_zh-cn/tutorials/scala-for-java-programmers.md @@ -18,7 +18,7 @@ Lightsing 译 这里用标准的 *Hello world* 程序作为第一个例子。虽然它很无趣,但让我们可以用少量语言特质来演示 Scala 工具。程序如下: object HelloWorld { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { println("Hello, world!") } } @@ -60,7 +60,7 @@ Java 的标准函数库定义了一些有用的工具类,如 `Date` 跟 `DateF import java.text.DateFormat._ object FrenchDate { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format now) @@ -114,7 +114,7 @@ Scala 中的函数也是对象,所以将函数当做对象传递、把它们 def timeFlies() { println("time flies like an arrow...") } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } } @@ -129,7 +129,7 @@ Scala 中的函数也是对象,所以将函数当做对象传递、把它们 def oncePerSecond(callback: () => Unit) { while (true) { callback(); Thread sleep 1000 } } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(() => println("time flies like an arrow...")) } @@ -157,7 +157,7 @@ Scala 中的函数也是对象,所以将函数当做对象传递、把它们 函数 `re`、`im` 有个小问题,为了调用函数,我们必须在函数名称后面加上一对空括号,如这个例子: object ComplexNumbers { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val c = new Complex(1.2, 3.4) println("imaginary part: " + c.im()) } @@ -261,7 +261,7 @@ Java 中我们会将这个树用一个抽象父类表示,然后每种节点跟 我们还没有探讨完模式匹配的全部功能,不过为了让这份文件保持简短,先就此打住。我们还是希望能看到这两个函数在真正的示例如何作用。因此让我们写一个简单的 `main` 函数,对表达式 `(x+x)+(7+y)` 做一些操作:先在环境 `{ x -> 5, y -> 7 }` 下计算结果,然后在对 `x` 接着对 `y` 取导数。 - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { 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) @@ -361,7 +361,7 @@ Scala 借由可定义泛型类 (跟函数) 来解决这问题。让我们借由 为了使用 `Reference` 类型,我们必须指定 `T`,也就是这容器所包容的元素类型。举例来说,创造并使用该容器来容纳整数,我们可以这样写: object IntegerReference { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) diff --git a/_zh-tw/tutorials/scala-for-java-programmers.md b/_zh-tw/tutorials/scala-for-java-programmers.md index d9b4bcd4d0..333744ecea 100755 --- a/_zh-tw/tutorials/scala-for-java-programmers.md +++ b/_zh-tw/tutorials/scala-for-java-programmers.md @@ -18,7 +18,7 @@ Chikei Lee 譯 這邊用標準的 *Hello world* 程式作為第一個例子。雖然它很無趣,可是這讓我們在僅用少量語言特性下演示 Scala 工具。程式如下: object HelloWorld { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { println("Hello, world!") } } @@ -60,7 +60,7 @@ Java 的標準函式庫定義了一些有用的工具類別,如 `Date` 跟 `Da import java.text.DateFormat._ object FrenchDate { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format now) @@ -114,7 +114,7 @@ Scala 是一個純粹的物件導向語言,這句話的意思是說,*所有 def timeFlies() { println("time flies like an arrow...") } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } } @@ -129,7 +129,7 @@ Scala 是一個純粹的物件導向語言,這句話的意思是說,*所有 def oncePerSecond(callback: () => Unit) { while (true) { callback(); Thread sleep 1000 } } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { oncePerSecond(() => println("time flies like an arrow...")) } @@ -157,7 +157,7 @@ Scala 是一個純粹的物件導向語言,這句話的意思是說,*所有 函式 `re`、`im` 有個小問題,為了呼叫函式,我們必須在函式名稱後面加上一對空括號,如這個例子: object ComplexNumbers { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val c = new Complex(1.2, 3.4) println("imaginary part: " + c.im()) } @@ -261,7 +261,7 @@ Java 中我們會將這個樹用一個抽象母類別表示,然後每種節點 我們還沒有探討完模式匹配的全部功能,不過為了讓這份文件保持簡短,先就此打住。我們還是希望能看到這兩個函式在真正的範例如何作用。因此讓我們寫一個簡單的 `main` 函數,對表示式 `(x+x)+(7+y)` 做一些操作:先在環境 `{ x -> 5, y -> 7 }` 下計算結果,然後在對 `x` 接著對 `y` 取導數。 - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { 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) @@ -361,7 +361,7 @@ Scala 藉由可定義泛型類別 (跟函式) 來解決這問題。讓我們藉 為了使用 `Reference` 類型,我們必須指定 `T`,也就是這容器所包容的元素型別。舉例來說,創造並使用該容器來容納整數,我們可以這樣寫: object IntegerReference { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) diff --git a/api/all.md b/api/all.md index ea55e544d9..6f049664b7 100644 --- a/api/all.md +++ b/api/all.md @@ -2,24 +2,28 @@ layout: singlepage-overview title: Scala API Docs includeTOC: true +redirect_from: + - /reference.html --- ## Latest releases -* Scala 3.1.1 - * [Library API](https://www.scala-lang.org/api/3.1.1/) -* Scala 2.13.8 - * [Library API](https://www.scala-lang.org/api/2.13.8/) - * [Compiler API](https://www.scala-lang.org/api/2.13.8/scala-compiler/scala/) - * [Reflection API](https://www.scala-lang.org/api/2.13.8/scala-reflect/scala/reflect/) -* Scala 2.12.15 - * [Library API](https://www.scala-lang.org/api/2.12.15/) - * [Compiler API](https://www.scala-lang.org/api/2.12.15/scala-compiler/scala/) - * [Reflection API](https://www.scala-lang.org/api/2.12.15/scala-reflect/scala/reflect/) +* Scala {{site.scala-3-version}} + * [Library API](https://www.scala-lang.org/api/{{site.scala-3-version}}/) +* Scala 3.3.6 LTS + * [Library API](https://www.scala-lang.org/api/3.3.6/) +* Scala 2.13.16 + * [Library API](https://www.scala-lang.org/api/2.13.16/) + * [Compiler API](https://www.scala-lang.org/api/2.13.16/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.16/scala-reflect/scala/reflect/) +* Scala 2.12.20 + * [Library API](https://www.scala-lang.org/api/2.12.20/) + * [Compiler API](https://www.scala-lang.org/api/2.12.20/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.20/scala-reflect/scala/reflect/) * Scala Modules - * [XML API](https://www.scala-lang.org/api/2.12.15/scala-xml/scala/xml/) - * [Parser Combinators API](https://www.scala-lang.org/api/2.12.15/scala-parser-combinators/scala/util/parsing/) - * [Swing API](https://www.scala-lang.org/api/2.12.15/scala-swing/scala/swing/) + * [XML API](https://www.scala-lang.org/api/2.12.20/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.20/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.20/scala-swing/scala/swing/) * Scala 2.11.12 * [Library API](https://www.scala-lang.org/api/2.11.12/) * [Compiler API](https://www.scala-lang.org/api/2.11.12/scala-compiler/) @@ -60,6 +64,50 @@ https://scala-ci.typesafe.com/artifactory/scala-integration/org/scala-lang/ ## Previous releases +* Scala 3.7.0 + * [Library API](https://www.scala-lang.org/api/3.7.0/) +* Scala 3.6.4 + * [Library API](https://www.scala-lang.org/api/3.6.4/) +* Scala 3.6.3 + * [Library API](https://www.scala-lang.org/api/3.6.3/) +* Scala 3.6.2 + * [Library API](https://www.scala-lang.org/api/3.6.2/) +* Scala 3.5.2 + * [Library API](https://www.scala-lang.org/api/3.5.2/) +* Scala 3.5.1 + * [Library API](https://www.scala-lang.org/api/3.5.1/) +* Scala 3.5.0 + * [Library API](https://www.scala-lang.org/api/3.5.0/) +* Scala 3.4.3 + * [Library API](https://www.scala-lang.org/api/3.4.3/) +* Scala 3.4.2 + * [Library API](https://www.scala-lang.org/api/3.4.2/) +* Scala 3.4.1 + * [Library API](https://www.scala-lang.org/api/3.4.1/) +* Scala 3.4.0 + * [Library API](https://www.scala-lang.org/api/3.4.0/) +* Scala 3.3.5 LTS + * [Library API](https://www.scala-lang.org/api/3.3.5/) +* Scala 3.3.4 LTS + * [Library API](https://www.scala-lang.org/api/3.3.4/) +* Scala 3.3.3 LTS + * [Library API](https://www.scala-lang.org/api/3.3.3/) +* Scala 3.3.1 LTS + * [Library API](https://www.scala-lang.org/api/3.3.1/) +* Scala 3.3.0 LTS + * [Library API](https://www.scala-lang.org/api/3.3.0/) +* Scala 3.2.2 + * [Library API](https://www.scala-lang.org/api/3.2.2/) +* Scala 3.2.1 + * [Library API](https://www.scala-lang.org/api/3.2.1/) +* Scala 3.2.0 + * [Library API](https://www.scala-lang.org/api/3.2.0/) +* Scala 3.1.3 + * [Library API](https://www.scala-lang.org/api/3.1.3/) +* Scala 3.1.2 + * [Library API](https://www.scala-lang.org/api/3.1.2/) +* Scala 3.1.1 + * [Library API](https://www.scala-lang.org/api/3.1.1/) * Scala 3.1.0 * [Library API](https://www.scala-lang.org/api/3.1.0/) * Scala 3.0.2 @@ -68,6 +116,38 @@ https://scala-ci.typesafe.com/artifactory/scala-integration/org/scala-lang/ * [Library API](https://www.scala-lang.org/api/3.0.1/) * Scala 3.0.0 * [Library API](https://www.scala-lang.org/api/3.0.0/) +* Scala 2.13.15 + * [Library API](https://www.scala-lang.org/api/2.13.15/) + * [Compiler API](https://www.scala-lang.org/api/2.13.15/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.15/scala-reflect/scala/reflect/) +* Scala 2.13.14 + * [Library API](https://www.scala-lang.org/api/2.13.14/) + * [Compiler API](https://www.scala-lang.org/api/2.13.14/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.14/scala-reflect/scala/reflect/) +* Scala 2.13.13 + * [Library API](https://www.scala-lang.org/api/2.13.13/) + * [Compiler API](https://www.scala-lang.org/api/2.13.13/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.13/scala-reflect/scala/reflect/) +* Scala 2.13.12 + * [Library API](https://www.scala-lang.org/api/2.13.12/) + * [Compiler API](https://www.scala-lang.org/api/2.13.12/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.12/scala-reflect/scala/reflect/) +* Scala 2.13.11 + * [Library API](https://www.scala-lang.org/api/2.13.11/) + * [Compiler API](https://www.scala-lang.org/api/2.13.11/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.11/scala-reflect/scala/reflect/) +* Scala 2.13.10 + * [Library API](https://www.scala-lang.org/api/2.13.10/) + * [Compiler API](https://www.scala-lang.org/api/2.13.10/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.10/scala-reflect/scala/reflect/) +* Scala 2.13.9 + * [Library API](https://www.scala-lang.org/api/2.13.9/) + * [Compiler API](https://www.scala-lang.org/api/2.13.9/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.9/scala-reflect/scala/reflect/) +* Scala 2.13.8 + * [Library API](https://www.scala-lang.org/api/2.13.8/) + * [Compiler API](https://www.scala-lang.org/api/2.13.8/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.13.8/scala-reflect/scala/reflect/) * Scala 2.13.7 * [Library API](https://www.scala-lang.org/api/2.13.7/) * [Compiler API](https://www.scala-lang.org/api/2.13.7/scala-compiler/scala/) @@ -100,6 +180,46 @@ https://scala-ci.typesafe.com/artifactory/scala-integration/org/scala-lang/ * [Library API](https://www.scala-lang.org/api/2.13.0/) * [Compiler API](https://www.scala-lang.org/api/2.13.0/scala-compiler/scala/) * [Reflection API](https://www.scala-lang.org/api/2.13.0/scala-reflect/scala/reflect/) +* Scala 2.12.19 + * [Library API](https://www.scala-lang.org/api/2.12.19/) + * [Compiler API](https://www.scala-lang.org/api/2.12.19/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.19/scala-reflect/scala/reflect/) + * Scala Modules + * [XML API](https://www.scala-lang.org/api/2.12.19/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.19/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.19/scala-swing/scala/swing/) +* Scala 2.12.18 + * [Library API](https://www.scala-lang.org/api/2.12.18/) + * [Compiler API](https://www.scala-lang.org/api/2.12.18/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.18/scala-reflect/scala/reflect/) + * Scala Modules + * [XML API](https://www.scala-lang.org/api/2.12.18/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.18/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.18/scala-swing/scala/swing/) +* Scala 2.12.17 + * [Library API](https://www.scala-lang.org/api/2.12.17/) + * [Compiler API](https://www.scala-lang.org/api/2.12.17/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.17/scala-reflect/scala/reflect/) + * Scala Modules + * [XML API](https://www.scala-lang.org/api/2.12.17/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.17/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.17/scala-swing/scala/swing/) +* Scala 2.12.16 + * [Library API](https://www.scala-lang.org/api/2.12.16/) + * [Compiler API](https://www.scala-lang.org/api/2.12.16/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.16/scala-reflect/scala/reflect/) + * Scala Modules + * [XML API](https://www.scala-lang.org/api/2.12.16/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.16/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.16/scala-swing/scala/swing/) +* Scala 2.12.15 + * [Library API](https://www.scala-lang.org/api/2.12.15/) + * [Compiler API](https://www.scala-lang.org/api/2.12.15/scala-compiler/scala/) + * [Reflection API](https://www.scala-lang.org/api/2.12.15/scala-reflect/scala/reflect/) + * Scala Modules + * [XML API](https://www.scala-lang.org/api/2.12.15/scala-xml/scala/xml/) + * [Parser Combinators API](https://www.scala-lang.org/api/2.12.15/scala-parser-combinators/scala/util/parsing/) + * [Swing API](https://www.scala-lang.org/api/2.12.15/scala-swing/scala/swing/) * Scala 2.12.14 * [Library API](https://www.scala-lang.org/api/2.12.14/) * [Compiler API](https://www.scala-lang.org/api/2.12.14/scala-compiler/scala/) diff --git a/books.md b/books.md index 0bdfff3358..3e33b863d5 100644 --- a/books.md +++ b/books.md @@ -1,12 +1,12 @@ --- title: Books -layout: inner-page +layout: basic-index 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. +More books about Scala are published every year. This is +only a selection of the available titles.
diff --git a/contribute.md b/contribute.md index 02a8e4b8d7..3ac938c17b 100644 --- a/contribute.md +++ b/contribute.md @@ -23,4 +23,4 @@ If you're interested in contributing to the Scala project in general, I argue th ## How Can I Contribute? -Please read: [Add New Guides/Tutorials](/contribute/add-guides.html) +Please read: [Add New Guides/Tutorials](https://docs.scala-lang.org/contribute/add-guides.html) diff --git a/docker-compose.yml b/docker-compose.yml index bfc376a362..75b1876399 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ -version: "2" services: - scala-lang: + jekyll: + user: "${UID}:${GID}" build: . - command: bundle exec jekyll serve --incremental --host=0.0.0.0 + command: sh -c "chown $UID / && bundle exec jekyll serve --incremental --host=0.0.0.0 " ports: - - 4000:4000 + - '4000:4000' volumes: - .:/srv/jekyll diff --git a/docker-compose_build-only.yml b/docker-compose_build-only.yml new file mode 100644 index 0000000000..615d0425a7 --- /dev/null +++ b/docker-compose_build-only.yml @@ -0,0 +1,9 @@ +version: '2' + +services: + jekyll: + user: "${UID}:${GID}" + build: . + command: sh -c "chown $UID / && bundle exec jekyll build" + volumes: + - .:/srv/jekyll diff --git a/guides.md b/guides.md deleted file mode 100644 index 98700b64b3..0000000000 --- a/guides.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: inner-page-no-masthead -title: Guides and Overviews -languages: [ja, zh-cn, es, ru] - ---- diff --git a/index.md b/index.md index 5790ce8b7c..22cbc790cc 100644 --- a/index.md +++ b/index.md @@ -1,77 +1,35 @@ --- -layout: documentation -languages: [ja, zh-cn, ru] -title: Documentation +layout: landing-page +languages: [ja, zh-cn, ru, uk] + +title: Learn Scala namespace: root discourse: true partof: documentation more-resources-label: More Resources +redirect_from: + - /scala3/ + - /scala3/index.html -scala3-sections: - - title: "First steps" - links: - - title: "New in Scala 3" - description: "An overview of the exciting new features in Scala 3." - icon: "fa fa-star" - link: /scala3/new-in-scala3.html - - title: "Getting Started" - description: "Install Scala 3 on your computer and start writing some Scala code!" - icon: "fa fa-rocket" - link: /scala3/getting-started.html - - title: "Scala 3 Book" - description: "Learn Scala by reading a series of short lessons." - icon: "fa fa-book-open" - link: /scala3/book/introduction.html - - title: "More detailed information" - links: - - title: "Migration Guide" - description: "A guide to help you move from Scala 2 to Scala 3." - icon: "fa fa-suitcase" - link: /scala3/guides/migration/compatibility-intro.html - - title: "Guides" - description: "Detailed guides about particular aspects of the language." - icon: "fa fa-map" - link: /scala3/guides.html - - title: "Scala Library API" - description: "API documentation for every version of the Scala 3 standard library." - icon: "fa fa-file-alt" - link: https://scala-lang.org/api/3.x/ - - title: "Language Reference" - description: "The Scala 3 language reference." - icon: "fa fa-book" - link: /scala3/reference/overview.html - - title: "Scala 3 Contributing Guide" - description: "Guide to the Scala 3 Compiler and fixing an issue" - icon: "fa fa-cogs" - link: /scala3/guides/contribution/contribution-intro.html - - title: "All new Scaladoc for Scala 3" - description: "Highlights of new features for Scaladoc" - icon: "fa fa-star" - link: /scala3/scaladoc.html - - title: "Talks" - description: "Talks about Scala 3 that can be watched online" - icon: "fa fa-play-circle" - link: /scala3/talks.html - - title: "Online Courses" - description: "MOOCs to learn Scala, for beginners and experienced programmers." - icon: "fa fa-cloud" - link: /online-courses.html - -scala2-sections: +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 + link: /getting-started/install-scala.html - title: "Tour of Scala" description: "Bite-sized introductions to core language features." icon: "fa fa-flag" link: /tour/tour-of-scala.html - - title: "Scala Book" + - title: "Scala 3 Book" description: "Learn Scala by reading a series of short lessons." icon: "fa fa-book-open" - link: /overviews/scala-book/introduction.html + link: /scala3/book/introduction.html + - title: "Scala Toolkit" + description: "Sending HTTP requests, writing files, running processes, parsing JSON..." + icon: "fa fa-toolbox" + link: /toolkit/introduction.html - title: Online Courses description: "MOOCs to learn Scala, for beginners and experienced programmers." icon: "fa fa-cloud" @@ -107,19 +65,46 @@ scala2-sections: description: "Answers to frequently-asked questions about Scala." icon: "fa fa-question-circle" link: /tutorials/FAQ/index.html - - title: "Language Spec" - description: "Scala's formal language specification." + - title: "Language Spec v2.x" + description: "Scala 2's formal language specification." icon: "fa fa-book" link: https://scala-lang.org/files/archive/spec/2.13/ + - title: "Language Spec v3.x" + description: "Scala 3's formal language specification." + icon: "fa fa-book" + link: https://scala-lang.org/files/archive/spec/3.4/ + - title: "Scala 3 Language Reference" + description: "The Scala 3 language reference." + icon: "fa fa-book" + link: https://docs.scala-lang.org/scala3/reference + + - title: "Explore Scala 3" + links: + - title: "Migration Guide" + description: "A guide to help you move from Scala 2 to Scala 3." + icon: "fa fa-suitcase" + link: /scala3/guides/migration/compatibility-intro.html + - title: "New in Scala 3" + description: "An overview of the exciting new features in Scala 3." + icon: "fa fa-star" + link: /scala3/new-in-scala3.html + - title: "All new Scaladoc for Scala 3" + description: "Highlights of new features for Scaladoc" + icon: "fa fa-star" + link: /scala3/scaladoc.html + - title: "Talks" + description: "Talks about Scala 3 that can be watched online" + icon: "fa fa-play-circle" + link: /scala3/talks.html - title: "Scala Evolution" links: - - title: "SIPs" - description: "The Scala Improvement Process. Language & compiler evolution." + - title: "Scala Improvement Process" + description: "Description of the process for evolving the language, and list of all the Scala Improvement Proposals (SIPs)." icon: "fa fa-cogs" link: /sips/index.html - - title: "Contributing to Scala" - description: "The Complete Guide to Contributing to the Scala Project" - icon: "fa fa-cogs" + - title: "Become a Scala OSS Contributor" + description: "From start to finish: discover how you can help Scala's open-source ecosystem" + icon: "fa fa-code-branch" link: /contribute/ --- diff --git a/learn.md b/learn.md deleted file mode 100644 index 531c2dea7a..0000000000 --- a/learn.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Online Resources -layout: singlepage-overview -redirect_from: - - /documentation/books.html ---- - -## 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 [ScalaFiddle](https://scalafiddle.io/) and [Scastie](https://scastie.org/). - -## Online courses from the Scala Center - -The [Scala Center](https://scala.epfl.ch) is committed to creating high-quality -and freely available online courses for learning Scala and functional -programming. The course levels range from beginner to advanced. -More details on [this page]({% link online-courses.md %}). - -## Scala 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. - -[Tour of Scala](https://tourofscala.com) gives you an introduction to Scala, step by step, from begineer to expert. - -## Dr. Mark C Lewis's lectures from Trinity University - -[Dr. Mark C Lewis](https://www.cs.trinity.edu/~mlewis/) from Trinity University, San Antonio, TX, teaches programming courses using the Scala language. Course videos are available in YouTube for free. Some of the courses below. - - * [Introduction to Programming and Problem Solving Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt9MIJ9DV4ps-_trOzWtphYO) - * [Object-Orientation, Abstraction, and Data Structures Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt8JLumqKj-3BlHmEXPIfR42) - -You can visit his [YouTube channel](https://www.youtube.com/user/DrMarkCLewis/featured) for more videos. - -## Scala Learning Community -[Scala Learning Community on Discord](http://sca.la/learning-community), a growing online community connecting learners with online resources to learn Scala together. - -## allaboutscala -[allaboutscala](https://allaboutscala.com/) provides detailed tutorials for beginners. - -## DevInsideYou -[DevInsideYou](https://youtube.com/devinsideyou) is a YouTube channel with hundreds of hours of free Scala content. - -## Visual Scala Reference -[Visual Scala Reference](https://superruzafa.github.io/visual-scala-reference/), a guide to visually learn about Scala concepts and functions. 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 38a941ac28..c4722a5bb3 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,5 +1,5 @@ --- -layout: inner-page-no-masthead +layout: singlepage-overview title: "Functional Programming Principles in Scala: Impressions and Statistics" --- ###### By Heather Miller and Martin Odersky @@ -33,7 +33,7 @@ Those of you reading this who were enrolled in the course might recall that, sev For example, as mentioned above, a success rate of 20% is quite high for an online course. One might suspect that it was because the course was very easy, but in our previous experience that's not the case. In fact, the online course is a direct adaptation of a 2nd year course that was given at EPFL for a number of years and that has a reputation of being rather tough. If anything, the material in the online course was a bit more compressed than in the previous on-campus class. -In particular, 57% of all respondents to the survey rated the overall course as being 3, "Just Right", on a scale from 1 to 5, with 1 being "Too Easy" and 5 being "Too Difficult. With regard to programming assignments specifically, 40% rated the assignments as being 3, "Just Right", while 46% rated assignments as being 4 "Challenging". +In particular, 57% of all respondents to the survey rated the overall course as being 3, "Just Right", on a scale from 1 to 5, with 1 being "Too Easy" and 5 being "Too Difficult". With regard to programming assignments specifically, 40% rated the assignments as being 3, "Just Right", while 46% rated assignments as being 4 "Challenging". Another point which might be particularly interesting is the fact that the difficulty rating appears to be independent of whether people have a background in Computer Science/Software Engineering or not. One might guess that this could mean that learning Scala is not much more difficult without a formal educational background in Computer Science. @@ -43,7 +43,7 @@ While a majority of the students in the course have degrees in Computer Science/
Participants' Fields of Study
 
-However, we were still interested to see how the formal education of participants influenced their assessment of the perceived difficulty. It turns out that, of those who have or have pursued university degrees— Bachelors or Masters degrees, there was almost no difference in perceived difficulty. The only marked differences appeared to the far left and the far right of the spectrum. +However, we were still interested to see how the formal education of participants influenced their assessment of the perceived difficulty. It turns out that, of those who have or have pursued university degrees— Bachelors or Master's degrees, there was almost no difference in perceived difficulty. The only marked differences appeared to the far left and the far right of the spectrum.
Perceived Difficulty Relative to Level of Education
 
Scale: 1 - Too Easy, 2 - Easy, 3 - Just Right, 4 - Challenging, 5 - Too Difficult

 

@@ -79,7 +79,7 @@ We also collected numbers about the used programming tools. First, we were inter
Which editor do you normally use, and which editor did you use for the course?
 
-The collected numbers are markedly different. In no small part this is due to the fact that the [Scala IDE for Eclipse](https://scala-ide.org) introduced a new [worksheet](https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started) component used throughout the lectures. +The collected numbers are markedly different. In no small part this is due to the fact that the Scala IDE for Eclipse introduced a new [worksheet](https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started) component used throughout the lectures. We'd like to close with some fun, and partially surprising, information on the demographics of those who took the course and completed our survey. Here is a world map showing the number of participants per country— darker colors indicate a larger number of students per-country: @@ -93,7 +93,7 @@ Here's that graph again, relating that population of students who enrolled in th ## Get the data and explore it with Scala! -For those of you who want to have a little bit of fun with the numbers, we've made all of the data publicly available, and we've made a small Scala project out of it. In particular, we put the code that we used to produce the above plots on [github (progfun-stats)](https://www.github.com/heathermiller/progfun-stats). +For those of you who want to have a little bit of fun with the numbers, we've made all the data publicly available, and we've made a small Scala project out of it. In particular, we put the code that we used to produce the above plots on [github (progfun-stats)](https://www.github.com/heathermiller/progfun-stats). For those of you who have taken the course and are itching for some fun additional exercises in functional programming, one of our suggestions is to tinker with and extend this project! You'll find the code examples for generating most of these plots available in this post, in the above repository. diff --git a/online-courses.md b/online-courses.md index 0266feb859..6b65a5115f 100644 --- a/online-courses.md +++ b/online-courses.md @@ -1,127 +1,43 @@ --- -title: Online Courses from The Scala Center -layout: singlepage-overview -scala3: true -testimonials: - - /resources/images/online-courses/testimonial000.jpg - - /resources/images/online-courses/testimonial001.jpg - - /resources/images/online-courses/testimonial002.jpg - - /resources/images/online-courses/testimonial003.jpg - - /resources/images/online-courses/testimonial004.jpg - - /resources/images/online-courses/testimonial005.jpg - - /resources/images/online-courses/testimonial006.jpg - - /resources/images/online-courses/testimonial007.jpg - - /resources/images/online-courses/testimonial008.jpg - - /resources/images/online-courses/testimonial009.jpg - - /resources/images/online-courses/testimonial010.jpg - - /resources/images/online-courses/testimonial011.jpg - - /resources/images/online-courses/testimonial012.jpg - - /resources/images/online-courses/testimonial013.jpg - - /resources/images/online-courses/testimonial014.jpg +title: Online Courses +layout: online-courses +redirect_from: + - /documentation/books.html + - /learn --- -The online courses from the [Scala Center] provide two main paths to learn -Scala. The fast path consists of taking the course [Effective Programming -in Scala], -otherwise you can take the full [Scala Specialization], which is made of -four courses and a capstone project. - -All the courses are available for free. Optionally, a subscription gives -you access to a certificate of completion that attests your accomplishments. -Learn more about -[Coursera certificates](https://learners.coursera.help/hc/en-us/articles/209819053-Get-a-Course-Certificate) or -[edX certificates](https://support.edx.org/hc/en-us/categories/115002269627-Certificates). -Your subscriptions also supports the work of the [Scala Center], whose mission -is to create high-quality educational material. - -You can learn more about the Scala Center online courses in the following video: - -
- -
- -## Scala Learning Path - -The diagram below summarizes the possible learning paths with our courses: - -![](/resources/images/learning-path.png) - -The “foundational” courses target programmers with no prior experience in Scala, whereas the “deepening” -courses aim at strengthening Scala programmers skills in a specific domain (such as parallel programming). - -We recommend starting with either Effective Programming in Scala, or Functional Programming Principles in -Scala followed by Functional Program Design. Then, you can complement your Scala skills by taking any -of the courses Programming Reactive Systems, Parallel Programming, or Big Data Analysis with Scala and Spark. -In case you take the Scala Specialization, you will end with the Scala Capstone Project. - -## Effective Programming in Scala - -[Effective Programming in Scala] teaches non-Scala programmers everything -they need to be ready to work in Scala. At the end of this hands-on course, -you will know how to achieve common programming tasks in Scala (e.g., -modeling business domains, implementing business logic, designing large -systems made of components, handling errors, manipulating data, running -concurrent tasks in parallel, testing your code). You can learn more about -this course in the following video: - -
- -
- -This course is also a good way to upgrade your Scala 2 knowledge to Scala 3. +## Other Online Resources -After taking this course, you might be interested in improving your -skills in specific areas by taking the courses [Parallel Programming], -[Big Data Analysis with Scala and Spark], or [Programming Reactive Systems]. +### Tour of Scala -## Scala Specialization +[Tour of Scala](https://tourofscala.com) is an interactive website that introduces the basics of Scala programming through a series of hands-on lessons. +Each lesson provides code examples and exercises that compiles and runs directly in the browser, making it a quick and accessible way to get started with Scala. -The [Scala Specialization] provides a hands-on introduction to functional programming using Scala. You can access the courses -material and exercises by either signing up for the specialization or auditing the courses individually. The -specialization has 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]. +In the [Scala Learning Discord](http://sca.la/learning-community), you can connect with fellow Scala learners and engage with the Tour of Scala community. -These courses provide a deep understanding of the Scala language itself, -and they also dive into more specific topics such as parallel programming, -and Spark. +### Scala Exercises -## Programming Reactive Systems +[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. +It also covers some libraries of the ecosystem such as cats, doobie, scalacheck etc. -[Programming Reactive Systems] (also available on [edX](https://www.edx.org/course/scala-akka-reactive)) -teaches how to write responsive, scalable, and resilient systems with the -library Akka. +### DevInsideYou -## Scala 2 Courses +[DevInsideYou](https://youtube.com/devinsideyou) is a YouTube channel with hundreds of hours of free Scala content. -The above courses all use Scala 3. If needed, you can find -the (legacy) Scala 2 version of our courses here: +### Visual Scala Reference -- [Functional Programming Principles in Scala (Scala 2 version)](https://www.coursera.org/learn/scala2-functional-programming) -- [Functional Program Design (Scala 2 version)](https://www.coursera.org/learn/scala2-functional-program-design) -- [Parallel Programming (Scala 2 version)](https://www.coursera.org/learn/scala2-parallel-programming) -- [Big Data Analysis with Scala and Spark (Scala 2 version)](https://www.coursera.org/learn/scala2-spark-big-data) -- [Programming Reactive Systems (Scala 2 version)](https://www.coursera.org/learn/scala2-akka-reactive) +[Visual Scala Reference](https://superruzafa.github.io/visual-scala-reference/) is a visual guide to the most common methods of the Scala collections. -## Testimonials +### allaboutscala -{% include carousel.html images=page.testimonials number=0 height="50" unit="%" duration="10" %} +[allaboutscala](https://allaboutscala.com/) provides detailed tutorials for beginners. -## Other Online Resources +### Dr. Mark C Lewis's lectures from Trinity University -You can find other online resources contributed by the community on -[this page]({% link learn.md %}). +[Dr. Mark C Lewis](https://www.cs.trinity.edu/~mlewis/) from Trinity University, San Antonio, TX, teaches programming courses using the Scala language. Course videos are available in YouTube for free. Some courses below. -[Scala Center]: https://scala.epfl.ch -[Scala Specialization]: https://www.coursera.org/specializations/scala -[Effective Programming in Scala]: https://www.coursera.org/learn/effective-scala -[Functional Programming Principles in Scala]: https://www.coursera.org/learn/scala-functional-programming -[Functional Program Design in Scala]: https://www.coursera.org/learn/scala-functional-program-design -[Parallel programming]: https://www.coursera.org/learn/scala-parallel-programming -[Big Data Analysis with Scala and Spark]: https://www.coursera.org/learn/scala-spark-big-data -[Functional Programming in Scala Capstone]: https://www.coursera.org/learn/scala-capstone -[Programming Reactive Systems]: https://www.coursera.org/learn/scala-akka-reactive +- [Introduction to Programming and Problem Solving Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt9MIJ9DV4ps-_trOzWtphYO) +- [Object-Orientation, Abstraction, and Data Structures Using Scala](https://www.youtube.com/playlist?list=PLLMXbkbDbVt8JLumqKj-3BlHmEXPIfR42) +You can visit his [YouTube channel](https://www.youtube.com/user/DrMarkCLewis/featured) for more videos. diff --git a/reference.md b/reference.md deleted file mode 100644 index e7a837825e..0000000000 --- a/reference.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: inner-page-no-masthead -sitemap: false -permalink: /reference.html -redirect_to: /api/all.html ---- diff --git a/resources/css/style.scss b/resources/css/style.scss index 57b121deba..5f30379a90 100755 --- a/resources/css/style.scss +++ b/resources/css/style.scss @@ -55,13 +55,16 @@ @import 'layout/training-events'; @import 'layout/blog'; @import 'layout/download'; +@import 'layout/online-courses'; @import 'layout/content-contributors'; // COMPONENTS +@import 'layout/details-summary'; //------------------------------------------------ //------------------------------------------------ @import 'components/buttons'; @import 'components/call-to-action'; @import 'components/slider'; @import 'components/heading-line'; +@import 'components/alt-details'; @import 'components/card'; @import 'components/calendar'; @import 'components/tooltip'; @@ -72,3 +75,4 @@ @import 'components/search'; @import 'components/dropdown'; @import 'components/wip-notice'; +@import 'components/heading-anchor.scss'; diff --git a/resources/images/getting-started/IntelliJScala.png b/resources/images/getting-started/IntelliJScala.png new file mode 100644 index 0000000000..c567da38df Binary files /dev/null and b/resources/images/getting-started/IntelliJScala.png differ diff --git a/resources/images/getting-started/VSCodeMetals.png b/resources/images/getting-started/VSCodeMetals.png new file mode 100644 index 0000000000..bdc5090726 Binary files /dev/null and b/resources/images/getting-started/VSCodeMetals.png differ diff --git a/resources/images/online-courses/coursera.png b/resources/images/online-courses/coursera.png new file mode 100644 index 0000000000..a329139aa0 Binary files /dev/null and b/resources/images/online-courses/coursera.png differ diff --git a/resources/images/online-courses/extension-school.png b/resources/images/online-courses/extension-school.png new file mode 100644 index 0000000000..94da532d8c Binary files /dev/null and b/resources/images/online-courses/extension-school.png differ diff --git a/resources/images/online-courses/rock-the-jvm.png b/resources/images/online-courses/rock-the-jvm.png new file mode 100644 index 0000000000..86b6512c0b Binary files /dev/null and b/resources/images/online-courses/rock-the-jvm.png differ diff --git a/resources/images/online-courses/testimonial000.jpg b/resources/images/scalacenter-courses/testimonial000.jpg similarity index 100% rename from resources/images/online-courses/testimonial000.jpg rename to resources/images/scalacenter-courses/testimonial000.jpg diff --git a/resources/images/online-courses/testimonial001.jpg b/resources/images/scalacenter-courses/testimonial001.jpg similarity index 100% rename from resources/images/online-courses/testimonial001.jpg rename to resources/images/scalacenter-courses/testimonial001.jpg diff --git a/resources/images/online-courses/testimonial002.jpg b/resources/images/scalacenter-courses/testimonial002.jpg similarity index 100% rename from resources/images/online-courses/testimonial002.jpg rename to resources/images/scalacenter-courses/testimonial002.jpg diff --git a/resources/images/online-courses/testimonial003.jpg b/resources/images/scalacenter-courses/testimonial003.jpg similarity index 100% rename from resources/images/online-courses/testimonial003.jpg rename to resources/images/scalacenter-courses/testimonial003.jpg diff --git a/resources/images/online-courses/testimonial004.jpg b/resources/images/scalacenter-courses/testimonial004.jpg similarity index 100% rename from resources/images/online-courses/testimonial004.jpg rename to resources/images/scalacenter-courses/testimonial004.jpg diff --git a/resources/images/online-courses/testimonial005.jpg b/resources/images/scalacenter-courses/testimonial005.jpg similarity index 100% rename from resources/images/online-courses/testimonial005.jpg rename to resources/images/scalacenter-courses/testimonial005.jpg diff --git a/resources/images/online-courses/testimonial006.jpg b/resources/images/scalacenter-courses/testimonial006.jpg similarity index 100% rename from resources/images/online-courses/testimonial006.jpg rename to resources/images/scalacenter-courses/testimonial006.jpg diff --git a/resources/images/online-courses/testimonial007.jpg b/resources/images/scalacenter-courses/testimonial007.jpg similarity index 100% rename from resources/images/online-courses/testimonial007.jpg rename to resources/images/scalacenter-courses/testimonial007.jpg diff --git a/resources/images/online-courses/testimonial008.jpg b/resources/images/scalacenter-courses/testimonial008.jpg similarity index 100% rename from resources/images/online-courses/testimonial008.jpg rename to resources/images/scalacenter-courses/testimonial008.jpg diff --git a/resources/images/online-courses/testimonial009.jpg b/resources/images/scalacenter-courses/testimonial009.jpg similarity index 100% rename from resources/images/online-courses/testimonial009.jpg rename to resources/images/scalacenter-courses/testimonial009.jpg diff --git a/resources/images/online-courses/testimonial010.jpg b/resources/images/scalacenter-courses/testimonial010.jpg similarity index 100% rename from resources/images/online-courses/testimonial010.jpg rename to resources/images/scalacenter-courses/testimonial010.jpg diff --git a/resources/images/online-courses/testimonial011.jpg b/resources/images/scalacenter-courses/testimonial011.jpg similarity index 100% rename from resources/images/online-courses/testimonial011.jpg rename to resources/images/scalacenter-courses/testimonial011.jpg diff --git a/resources/images/online-courses/testimonial012.jpg b/resources/images/scalacenter-courses/testimonial012.jpg similarity index 100% rename from resources/images/online-courses/testimonial012.jpg rename to resources/images/scalacenter-courses/testimonial012.jpg diff --git a/resources/images/online-courses/testimonial013.jpg b/resources/images/scalacenter-courses/testimonial013.jpg similarity index 100% rename from resources/images/online-courses/testimonial013.jpg rename to resources/images/scalacenter-courses/testimonial013.jpg diff --git a/resources/images/online-courses/testimonial014.jpg b/resources/images/scalacenter-courses/testimonial014.jpg similarity index 100% rename from resources/images/online-courses/testimonial014.jpg rename to resources/images/scalacenter-courses/testimonial014.jpg diff --git a/resources/images/sip/process-chart.png b/resources/images/sip/process-chart.png new file mode 100644 index 0000000000..c647901efd Binary files /dev/null and b/resources/images/sip/process-chart.png differ diff --git a/resources/images/sip/sip-stages.png b/resources/images/sip/sip-stages.png new file mode 100644 index 0000000000..981f8b8937 Binary files /dev/null and b/resources/images/sip/sip-stages.png differ diff --git a/resources/img/books/FPiS_93x116.jpg b/resources/img/books/FPiS_93x116.jpg new file mode 100644 index 0000000000..dac8f9d915 Binary files /dev/null and b/resources/img/books/FPiS_93x116.jpg differ diff --git a/resources/img/books/FPiS_93x116.png b/resources/img/books/FPiS_93x116.png deleted file mode 100644 index c942bd6bab..0000000000 Binary files a/resources/img/books/FPiS_93x116.png and /dev/null differ diff --git a/resources/img/books/scala_for_the_impatient.jpg b/resources/img/books/scala_for_the_impatient.jpg new file mode 100644 index 0000000000..c1dab91058 Binary files /dev/null and b/resources/img/books/scala_for_the_impatient.jpg differ diff --git a/resources/img/books/scala_for_the_impatient.png b/resources/img/books/scala_for_the_impatient.png deleted file mode 100644 index 012367038d..0000000000 Binary files a/resources/img/books/scala_for_the_impatient.png and /dev/null differ diff --git a/resources/js/functions.js b/resources/js/functions.js index e52eddd5cf..686a38f47b 100644 --- a/resources/js/functions.js +++ b/resources/js/functions.js @@ -65,14 +65,11 @@ $(document).ready(function() { // Highlight $(document).ready(function() { hljs.configure({ - languages: ["scala", "bash"] + languages: ["scala", "bash"], + noHighlightRe: /^hljs-skip$/i }) - hljs.initHighlighting(); -}); - -// Show Blog -$(".hide").click(function() { - $(".new-on-the-blog").hide(); + hljs.registerLanguage("scala", highlightDotty); + hljs.highlightAll(); }); // Documentation menu dropdown toggle @@ -147,7 +144,7 @@ $(document).ready(function() { old.empty(); // if there are no translations, hide language dropdown box - if (items.length <= 1) { + if (items.length === 0) { $("#dd").hide(); } }); @@ -280,6 +277,11 @@ $(document).ready(function() { autoId: true, numerate: false }); + const target = $('#sidebar-toc .active'); + if (target.length) { + const marginTop = $('#sidebar-toc .type-chapter').length ? 15 : 10; + $('#sidebar-toc').animate({scrollTop: target.position().top - marginTop}, 200); + }; } }); @@ -382,25 +384,151 @@ $(document).ready(function() { } }); +// Browser Storage Support (https://stackoverflow.com/a/41462752/2538602) +function storageAvailable(type) { + try { + var storage = window[type], + x = '__storage_test__'; + storage.setItem(x, x); + storage.removeItem(x); + return true; + } + catch (e) { + return false; + } +} + +// Store preference for Scala 2 vs 3 +$(document).ready(function() { + + const Storage = (namespace) => { + return ({ + getPreference(key, defaultValue) { + const res = localStorage.getItem(`${namespace}.${key}`); + return res === null ? defaultValue : res; + }, + setPreference(key, value, onChange) { + const old = this.getPreference(key, null); + if (old !== value) { // activate effect only if value changed. + localStorage.setItem(`${namespace}.${key}`, value); + onChange(old); + } + } + }); + }; + + function activateTab(tabs, value) { + // check the code tab corresponding to the preferred value + tabs.find('input[data-target=' + value + ']').prop("checked", true); + } + + /** Links all tabs created in Liquid templates with class ".tabs-$namespace" + * on the page together, such that + * changing a tab to some value will activate all other tab sections to + * also change to that value. + * Also records a preference for the tab in localStorage, so + * that when the page is refreshed, the same tab will be selected. + * On page load, selects the tab corresponding to stored value. + */ + function setupTabs(tabs, namespace, defaultValue, storage) { + const preferredValue = storage.getPreference(namespace, defaultValue); + + activateTab(tabs, preferredValue) + + // setup listeners to record new preferred Scala version. + tabs.find('input').on('change', function() { + // if checked then set the preferred version, and activate the other tabs on page. + if ($(this).is(':checked')) { + const parent = $(this).parent(); + const newValue = $(this).data('target'); + + storage.setPreference(namespace, newValue, _ => { + // when we set a new scalaVersion, find scalaVersionTabs except current one + // and activate those tabs. + activateTab(tabs.not(parent), newValue); + }); + + } + + }); + } + + function setupAlertCancel(alert, storage) { + const messageId = alert.data('message_id'); + let onHide = () => {}; + if (messageId) { + const key = `alert.${messageId}`; + const isHidden = storage.getPreference(key, 'show') === 'hidden'; + if (isHidden) { + alert.hide(); + } + onHide = () => storage.setPreference(key, 'hidden', _ => {}); + } + + + alert.find('.hide').click(function() { + alert.hide(), onHide(); + }); + } + + function setupAllTabs(storage) { + var scalaVersionTabs = $(".tabsection.tabs-scala-version"); + if (scalaVersionTabs.length) { + setupTabs(scalaVersionTabs, "scalaVersion", "scala-3", storage); + } + var buildToolTabs = $(".tabsection.tabs-build-tool"); + if (buildToolTabs.length) { + setupTabs(buildToolTabs, "buildTool", "scala-cli", storage); + } + } + + function setupAllAlertCancels(storage) { + var alertBanners = $(".new-on-the-blog.alert-warning"); + if (alertBanners.length) { + setupAlertCancel(alertBanners, storage); + } + } + + if (storageAvailable('localStorage')) { + const PreferenceStorage = Storage('org.scala-lang.docs.preferences'); + setupAllTabs(PreferenceStorage); + setupAllAlertCancels(PreferenceStorage); + } + +}); + // 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("Mac") != -1) osname = "macos"; 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) { +$(document).ready(function () { + // for each code snippet area, find the copy button, + // and add a click listener that will copy text from + // the code area to the clipboard + $(".code-snippet-area").each(function () { + var area = this; + $(area).children(".code-snippet-buttons").children("button.copy-button").click(function () { + var code = $(area).children(".code-snippet-display").children("code").text(); + window.navigator.clipboard.writeText(code); + }); + }); +}); + +$(document).ready(function () { + // click the get-started tab corresponding to the users OS. + var platformOSOptions = $(".tabsection.platform-os-options"); + if (platformOSOptions.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); + if (os === 'unix') { + os = 'linux'; + } + platformOSOptions.find('input[data-target=' + os + ']').prop("checked", true); } }); @@ -444,7 +572,7 @@ $('#filter-glossary-terms').focus(); // Loop through the comment list $(".glossary .toc-context > 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) { + if (jQuery(this).find("h3").text().search(new RegExp(filter, "i")) < 0) { $(this).fadeOut(); // Show the list item if the phrase matches and increase the count by 1 @@ -467,18 +595,18 @@ $('#filter-glossary-terms').focus(); //Footer scroll to top button -$(document).ready(function(){ - $(window).scroll(function(){ - if ($(this).scrollTop() > 100) { - $('#scroll-to-top-btn').fadeIn(); - } else { - $('#scroll-to-top-btn').fadeOut(); - } - }); - $('#scroll-to-top-btn').click(function(){ - $("html, body").animate({ scrollTop: 0 }, 600); - return false; - }); +$(document).ready(function(){ + $(window).scroll(function(){ + if ($(this).scrollTop() > 100) { + $('#scroll-to-top-btn').fadeIn(); + } else { + $('#scroll-to-top-btn').fadeOut(); + } + }); + $('#scroll-to-top-btn').click(function(){ + $("html, body").animate({ scrollTop: 0 }, 600); + return false; + }); }); //Contributors widget @@ -490,7 +618,7 @@ $(document).ready(function () { * - some files aren't prefixed with underscore, see rootFiles * - some files are placed in _overviews but rendered to its folder, see overviewsFolders */ - + let rootFiles = ['getting-started', 'learn', 'glossary']; let overviewsFolders = ['FAQ', 'cheatsheets', 'collections', 'compiler-options', 'core', 'jdk-compatibility', 'macros', 'parallel-collections', @@ -565,3 +693,15 @@ $(document).ready(function () { $('#contributors').html(contributorsHtml); }); }); + +$(document).ready(function() { + const icon = '' + const anchor = '' + + $('.content-primary.documentation').find('h1, h2, h3, h4, h5, h6').each(function() { + const id = $(this).attr('id'); + if (id) { + $(this).append($(anchor).attr('href', '#' + id).html(icon)); + } + }); +}); diff --git a/resources/js/hljs-scala3.js b/resources/js/hljs-scala3.js new file mode 100644 index 0000000000..e0be3758b8 --- /dev/null +++ b/resources/js/hljs-scala3.js @@ -0,0 +1,504 @@ +function highlightDotty(hljs) { + + // identifiers + const capitalizedId = /\b[A-Z][$\w]*\b/ + const alphaId = /[a-zA-Z$_][$\w]*/ + const op1 = /[^\s\w\d,;"'()[\]{}=:]/ + const op2 = /[^\s\w\d,;"'()[\]{}]/ + const compound = `[a-zA-Z$][a-zA-Z0-9$]*_${op2.source}` // e.g. value_= + const id = new RegExp(`(${compound}|${alphaId.source}|${op2.source}{2,}|${op1.source}+|\`.+?\`)`) + + // numbers + const hexDigit = '[a-fA-F0-9]' + const hexNumber = `0[xX](${hexDigit})+(_(${hexDigit})+)*[lL]?` + const wholePart = `(\\d+)(_\\d+)*` + const decPoint = `\\.\\d+` + const floatingSuffix = `([eE][-+]?\\d+)?[fFdD]?` + const intOrFloat = `${decPoint}${floatingSuffix}|\\b${wholePart}([lLfFdD]|(${decPoint})?${floatingSuffix})` + const number = new RegExp(`(-?)((\\b(${hexNumber})|${intOrFloat}))`) + + // Regular Keywords + // The "soft" keywords (e.g. 'using') are added later where necessary + const alwaysKeywords = { + $pattern: /(\w+|\?=>|\?{1,3}|=>>|=>|<:|>:|_|#|<-|\.nn)/, + keyword: + 'abstract case catch class def do else enum export extends final finally for given ' + + 'if implicit import lazy match new object package private protected override return ' + + 'sealed then throw trait true try type val var while with yield =>> => ?=> <: >: _ ? <- #', + literal: 'true false null this super', + built_in: '??? asInstanceOf isInstanceOf assert implicitly locally summon valueOf .nn' + } + const modifiers = 'abstract|final|implicit|override|private|protected|sealed' + + // End of class, enum, etc. header + const templateDeclEnd = /(\/[/*]|{|:(?= *\n)|\n(?! *(extends|with|derives)))/ + + // all the keywords + soft keywords, separated by spaces + function withSoftKeywords(kwd) { + return { + $pattern: alwaysKeywords.$pattern, + keyword: kwd + ' ' + alwaysKeywords.keyword, + literal: alwaysKeywords.literal, + built_in: alwaysKeywords.built_in + } + } + + // title inside of a complex token made of several parts, but colored as a type (e.g. class) + const TP_TITLE = { + className: 'type', + begin: id, + returnEnd: true, + keywords: alwaysKeywords.keyword, + literal: alwaysKeywords.literal, + built_in: alwaysKeywords.built_in + } + + // title inside of a complex token made of several parts (e.g. class) + const TITLE = { + className: 'title', + begin: id, + returnEnd: true, + keywords: alwaysKeywords.keyword, + literal: alwaysKeywords.literal, + built_in: alwaysKeywords.built_in + } + + // title that goes to the end of a simple token (e.g. val) + const TITLE2 = { + className: 'title', + begin: id, + excludeEnd: true, + endsWithParent: true + } + + const TYPED = { + begin: /: (?=[a-zA-Z()?])/, + end: /\/\/|\/\*|\n/, + endsWithParent: true, + returnEnd: true, + contains: [ + { + // works better than the usual way of defining keyword, + // in this specific situation + className: 'keyword', + begin: /\?\=>|=>>|[=:][><]|\?/, + }, + { + className: 'type', + begin: alphaId + } + ] + } + + const PROBABLY_TYPE = { + className: 'type', + begin: capitalizedId, + relevance: 0 + } + + const NUMBER = { + className: 'number', + begin: number, + relevance: 0, + } + + const CHAR = { + className: 'string', + begin: /\'([^'\\]|\\[\s\S])\'/, + relevance: 0, + } + + // type parameters within [square brackets] + const TPARAMS = { + begin: /\[/, end: /\]/, + keywords: { + $pattern: /<:|>:|[+-?_:]/, + keyword: '<: >: : + - ? _' + }, + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'type', + begin: alphaId + }, + ], + relevance: 3 + } + + // Class or method parameters declaration + const PARAMS = { + className: 'params', + begin: /\(/, end: /\)/, + excludeBegin: true, + excludeEnd: true, + keywords: withSoftKeywords('inline using'), + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + hljs.QUOTE_STRING_MODE, + PROBABLY_TYPE + ] + } + + // (using T1, T2, T3) + const CTX_PARAMS = { + className: 'params', + begin: /\(using (?!\w+:)/, end: /\)/, + excludeBegin: false, + excludeEnd: true, + relevance: 5, + keywords: withSoftKeywords('using'), + contains: [ + PROBABLY_TYPE + ] + } + + // String interpolation + const SUBST = { + className: 'subst', + variants: [ + { begin: /\$[a-zA-Z_]\w*/ }, + { + begin: /\${/, end: /}/, + contains: [ + NUMBER, + hljs.QUOTE_STRING_MODE + ] + } + ] + } + + // "string" or """string""", with or without interpolation + const STRING = { + className: 'string', + variants: [ + hljs.QUOTE_STRING_MODE, + { + begin: '"""', end: '"""', + contains: [hljs.BACKSLASH_ESCAPE], + relevance: 10 + }, + { + begin: alphaId.source + '"', end: '"', + contains: [hljs.BACKSLASH_ESCAPE, SUBST], + illegal: /\n/, + relevance: 5 + }, + { + begin: alphaId.source + '"""', end: '"""', + contains: [hljs.BACKSLASH_ESCAPE, SUBST], + relevance: 10 + } + ] + } + + // Class or method apply + const APPLY = { + begin: /\(/, end: /\)/, + excludeBegin: true, excludeEnd: true, + keywords: { + $pattern: alwaysKeywords.$pattern, + keyword: 'using ' + alwaysKeywords.keyword, + literal: alwaysKeywords.literal, + built_in: alwaysKeywords.built_in + }, + contains: [ + STRING, + NUMBER, + CHAR, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + PROBABLY_TYPE, + ] + } + + // @annot(...) or @my.package.annot(...) + const ANNOTATION = { + className: 'meta', + begin: `@${id.source}(\\.${id.source})*`, + contains: [ + APPLY, + hljs.C_BLOCK_COMMENT_MODE + ] + } + + // glob all non-whitespace characters as a "string" + const DIRECTIVE_VALUE = { + className: 'string', + begin: /\S+/, + } + + // glob all non-whitespace characters as a "type", so that we can highlight differently to values + const DIRECTIVE_KEY = { + className: 'type', + begin: /\S+/, + } + + // directives + const USING_DIRECTIVE = hljs.COMMENT('//>', '\n', { + contains: [ + { + begin: /using /, + end: /\s/, + keywords: 'using', + contains: [ + DIRECTIVE_KEY + ] + }, + DIRECTIVE_VALUE, + ] + }) + + // Documentation + const SCALADOC = hljs.COMMENT('/\\*\\*', '\\*/', { + contains: [ + { + className: 'doctag', + begin: /@[a-zA-Z]+/ + }, + // markdown syntax elements: + { + className: 'code', + variants: [ + { begin: /```.*\n/, end: /```/ }, + { begin: /`/, end: /`/ } + ], + }, + { + className: 'bold', + variants: [ + { begin: /\*\*/, end: /\*\*/ }, + { begin: /__/, end: /__/ } + ], + }, + { + className: 'emphasis', + variants: [ + { begin: /\*(?!([\*\s/])|([^\*]*\*[\*/]))/, end: /\*/ }, + { begin: /_/, end: /_/ } + ], + }, + { + className: 'bullet', // list item + begin: /- (?=\S)/, end: /\s/, + }, + { + begin: /\[.*?\]\(/, end: /\)/, + contains: [ + { + // mark as "link" only the URL + className: 'link', + begin: /.*?/, + endsWithParent: true + } + ] + } + ] + }) + + // Methods + const METHOD = { + className: 'function', + begin: `((${modifiers}|transparent|inline|infix) +)*def `, end: / =\s|\n/, + excludeEnd: true, + relevance: 5, + keywords: withSoftKeywords('inline infix transparent'), + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + TPARAMS, + CTX_PARAMS, + PARAMS, + TYPED, // prevents the ":" (declared type) to become a title + PROBABLY_TYPE, + TITLE + ] + } + + // Variables & Constants + const VAL = { + beginKeywords: 'val var', end: /[=:;\n/]/, + excludeEnd: true, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + TITLE2 + ] + } + + // Type declarations + const TYPEDEF = { + className: 'typedef', + begin: `((${modifiers}|opaque) +)*type `, end: /[=;\n]| ?[<>]:/, + excludeEnd: true, + keywords: withSoftKeywords('opaque'), + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + PROBABLY_TYPE, + TITLE, + ] + } + + // Given instances + const GIVEN = { + begin: `((${modifiers}|transparent|inline) +)*given `, end: / =|[=;\n]/, + excludeEnd: true, + keywords: withSoftKeywords('inline transparent given using with'), + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + PARAMS, + CTX_PARAMS, + PROBABLY_TYPE, + TITLE + ] + } + + // Extension methods + const EXTENSION = { + begin: /extension/, end: /(\n|def)/, + returnEnd: true, + keywords: 'extension implicit using', + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + CTX_PARAMS, + PARAMS, + PROBABLY_TYPE + ] + } + + // 'end' soft keyword + const END = { + begin: `end(?= (if|while|for|match|try|given|extension|this|val|${id.source})\\n)`, end: /\s/, + keywords: 'end' + } + + // Classes, traits, enums, etc. + const EXTENDS_PARENT = { + begin: ' extends ', end: /( with | derives |\/[/*])/, + endsWithParent: true, + returnEnd: true, + keywords: 'extends', + contains: [APPLY, PROBABLY_TYPE] + } + const WITH_MIXIN = { + begin: ' with ', end: / derives |\/[/*]/, + endsWithParent: true, + returnEnd: true, + keywords: 'with', + contains: [APPLY, PROBABLY_TYPE], + relevance: 10 + } + const DERIVES_TYPECLASS = { + begin: ' derives ', end: /\n|\/[/*]/, + endsWithParent: true, + returnEnd: true, + keywords: 'derives', + contains: [PROBABLY_TYPE], + relevance: 10 + } + + const CLASS = { + className: 'class', + begin: `((${modifiers}|open|case|transparent) +)*(class|trait|enum|object|package object)`, end: templateDeclEnd, + keywords: withSoftKeywords('open transparent'), + excludeEnd: true, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + TPARAMS, + CTX_PARAMS, + PARAMS, + EXTENDS_PARENT, + WITH_MIXIN, + DERIVES_TYPECLASS, + TITLE, + PROBABLY_TYPE + ] + } + + // package declaration with a content + const PACKAGE = { + className: 'package', + begin: /package (?=\w+ *[:{\n])/, end: /[:{\n]/, + excludeEnd: true, + keywords: alwaysKeywords, + contains: [ + TITLE + ] + } + + // Case in enum + const ENUM_CASE = { + begin: /case (?!.*=>)/, end: /\n/, + keywords: 'case', + excludeEnd: true, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + PARAMS, + EXTENDS_PARENT, + WITH_MIXIN, + DERIVES_TYPECLASS, + TP_TITLE, + PROBABLY_TYPE + ] + } + + // Case in pattern matching + const MATCH_CASE = { + begin: /case/, end: /=>|\n/, + keywords: 'case', + excludeEnd: true, + contains: [ + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + begin: /[@_]/, + keywords: { + $pattern: /[@_]/, + keyword: '@ _' + } + }, + NUMBER, + STRING, + PROBABLY_TYPE + ] + } + + // inline someVar[andMaybeTypeParams] match + const INLINE_MATCH = { + begin: /inline [^\n:]+ match/, + keywords: 'inline match' + } + + return { + name: 'Scala3', + aliases: ['scala', 'dotty'], + keywords: alwaysKeywords, + contains: [ + NUMBER, + CHAR, + STRING, + USING_DIRECTIVE, + SCALADOC, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE, + METHOD, + VAL, + TYPEDEF, + PACKAGE, + CLASS, + GIVEN, + EXTENSION, + ANNOTATION, + ENUM_CASE, + MATCH_CASE, + INLINE_MATCH, + END, + APPLY, + PROBABLY_TYPE + ] + } +} diff --git a/resources/js/tweetMachine-update.js b/resources/js/tweetMachine-update.js index a7604d2ba7..0addd1b951 100755 --- a/resources/js/tweetMachine-update.js +++ b/resources/js/tweetMachine-update.js @@ -133,11 +133,11 @@ }); // Usernames text = text.replace(/@[A-Za-z0-9_]+/g, function (u) { - return '' + u + ''; + return '' + u + ''; }); // Hashtags text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) { - return '' + u + ''; + return '' + u + ''; }); return text; }, @@ -160,7 +160,7 @@ // Set the user screen name var usernameLink = "" + "@" @@ -170,7 +170,7 @@ // Set the username: var userLink = "" + actualTweet.user.name @@ -178,7 +178,7 @@ tweetObj.find('.user').html("" + userLink); // Set the timestamp - var dateLink = "" + tweetMachine.relativeTime(actualTweet.created_at) @@ -411,4 +411,4 @@ } }); }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/scala3/contribute-to-docs.md b/scala3/contribute-to-docs.md index d39ba71ec4..3196600581 100644 --- a/scala3/contribute-to-docs.md +++ b/scala3/contribute-to-docs.md @@ -2,6 +2,7 @@ layout: singlepage-overview overview-name: "Scala 3 Documentation" title: Contributing to the Docs +languages: ["ja", "ru"] --- ## Overview There are several ongoing efforts to produce high quality documentation for @@ -53,10 +54,10 @@ contains a comprehensive overview over contribution to and internals of the Scal - [Issues](https://github.com/scala/docs.scala-lang/issues) ## Scala 3 Language Reference -The [Scala 3 reference](/scala3/reference/overview.html) contains a formal presentation and detailed technical information about the various features of the language. +The [Scala 3 reference]({{ site.scala3ref }}) contains a formal presentation and detailed technical information about the various features of the language. -- [Sources](https://github.com/scala/docs.scala-lang/tree/main/_scala3-reference) -- [Issues](https://github.com/scala/docs.scala-lang/issues) +- [Sources](https://github.com/scala/scala3/tree/main/docs/_docs) +- [Issues](https://github.com/scala/scala3/issues) [scala3-book]: {% link _overviews/scala3-book/introduction.md %} diff --git a/scala3/getting-started.md b/scala3/getting-started.md deleted file mode 100644 index b82ed44938..0000000000 --- a/scala3/getting-started.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: singlepage-overview -title: Getting Started -languages: ["ja"] ---- - -{% include getting-started.md %} diff --git a/scala3/guides.md b/scala3/guides.md deleted file mode 100644 index 26110b10ba..0000000000 --- a/scala3/guides.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: inner-page-parent -title: Guides on Scala 3 - -guides: - - title: "Migration from Scala 2 to Scala 3" - icon: suitcase - url: "/scala3/guides/migration/compatibility-intro.html" - description: "Everything you need to know about compatibility and migration to Scala 3." - - title: Macros - by: Nicolas Stucki - icon: magic - url: "/scala3/guides/macros" - description: "A detailed tutorial to cover all the features involved in writing macros in Scala 3." - label-text: feature - - title: An Overview of TASTy - by: Alvin Alexander - icon: birthday-cake - url: "/scala3/guides/tasty-overview.html" - description: "An overview over the TASTy format aimed at end-users of the Scala language." - - title: "Scala 3 Contributing Guide" - by: Jamie Thompson, Anatolii Kmetiuk - icon: cogs - url: "/scala3/guides/contribution/contribution-intro.html" - description: "Guide to the Scala 3 Compiler and fixing an issue" - - title: Scaladoc - by: Krzysztof Romanowski, Aleksander Boruch-Gruszecki, Andrzej Ratajczak, Kacper Korban, Filip Zybała - icon: book - url: "/scala3/guides/scaladoc" - description: "Scala’s API documentation generation tool." ---- - -
-
-
-
-

Overviews and Guides

-

- Detailed guides about the Scala 3 language and its features. -

-{% include scala3-guides-card-group.html %} -
-
-
-
diff --git a/scala3/guides/tasty-overview.md b/scala3/guides/tasty-overview.md index 1bf643bebd..245c822ee5 100644 --- a/scala3/guides/tasty-overview.md +++ b/scala3/guides/tasty-overview.md @@ -1,6 +1,10 @@ --- layout: singlepage-overview title: An Overview of TASTy +partof: tasty-overview +languages: ["uk"] +scala3: true +versionSpecific: true --- If you create a Scala 3 source code file named _Hello.scala_: @@ -35,7 +39,7 @@ This leads to the natural question, “What is tasty?” TASTy is an acronym that comes from the term, _Typed Abstract Syntax Trees_. It’s a high-level interchange format for Scala 3, and in this document we’ll refer to it as _Tasty_. -A first important thing to know is that Tasty files are generated by the `scalac` compiler, and contain _all_ of the information about your source code, including the syntactic structure of your program, and _complete_ information about types, positions, and even documentation. Tasty files contain much more information than _.class_ files, which are generated to run on the JVM. (More on this shortly.) +A first important thing to know is that Tasty files are generated by the `scalac` compiler, and contain _all_ the information about your source code, including the syntactic structure of your program, and _complete_ information about types, positions, and even documentation. Tasty files contain much more information than _.class_ files, which are generated to run on the JVM. (More on this shortly.) In Scala 3, the compilation process looks like this: @@ -76,7 +80,7 @@ that code is compiled to a _.class_ file that needs to be compatible with the JV public scala.collection.immutable.List xs(); ``` -That `javap` command output shows a Java representation of what’s contained in the class file. Notice in this output that `xs` _is not_ defined as a `List[Int]` any more; it’s essentially represented as a `List[java.lang.Object]`. For your Scala code to work with the JVM, the `Int` type has been erased. +That `javap` command output shows a Java representation of what’s contained in the class file. Notice in this output that `xs` _is not_ defined as a `List[Int]` anymore; it’s essentially represented as a `List[java.lang.Object]`. For your Scala code to work with the JVM, the `Int` type has been erased. Later, when you access an element of your `List[Int]` in your Scala code, like this: @@ -111,7 +115,7 @@ A second key point is to understand that there are differences between the infor With Scala 3 and Tasty, here’s another important note about compile time: -- When you write Scala 3 code that uses other Scala 3 libraries, `scalac` doesn’t have to read their _.class_ files any more; it can read their _.tasty_ files, which, as mentioned, are an _exact_ representation of your code. This is important to enable separate compilation and compatiblity between Scala 2.13 and Scala 3. +- When you write Scala 3 code that uses other Scala 3 libraries, `scalac` doesn’t have to read their _.class_ files anymore; it can read their _.tasty_ files, which, as mentioned, are an _exact_ representation of your code. This is important to enable separate compilation and compatibility between Scala 2.13 and Scala 3. @@ -134,7 +138,7 @@ In summary, Tasty is a high-level interchange format for Scala 3, and _.tasty_ f For more details, see these resources: -- In this [this video](https://www.youtube.com/watch?v=YQmVrUdx8TU), Jamie Thompson of the Scala Center provides a thorough discussion of how Tasty works, and its benefits +- In [this video](https://www.youtube.com/watch?v=YQmVrUdx8TU), Jamie Thompson of the Scala Center provides a thorough discussion of how Tasty works, and its benefits - [Binary Compatibility for library authors][binary] discusses binary compatibility, source compatibility, and the JVM execution model - [Forward Compatibility for the Scala 3 Transition](https://www.scala-lang.org/blog/2020/11/19/scala-3-forward-compat.html) demonstrates techniques for using Scala 2.13 and Scala 3 in the same project @@ -145,6 +149,11 @@ These articles provide more information about Scala 3 macros: - [The reference documentation on Quotes Reflect][quotes-reflect] - [The reference documentation on macros](macros) + +TASTyViz is a tool to inspect TASTy files visually. +At the time of writing, it is still in the early stages of developement, therefore you can expect missing functionality and less-than-ideal user experience, but it could still prove useful when debugging. +You can check it out [here](https://github.com/shardulc/tastyviz). + [benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html [erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure [binary]: {% link _overviews/tutorials/binary-compatibility-for-library-authors.md %} diff --git a/scala3/index.md b/scala3/index.md deleted file mode 100644 index a34c19a1ca..0000000000 --- a/scala3/index.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: inner-page-documentation -title: Documentation for Scala 3 -namespace: root -partof: scala3 -discourse: true -# Content masthead links -more-resources-label: More Resources -sections: - - - title: "First Steps" - links: - - title: "New in Scala 3" - description: "An overview of the exciting new features in Scala 3." - icon: "fa fa-star" - link: /scala3/new-in-scala3.html - - title: "Getting Started" - description: "Install Scala 3 on your computer and start writing some Scala code!" - icon: "fa fa-rocket" - link: /scala3/getting-started.html - - title: "Scala 3 Book" - description: "An online book introducing the main language features." - icon: "fa fa-book" - link: /scala3/book/introduction.html - - title: "More Detailed Information" - links: - - title: "Migration Guide" - description: "A guide to help you move from Scala 2.13 to Scala 3." - icon: "fa fa-suitcase" - link: /scala3/guides/migration/compatibility-intro.html - - title: "Guides" - description: "Detailed guides about particular aspects of the language." - icon: "fa fa-map" - link: /scala3/guides.html - - title: "API" - description: "API documentation for every version of Scala 3." - icon: "fa fa-file-alt" - link: https://scala-lang.org/api/3.x/ - - title: "Language Reference" - description: "The Scala 3 language reference." - icon: "fa fa-book" - link: /scala3/reference/overview.html - - title: "Scala 3 Contributing Guide" - description: "Guide to the Scala 3 Compiler and fixing an issue" - icon: "fa fa-cogs" - link: /scala3/guides/contribution/contribution-intro.html - - title: "All new Scaladoc for Scala 3" - description: "Highlights of new features for Scaladoc" - icon: "fa fa-star" - link: /scala3/scaladoc.html - - title: "Talks" - description: "Talks about Scala 3 that can be watched online" - icon: "fa fa-play-circle" - link: /scala3/talks.html - - title: "Online Courses" - description: "Online Courses on Scala 3" - icon: "fa fa-cloud" - link: /online-courses.html ---- diff --git a/scala3/new-in-scala3.md b/scala3/new-in-scala3.md index af6d6111c1..cc3c0add30 100644 --- a/scala3/new-in-scala3.md +++ b/scala3/new-in-scala3.md @@ -1,7 +1,7 @@ --- layout: singlepage-overview title: New in Scala 3 -languages: ["ja"] +languages: ["ja","zh-cn","uk","ru"] --- The exciting new version of Scala 3 brings many improvements and new features. Here we provide you with a quick overview of the most important @@ -10,7 +10,7 @@ changes. If you want to dig deeper, there are a few references at your disposal: - The [Scala 3 Book]({% link _overviews/scala3-book/introduction.md %}) targets developers new to the Scala language. - The [Syntax Summary][syntax-summary] provides you with a formal description of the new syntax. - The [Language Reference][reference] gives a detailed description of the changes from Scala 2 to Scala 3. -- The [Migration Guide][migration] provides you with all of the information necessary to move from Scala 2 to Scala 3. +- The [Migration Guide][migration] provides you with all the information necessary to move from Scala 2 to Scala 3. - The [Scala 3 Contributing Guide][contribution] dives deeper into the compiler, including a guide to fix issues. ## What's new in Scala 3 @@ -60,7 +60,7 @@ Besides greatly improved type inference, the Scala 3 type system also offers man - **Opaque Types**. Hide implementation details behind [opaque type aliases][types-opaque] without paying for it in performance! Opaque types supersede value classes and allow you to set up an abstraction barrier without causing additional boxing overhead. -- **Intersection and union types**. Basing the type system on new foundations led to the introduction of new type system features: instances of [intersection types][types-intersection], like `A & B`, are instances of _both_ `A` and of `B`. Instances of [union types][types-union], like `A | B`, are instances of _either_ `A` or `B`. Both constructs allow programmers to flexibly express type constraints outside of the inheritance hierarchy. +- **Intersection and union types**. Basing the type system on new foundations led to the introduction of new type system features: instances of [intersection types][types-intersection], like `A & B`, are instances of _both_ `A` and of `B`. Instances of [union types][types-union], like `A | B`, are instances of _either_ `A` or `B`. Both constructs allow programmers to flexibly express type constraints outside the inheritance hierarchy. - **Dependent function types**. Scala 2 already allowed return types to depend on (value) arguments. In Scala 3 it is now possible to abstract over this pattern and express [dependent function types][types-dependent]. In the type `type F = (e: Entry) => e.Key` the result type _depends_ on the argument! @@ -80,7 +80,7 @@ At the same time, the following novel features enable well-structured _object-or - **Plan for extension**. Extending classes that are not intended for extension is a long-standing problem in object-oriented design. To address this issue, [open classes][oo-open] require library designers to _explicitly_ mark classes as open. - **Hide implementation details**. Utility traits that implement behavior sometimes should not be part of inferred types. In Scala 3, those traits can be marked as [transparent][oo-transparent] hiding the inheritance from the user (in inferred types). - **Composition over inheritance**. This phrase is often cited, but tedious to implement. Not so with Scala 3's [export clauses][oo-export]: symmetric to imports, export clauses allow the user to define aliases for selected members of an object. -- **No more NPEs**. Scala 3 is safer than ever: [explicit null][oo-explicit-null] moves `null` out of the type hierarchy, helping you to catch errors statically; additional checks for [safe initialization][oo-safe-init] detect access to uninitialized objects. +- **No more NPEs (experimental)**. Scala 3 is safer than ever: [explicit null][oo-explicit-null] moves `null` out of the type hierarchy, helping you to catch errors statically; additional checks for [safe initialization][oo-safe-init] detect access to uninitialized objects. ### Batteries Included: Metaprogramming @@ -113,7 +113,7 @@ If you want to learn more about metaprogramming in Scala 3, we invite you to tak [migration]: {% link _overviews/scala3-migration/compatibility-intro.md %} [contribution]: {% link _overviews/scala3-contribution/contribution-intro.md %} -[implicits]: {{ site.scala3ref }}/contextual.html +[implicits]: {{ site.scala3ref }}/contextual [contextual-using]: {{ site.scala3ref }}/contextual/using-clauses.html [contextual-givens]: {{ site.scala3ref }}/contextual/givens.html [contextual-extension]: {{ site.scala3ref }}/contextual/extension-methods.html @@ -131,7 +131,7 @@ If you want to learn more about metaprogramming in Scala 3, we invite you to tak [meta-quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} [meta-reflection]: {% link _overviews/scala3-macros/tutorial/reflection.md %} -[oo-explicit-null]: {{ site.scala3ref }}/other-new-features/explicit-nulls.html +[oo-explicit-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html [oo-safe-init]: {{ site.scala3ref }}/other-new-features/safe-initialization.html [oo-trait-parameters]: {{ site.scala3ref }}/other-new-features/trait-parameters.html [oo-open]: {{ site.scala3ref }}/other-new-features/open-classes.html diff --git a/scala3/reference/README.md b/scala3/reference/README.md new file mode 100644 index 0000000000..418bdbe3e1 --- /dev/null +++ b/scala3/reference/README.md @@ -0,0 +1,6 @@ +The content of the pages https://docs.scala-lang.org/scala3/reference is +generated from the [Scala 3 compiler repository](https://github.com/scala/scala3). + +Please go here to contribute to the Scala 3 reference documentation: + +https://github.com/scala/scala3/tree/main/docs/_docs diff --git a/scala3/scaladoc.md b/scala3/scaladoc.md index 3b8e90b7d8..5a735173e3 100644 --- a/scala3/scaladoc.md +++ b/scala3/scaladoc.md @@ -1,11 +1,13 @@ --- layout: singlepage-overview title: New features for Scaladoc +partof: scala3-scaladoc +languages: ["uk","ru"] --- The new Scala version 3 comes with a completely new implementation of the documentation generator _Scaladoc_, rewritten from scratch. In this article you can find highlights of new features that are or will be introduced to Scaladoc. -For general reference, visit [Scaladoc manual](https://dotty.epfl.ch/docs/usage/scaladoc/) +For general reference, visit [Scaladoc manual]({% link _overviews/scala3-scaladoc/index.md %}). ## New features @@ -25,19 +27,19 @@ Thanks to this feature, you can store your documentation along-side with the gen For more information on how to configure the generation of static sites check out [Static documentation][static-documentation] chapter -![](../resources/images/scala3/scaladoc/static-site.png) +![](../../resources/images/scala3/scaladoc/static-site.png) ### Blog posts Blog posts are a specific type of static sites. In the Scaladoc manual you can find additional information about how to work with [blog posts][built-in-blog]. -![](../resources/images/scala3/scaladoc/blog-post.png) +![](../../resources/images/scala3/scaladoc/blog-post.png) ### Social links Furthermore, Scaladoc provides an easy way to configure your [social media links][social-links] e.g. Twitter or Discord. -![](../resources/images/scala3/scaladoc/social-links.png){: style="width: 180px"} +![](../../resources/images/scala3/scaladoc/social-links.png){: style="width: 180px"} ## Experimental features @@ -45,7 +47,7 @@ The following features are currently (May 2021) not stable to be released with s ### Snippet compiling -One of the experimental features of Scaladoc is a snippets compiler. This tool will allow you to compile snippets that you attach to your docstring +One of the experimental features of Scaladoc is a compiler for snippets. This tool will allow you to compile snippets that you attach to your docstring to check that they actually behave as intended, e.g., to properly compile. This feature is very similar to the `tut` or `mdoc` tools, but will be shipped with Scaladoc out of the box for easy setup and integration into your project. Making snippets interactive---e.g., letting users edit and compile them in the browser---is under consideration, though this feature is not in scope at this time. @@ -62,7 +64,7 @@ Searching for functions by their symbolic names can be time-consuming. That is why the new scaladoc allows you to search for methods and fields by their types. -So, for a declatation: +So, for a declaration: ``` extension [T](arr: IArray[T]) def span(p: T => Boolean): (IArray[T], IArray[T]) = ... ``` @@ -70,16 +72,16 @@ Instead of searching for `span` we can also search for `IArray[A] => (A => Boole To use this feature simply type the signature of the function you are looking for in the scaladoc searchbar. This is how it works: -![](../resources/images/scala3/scaladoc/inkuire-1.0.0-M2_js_flatMap.gif) +![](../../resources/images/scala3/scaladoc/inkuire-1.0.0-M2_js_flatMap.gif) This feature is provided by the [Inkuire](https://github.com/VirtusLab/Inkuire) search engine, which works for Scala 3 and Kotlin. To be up-to-date with the development of this feature, follow the [Inkuire repository](https://github.com/VirtusLab/Inkuire). For more information see [Guides](/scala3/guides/scaladoc/search-engine.html) Note that this feature is still in development, so it can be subject to considerable change. -If you encounter a bug or have an idea for improvement, don't hesitate to create an issue on [Inkuire](https://github.com/VirtusLab/Inkuire/issues/new) or [dotty](https://github.com/lampepfl/dotty/issues/new). +If you encounter a bug or have an idea for improvement, don't hesitate to create an issue on [Inkuire](https://github.com/VirtusLab/Inkuire/issues/new) or [scala3](https://github.com/scala/scala3/issues/new). [scaladoc-docstrings]: {% link _overviews/scala3-scaladoc/docstrings.md %} [static-documentation]: {% link _overviews/scala3-scaladoc/static-site.md %} [built-in-blog]: {% link _overviews/scala3-scaladoc/blog.md %} -[social-links]: https://dotty.epfl.ch/docs/usage/scaladoc/settings.html#-social-links +[social-links]: {% link _overviews/scala3-scaladoc/settings.md %}#-social-links diff --git a/scala3/talks.md b/scala3/talks.md index a26eb4034a..0cdba8b2d0 100644 --- a/scala3/talks.md +++ b/scala3/talks.md @@ -1,6 +1,10 @@ --- layout: singlepage-overview title: Talks +scala3: true +partof: scala3-talks +languages: ["uk","ru"] +versionSpecific: true --- Let’s Talk About Scala 3 Series @@ -12,13 +16,13 @@ advantage of the new language features, or how to migrate from Scala 2. Talks on Scala 3 ---------------- -- (ScalaDays 2019, Lausanne) [A Tour of Scala 3](https://www.youtube.com/watch?v=_Rnrx2lo9cw) by [Martin Odersky](http://twitter.com/odersky) [\[slides\]](https://www.slideshare.net/Odersky/a-tour-of-scala-3) +- (ScalaDays 2019, Lausanne) [A Tour of Scala 3](https://www.youtube.com/watch?v=_Rnrx2lo9cw) by [Martin Odersky](http://x.com/odersky) -- (ScalaDays 2016, Berlin) [Scala's Road Ahead](https://www.youtube.com/watch?v=GHzWqJKFCk4) by [Martin Odersky](http://twitter.com/odersky) [\[slides\]](http://www.slideshare.net/Odersky/scala-days-nyc-2016) +- (ScalaDays 2016, Berlin) [Scala's Road Ahead](https://www.youtube.com/watch?v=GHzWqJKFCk4) by [Martin Odersky](http://x.com/odersky) [\[slides\]](http://www.slideshare.net/Odersky/scala-days-nyc-2016) -- (JVMLS 2015) [Compilers are Databases](https://www.youtube.com/watch?v=WxyyJyB_Ssc) by [Martin Odersky](http://twitter.com/odersky) [\[slides\]](http://www.slideshare.net/Odersky/compilers-are-databases) +- (JVMLS 2015) [Compilers are Databases](https://www.youtube.com/watch?v=WxyyJyB_Ssc) by [Martin Odersky](http://x.com/odersky) [\[slides\]](http://www.slideshare.net/Odersky/compilers-are-databases) -- (Scala World 2015) [Dotty: Exploring the future of Scala](https://www.youtube.com/watch?v=aftdOFuVU1o) by [Dmitry Petrashko](http://twitter.com/darkdimius) [\[slides\]](https://d-d.me/scalaworld2015/#/). +- (Scala World 2015) [Dotty: Exploring the future of Scala](https://www.youtube.com/watch?v=aftdOFuVU1o) by [Dmitry Petrashko](http://x.com/darkdimius) [\[slides\]](https://d-d.me/scalaworld2015/#/). Dmitry covers many of the new features that Dotty brings on the table such as Intersection and Union types, improved lazy val initialization and more. Dmitry also covers dotty internals and in particular the high-level of contextual abstractions of Dotty. You will get to become familiar with many core concepts such as `Denotations`, their evolution through (compilation) time, their @@ -30,25 +34,25 @@ Deep Dive with Scala 3 - (ScalaDays 2019, Lausanne) [Future-proofing Scala: the TASTY intermediate representation](https://www.youtube.com/watch?v=zQFjC3zLYwo) by [Guillaume Martres](http://guillaume.martres.me/). -- (Mar 21, 2017) [Dotty Internals 1: Trees & Symbols](https://www.youtube.com/watch?v=yYd-zuDd3S8) by [Dmitry Petrashko](http://twitter.com/darkdimius) [\[meeting notes\]](https://dotty.epfl.ch/docs/internals/dotty-internals-1-notes.html). +- (Mar 21, 2017) [Dotty Internals 1: Trees & Symbols](https://www.youtube.com/watch?v=yYd-zuDd3S8) by [Dmitry Petrashko](http://x.com/darkdimius) [\[meeting notes\]](https://dotty.epfl.ch/docs/internals/dotty-internals-1-notes.html). This is a recorded meeting between EPFL and Waterloo, where we introduce first notions inside Dotty: Trees and Symbols. -- (Mar 21, 2017) [Dotty Internals 2: Types](https://www.youtube.com/watch?v=3gmLIYlGbKc) by [Martin Odersky](http://twitter.com/odersky) and [Dmitry Petrashko](http://twitter.com/darkdimius). +- (Mar 21, 2017) [Dotty Internals 2: Types](https://www.youtube.com/watch?v=3gmLIYlGbKc) by [Martin Odersky](http://x.com/odersky) and [Dmitry Petrashko](http://x.com/darkdimius). This is a recorded meeting between EPFL and Waterloo, where we introduce how types are represented inside Dotty. -- (Jun 15, 2017) [Dotty Internals 3: Denotations](https://youtu.be/9iPA7zMRGKY) by [Martin Odersky](http://twitter.com/odersky) and [Dmitry Petrashko](http://twitter.com/darkdimius). +- (Jun 15, 2017) [Dotty Internals 3: Denotations](https://youtu.be/9iPA7zMRGKY) by [Martin Odersky](http://x.com/odersky) and [Dmitry Petrashko](http://x.com/darkdimius). This is a recorded meeting between EPFL and Waterloo, where we introduce denotations in Dotty. -- (JVM Language Summit) [How do we make the Dotty compiler fast](https://www.youtube.com/watch?v=9xYoSwnSPz0) by [Dmitry Petrashko](http://twitter.com/darkdimius). - [Dmitry Petrashko](http://twitter.com/darkdimius) gives a high-level introduction on what was done to make Dotty . +- (JVM Language Summit) [How do we make the Dotty compiler fast](https://www.youtube.com/watch?v=9xYoSwnSPz0) by [Dmitry Petrashko](http://x.com/darkdimius). + [Dmitry Petrashko](http://x.com/darkdimius) gives a high-level introduction on what was done to make Dotty . - (Typelevel Summit Oslo, May 2016) [Dotty and types: the story so far](https://www.youtube.com/watch?v=YIQjfCKDR5A) by Guillaume Martres [\[slides\]](http://guillaume.martres.me/talks/typelevel-summit-oslo/). - Guillaume focused on some of the practical improvements to the type system that Dotty makes, like the new type parameter + Guillaume focused on some practical improvements to the type system that Dotty makes, like the new type parameter inference algorithm that is able to reason about the type safety of more situations than scalac. -- (flatMap(Oslo) 2016) [AutoSpecialization in Dotty](https://vimeo.com/165928176) by [Dmitry Petrashko](http://twitter.com/darkdimius) [\[slides\]](https://d-d.me/talks/flatmap2016/#/). +- (flatMap(Oslo) 2016) [AutoSpecialization in Dotty](https://vimeo.com/165928176) by [Dmitry Petrashko](http://x.com/darkdimius) [\[slides\]](https://d-d.me/talks/flatmap2016/#/). The Dotty Linker analyses your program and its dependencies to apply a new specialization scheme. It builds on our experience from Specialization, Miniboxing and the Valhalla Project, and drastically reduces the size of the emitted bytecode. And, best of all, it's always enabled, happens behind the @@ -66,4 +70,3 @@ Deep Dive with Scala 3 Dmitry introduces the call-graph analysis algorithm that Dotty implements and the performance benefits we can get in terms of number of methods, bytecode size, JVM code size and the number of objects allocated in the end. - diff --git a/scalacenter-courses.md b/scalacenter-courses.md new file mode 100644 index 0000000000..b242e6f5fe --- /dev/null +++ b/scalacenter-courses.md @@ -0,0 +1,169 @@ +--- +title: Online Courses (MOOCs) from The Scala Center +layout: singlepage-overview +languages: [ru] +testimonials: + - /resources/images/scalacenter-courses/testimonial000.jpg + - /resources/images/scalacenter-courses/testimonial001.jpg + - /resources/images/scalacenter-courses/testimonial002.jpg + - /resources/images/scalacenter-courses/testimonial003.jpg + - /resources/images/scalacenter-courses/testimonial004.jpg + - /resources/images/scalacenter-courses/testimonial005.jpg + - /resources/images/scalacenter-courses/testimonial006.jpg + - /resources/images/scalacenter-courses/testimonial007.jpg + - /resources/images/scalacenter-courses/testimonial008.jpg + - /resources/images/scalacenter-courses/testimonial009.jpg + - /resources/images/scalacenter-courses/testimonial010.jpg + - /resources/images/scalacenter-courses/testimonial011.jpg + - /resources/images/scalacenter-courses/testimonial012.jpg + - /resources/images/scalacenter-courses/testimonial013.jpg + - /resources/images/scalacenter-courses/testimonial014.jpg +--- + +The [Scala Center] produces online courses (a.k.a. MOOCs) of various levels, from beginner +to advanced. + +**If you are a programmer and want to learn Scala**, there are two recommended +approaches. The fast path consists of taking the course [Effective Programming +in Scala](#effective-programming-in-scala). +Otherwise, you can take the full [Scala Specialization], which is made of +four courses (covering advanced topics such as big data analysis and +parallel programming) and a capstone project. + +You can learn more about the courses in the following video: + +
+ +
+ +## Scala Learning Path + +The diagram below summarizes the possible learning paths with our courses: + +![](/resources/images/learning-path.png) + +The “foundational” courses target programmers with no prior experience in Scala, whereas the “deepening” +courses aim at strengthening Scala programmers skills in a specific domain (such as parallel programming). + +We recommend starting with either Effective Programming in Scala, or Functional Programming Principles in +Scala followed by Functional Program Design. Then, you can complement your Scala skills by taking any +of the courses Programming Reactive Systems, Parallel Programming, or Big Data Analysis with Scala and Spark. +In case you take the Scala Specialization, you will end with the Scala Capstone Project. + +## Learning Platforms + +Currently, all our MOOCs are available on the platform [Coursera](https://coursera.org), +and some of them are available on [edX](https://edx.org) or the [Extension School](https://extensionschool.ch). +This section explains the differences between these learning platforms. + +On all the platforms the full material is always available online. It includes +video lectures, text articles, quizzes, and auto-graded homeworks. All the +platforms also provide discussion forums where you can exchange with the +other learners. + +The difference between the Extension School and the other platforms is that it +provides live meetings with instructors, and code reviews by Scala experts. + +On the other hand, on Coursera or edX it is possible to take +our courses for free (a.k.a. “audit” mode). Optionally, a subscription gives +you access to a certificate of completion that attests your accomplishments. + +Learn more about +[Coursera certificates](https://learners.coursera.help/hc/en-us/articles/209819053-Get-a-Course-Certificate), +[edX certificates](https://support.edx.org/hc/en-us/categories/115002269627-Certificates), +or [Extension School certificates](https://www.extensionschool.ch/faqs#certifying-coursework). +Note that your subscriptions also supports the work of the [Scala Center], +whose mission is to create high-quality educational material. + +If you prefer learning in autonomy, we recommend +you to choose the Coursera or edX platform, but if you are looking for more +support, we recommend you to choose the Extension School. Here is a table +below that compares the learning platforms: + +| | Coursera / edX (audit) | Coursera / edX (subscription) | Extension School | +|--------------------------------------------------|------------------------|-------------------------------|------------------| +| Video lectures, quizzes | Yes | Yes | Yes | +| Auto-graded homeworks | Yes | Yes | Yes | +| Discussion forums | Yes | Yes | Yes | +| Self-paced | Yes | Yes | Yes | +| Price | $0 | $50 to $100 per course | $420 per month | +| Certificate of completion | No | Yes | Yes | +| Supports the Scala Center | No | Yes | Yes | +| 30 min of live session with instructors per week | No | No | Yes | +| Code reviews by Scala experts | No | No | Yes | + +## Effective Programming in Scala + +This course is available on [Coursera](https://coursera.org/learn/effective-scala) +and the [Extension School](https://extensionschool.ch/learn/effective-programming-in-scala). +Please refer to [this section](#learning-platforms) to know the differences +between both learning platforms. + +[Effective Programming in Scala] teaches non-Scala programmers everything +they need to be ready to work in Scala. At the end of this hands-on course, +you will know how to achieve common programming tasks in Scala (e.g., +modeling business domains, implementing business logic, designing large +systems made of components, handling errors, manipulating data, running +concurrent tasks in parallel, testing your code). You can learn more about +this course in the following video: + +
+ +
+ +This course is also a good way to upgrade your Scala 2 knowledge to Scala 3. + +After taking this course, you might be interested in improving your +skills in specific areas by taking the courses [Parallel Programming], +[Big Data Analysis with Scala and Spark], or [Programming Reactive Systems]. + +## Scala Specialization + +The [Scala Specialization] provides a hands-on introduction to functional programming using Scala. You can access the courses +material and exercises by either signing up for the specialization or auditing the courses individually. The +specialization has 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]. + +These courses provide a deep understanding of the Scala language itself, +and they also dive into more specific topics such as parallel programming, +and Spark. + +## Programming Reactive Systems + +[Programming Reactive Systems] (also available on [edX](https://www.edx.org/course/scala-akka-reactive)) +teaches how to write responsive, scalable, and resilient systems with the +library Akka. + +## Scala 2 Courses + +The above courses all use Scala 3. If needed, you can find +the (legacy) Scala 2 version of our courses here: + +- [Functional Programming Principles in Scala (Scala 2 version)](https://www.coursera.org/learn/scala2-functional-programming) +- [Functional Program Design (Scala 2 version)](https://www.coursera.org/learn/scala2-functional-program-design) +- [Parallel Programming (Scala 2 version)](https://www.coursera.org/learn/scala2-parallel-programming) +- [Big Data Analysis with Scala and Spark (Scala 2 version)](https://www.coursera.org/learn/scala2-spark-big-data) +- [Programming Reactive Systems (Scala 2 version)](https://www.coursera.org/learn/scala2-akka-reactive) + +## Testimonials + +{% include carousel.html images=page.testimonials number=0 height="50" unit="%" duration="10" %} + +## Other Online Resources + +You can find other online resources contributed by the community on +[this page]({% link online-courses.md %}). + +[Scala Center]: https://scala.epfl.ch +[Scala Specialization]: https://www.coursera.org/specializations/scala +[Effective Programming in Scala]: https://www.coursera.org/learn/effective-scala +[Functional Programming Principles in Scala]: https://www.coursera.org/learn/scala-functional-programming +[Functional Program Design in Scala]: https://www.coursera.org/learn/scala-functional-program-design +[Parallel programming]: https://www.coursera.org/learn/scala-parallel-programming +[Big Data Analysis with Scala and Spark]: https://www.coursera.org/learn/scala-spark-big-data +[Functional Programming in Scala Capstone]: https://www.coursera.org/learn/scala-capstone +[Programming Reactive Systems]: https://www.coursera.org/learn/scala-akka-reactive diff --git a/scripts/run-mdoc.sh b/scripts/run-mdoc.sh index 97593abd20..fc478f83d5 100755 --- a/scripts/run-mdoc.sh +++ b/scripts/run-mdoc.sh @@ -1,10 +1,15 @@ #!/bin/bash set -eux -cs launch org.scalameta:mdoc_2.12:2.2.13 -- \ +cs launch --scala-version 2.13.16 org.scalameta::mdoc:2.3.3 -- \ --in . \ --out /tmp/mdoc-out/ \ - --classpath $(cs fetch -p com.chuusai:shapeless_2.12:2.3.3) \ + --classpath \ + $(cs fetch --scala-version 2.13.16 -p \ + com.chuusai::shapeless:2.3.10 \ + org.scala-lang::toolkit:0.1.7 \ + org.scala-lang::toolkit-test:0.1.7 \ + ) \ --scalac-options "-Xfatal-warnings -feature" \ --no-link-hygiene \ --include '**.md' diff --git a/tutorials.md b/tutorials.md index 7cffa28385..17ade963ba 100644 --- a/tutorials.md +++ b/tutorials.md @@ -1,5 +1,5 @@ --- -layout: inner-page-parent +layout: root-index-layout title: Tutorials tutorials: