diff --git a/_config.yml b/_config.yml index 6ab7b65d5c..58942c8e8e 100644 --- a/_config.yml +++ b/_config.yml @@ -17,6 +17,8 @@ keywords: scala-version: 2.13.3 scala-212-version: 2.12.12 +scala-3-version: 3.0.0-M3 +scala-3-plugin-version: 0.5.1 collections: style: @@ -90,10 +92,37 @@ defaults: type: "tour" values: overview-name: "Tour of Scala" + - + scope: + path: "_overviews/scala3-book" + values: + scala3: true + # num: 99 # to list them in the TOC, should be overwritten individually + partof: scala3-book + type: section + overview-name: "Scala 3 — Book" + layout: multipage-overview + permalink: "/scala3/book/:title.html" + - + scope: + path: "_overviews/scala3-macros" + values: + scala3: true + partof: scala3-macros + overview-name: "Macros in Scala 3" + layout: multipage-overview + permalink: "/scala3/guides/macros/:title.html" + - + scope: + path: "scala3" + values: + scala3: true + highlighter: rouge permalink: /:categories/:title.html:output_ext baseurl: +scala3ref: "https://dotty.epfl.ch/docs/reference" exclude: ["vendor"] plugins: - jekyll-redirect-from diff --git a/_data/scala3-doc-nav-header.yml b/_data/scala3-doc-nav-header.yml new file mode 100644 index 0000000000..823c440e3a --- /dev/null +++ b/_data/scala3-doc-nav-header.yml @@ -0,0 +1,17 @@ +- title: Learn + url: "#" + submenu: + - title: New in Scala 3 + url: "/scala3/new-in-scala3.html" + - title: Getting Started + url: "/scala3/getting-started.html" + - title: Scala 3 Book + url: "/scala3/book/introduction.html" + - title: Macro Tutorial + url: "/scala3/guides/macros/index.html" +- title: Migrate + url: "https://scalacenter.github.io/scala-3-migration-guide" +- title: Reference + url: "https://dotty.epfl.ch/docs/reference/overview.html" +- title: API + url: "https://dotty.epfl.ch/api/index.html" diff --git a/_includes/documentation-sections.html b/_includes/documentation-sections.html new file mode 100644 index 0000000000..4ca75f459d --- /dev/null +++ b/_includes/documentation-sections.html @@ -0,0 +1,28 @@ +
+ {% for section in include.sections %} +
+

{{ section.title }}

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

{{link.description}}

+
+
+ {% endfor %} +
+
  + {% if section.more-resources %} + {{ page.more-resources-label }}: + + {% endif %} +
+ {% endfor %} +
diff --git a/_includes/headertop.html b/_includes/headertop.html index a21cec7592..a70469cf62 100644 --- a/_includes/headertop.html +++ b/_includes/headertop.html @@ -1,5 +1,9 @@ +{% if page.scala3 %} + +{% else %} +{% endif %} {% if page.title %}{{ page.title }} | {% endif %} diff --git a/_includes/masthead-documentation.html b/_includes/masthead-documentation.html index e0da85bcb5..c9bcf75255 100644 --- a/_includes/masthead-documentation.html +++ b/_includes/masthead-documentation.html @@ -29,34 +29,7 @@ {% endif %} </ul> - <div class="documentation"> - {% for section in page.sections %} - <div class="section"> - <h2 class="frontpage">{{ section.title }}</h2> - {% for link in section.links %} - <a href="{% if link.link contains '://' %}{{link.link}}{% else %}{{site.baseurl}}{{link.link}}{% endif %}" class="doc-item"> - <div class="doc-item-header"> - <i class="{{link.icon}}"></i> - <h5>{{link.title}}</h5> - </div> - <div class="doc-item-main"> - <p>{{link.description}}</p> - </div> - </a> - {% endfor %} - </div> - <div class="more-resources">  - {% if section.more-resources %} - <span class="heading">{{ page.more-resources-label }}: </span> - <ul> - {% for resource in section.more-resources %} - <li><a href="{{ site.baseurl }}{{ resource.url }}">{{ resource.title }}</a></li> - {% endfor %} - </ul> - {% endif %} - </div> - {% endfor %} - </div> + {% include documentation-sections.html sections=page.sections %} </div> </div> </section> diff --git a/_includes/navbar-inner.html b/_includes/navbar-inner.html index e565a2fd73..171d00924e 100644 --- a/_includes/navbar-inner.html +++ b/_includes/navbar-inner.html @@ -1,34 +1,32 @@ -<header id="site-header"> - <div class="wrap"> - <nav class="navigation" role="menu"> - <a href="http://scala-lang.org" 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="{% if navItem.url contains '://' %}{{navItem.url}}{% else %}{{site.baseurl}}{{navItem.url}}{% endif %}" {% if page.url contains navItem.url %}class="active"{% endif %}>{{navItem.title}}</a> - </li> - {% endfor %} - </ul> - </nav> - </div> -</header> +{% if page.scala3 %} + {% assign navdata = site.data.scala3-doc-nav-header %} +{% else %} + {% assign navdata = site.data.doc-nav-header %} +{% endif %} + +{% include site-header.html %} + +{% if page.scala3 %} +<header id="doc-header" class="scala3"> +{% else %} <header id="doc-header"> +{% endif %} <div class="wrap" style="padding: 0px;"> <nav class="doc-navigation" role="menu"> {% assign docsRootTranslated = site[page.language] | where: 'partof', 'documentation' | first %} - <a href="{{ site.baseurl }}/{{ docsRootTranslated.language }}" class="navigation-bdand" > - <img src="{{ site.baseurl }}/resources/img/documentation-logo@2x.png" alt=""> + <div class="navigation-bdand"> + <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> </div> <ul class="navigation-menu"> - {% for navItem in site.data.doc-nav-header %} + {% for navItem in navdata %} <li class="navigation-menu-item"> {% capture translatedPageId %}/{{page.language}}{{navItem.url | remove_first: '.html' }}{% endcapture %} {% assign navItemTranslated = site.documents | where: 'id', translatedPageId | first %} @@ -49,7 +47,7 @@ </ul> </nav> <nav class="doc-navigation-submenus"> - {% for navItem in site.data.doc-nav-header %} + {% for navItem in navdata %} {% if navItem.submenu %} <ul class="navigation-submenu" id="{{ navItem.title | downcase | strip }}" style="display: none;"> {% for subItem in navItem.submenu %} @@ -61,7 +59,7 @@ {% endif %} {% endfor %} <ul class="navigation-submenu ellipsis-menu" style="display: none;"> - {% for navItem in site.data.doc-nav-header %} + {% for navItem in navdata %} {% if forloop.index > 3 %} <li><a href="{% if navItem.url contains '://' %}{{navItem.url}}{% else %}{{site.baseurl}}{{navItem.url}}{% endif %}">{{ navItem.title }}</a></li> {% endif %} diff --git a/_includes/sidebar-toc-multipage-overview.html b/_includes/sidebar-toc-multipage-overview.html index b7bc76d761..8942aa3169 100644 --- a/_includes/sidebar-toc-multipage-overview.html +++ b/_includes/sidebar-toc-multipage-overview.html @@ -11,25 +11,30 @@ <h5 class="contents">Contents</h5> <ul> {% assign sorted = site.overviews | sort: 'num' %} {% for pg in sorted %} + {% if pg.num == page.num %} + {% capture toc %} + <div id="toc"></div> + {% endcapture %} + {% endif %} + {% 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 %} {% 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></li> + <li><a {% if page.title == lpg.title %}class="active"{% endif %} href="/{{ site.baseurl }}{{ page.language }}{{ pg.url }}">{{ lpg.title }}</a> + {{ toc }}</li> {% endif %} {% endfor %} {% else %} <!-- this must be English, so get the other documents' titles --> {% 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.title == pg.title %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ pg.title }}</a></li> + <li class="type-{{ pg.type }}"><a {% if page.num == pg.num %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ pg.title }}</a>{{toc}}</li> {% else %} - <li><a {% if page.title == pg.title %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ pg.title }}</a></li> + <li><a {% if page.num == pg.num %}class="active"{% endif %} href="{{ site.baseurl }}{{ pg.url }}">{{ pg.title }}</a>{{toc}}</li> {% endif %} - {% endif %} - {% if pg.num == page.num %}<div id="toc"></div>{% endif %} {% endif %} {% endfor %} </ul> diff --git a/_includes/site-header.html b/_includes/site-header.html new file mode 100644 index 0000000000..31f2bc2a38 --- /dev/null +++ b/_includes/site-header.html @@ -0,0 +1,19 @@ +<header id="site-header"> + <div class="wrap"> + <nav class="navigation" role="menu"> + <a href="http://scala-lang.org" 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="{% if navItem.url contains '://' %}{{navItem.url}}{% else %}{{site.baseurl}}{{navItem.url}}{% endif %}" {% if page.url contains navItem.url %}class="active"{% endif %}>{{navItem.title}}</a> + </li> + {% endfor %} + </ul> + </nav> + </div> +</header> diff --git a/_layouts/documentation.html b/_layouts/documentation.html new file mode 100644 index 0000000000..8c3f178796 --- /dev/null +++ b/_layouts/documentation.html @@ -0,0 +1,56 @@ +--- +--- +{% 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 content-primary"> + <div class="wrap scala2"> + <div class="language-header"> + <h1>Scala 2</h1> + </div> + <div class="inner-box"> + {% include documentation-sections.html sections=page.scala2-sections %} + </div> + </div> + + <div class="wrap scala3"> + <div class="language-header"> + <h1>Scala 3 (Preview)</h1> + </div> + <div class="inner-box"> + <blockquote> + Scala 3 has not been released, yet. We are still in the process of writing the documentation for Scala 3.<br/> + 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/inner-page-parent-dropdown.html b/_layouts/inner-page-parent-dropdown.html index 5b89d3ef47..9b07f3abfa 100644 --- a/_layouts/inner-page-parent-dropdown.html +++ b/_layouts/inner-page-parent-dropdown.html @@ -2,16 +2,29 @@ {% 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"> + {% if page.scala3 %} + <div class="wip-notice"> + <h4>Work in Progress</h4> + <p> + Scala 3 has not been released, yet. + 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/_overviews/scala3-book/ca-context-bounds.md b/_overviews/scala3-book/ca-context-bounds.md new file mode 100644 index 0000000000..096f0a92b4 --- /dev/null +++ b/_overviews/scala3-book/ca-context-bounds.md @@ -0,0 +1,50 @@ +--- +title: Context Bounds +type: section +description: This page demonstrates Context Bounds in Scala 3. +num: 61 +previous-page: types-type-classes +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 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`: + +```scala +def maximum[T](xs: List[A])(using ord: Ord[A]): A = + xs.reduceLeft(max(ord)) +``` + +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: + +```scala +def maximum[T](xs: List[A])(using Ord[A]): A = + xs.reduceLeft(max) +``` + + +## 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.” + +Using a context bound, the `maximum` method can be written like this: + +```scala +def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) +``` + +A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with `Ord[A]`. + +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. diff --git a/_overviews/scala3-book/ca-contextual-abstractions-intro.md b/_overviews/scala3-book/ca-contextual-abstractions-intro.md new file mode 100644 index 0000000000..d95a9bfc54 --- /dev/null +++ b/_overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -0,0 +1,87 @@ +--- +title: Contextual Abstractions +type: chapter +description: This chapter provides an introduction to the Scala 3 concept of Contextual Abstractions. +num: 58 +previous-page: types-others +next-page: ca-given-using-clauses +--- + + +## Background + +Implicits in Scala 2 were a major distinguishing design 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 + +Since then, other languages have followed suit, 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. + +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. + + +## Redesign + +Scala 3 includes a redesign of contextual abstractions in Scala. +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**. + [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. + +- **Viewing one type as another**. + Implicit conversion 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. + They are an important tool for library authors and allow to express concise domain specific languages. + +- **Actionable feedback from the compiler**. + In case an implicit parameter can not be resolved by the compiler, it now provides you [import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) that may fix the problem. + + +## Benefits + +These changes in Scala 3 achieve a better separation of term inference from the rest of the language: + +- There’s a single way to define givens +- There’s a single way to introduce implicit parameters and arguments +- There’s a separate way to [import givens][given-imports] that does not allow them to hide in a sea of normal imports +- There’s a single way to define an [implicit conversion][implicit-conversions], which is clearly marked as such, and does not require special syntax + +Benefits of these changes include: + +- The new design thus avoids feature interactions and makes the language more consistent +- It makes implicits easier to learn and harder to abuse +- It greatly improves the clarity of the 95% of Scala programs that use implicits +- It has the potential to enable term inference in a principled way that is also accessible and friendly + +This chapter introduces many of these new features in the following sections. + +[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.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 %} diff --git a/_overviews/scala3-book/ca-extension-methods.md b/_overviews/scala3-book/ca-extension-methods.md new file mode 100644 index 0000000000..9caf7f3581 --- /dev/null +++ b/_overviews/scala3-book/ca-extension-methods.md @@ -0,0 +1,65 @@ +--- +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 +--- + + +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: + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +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: + +```scala +object CircleHelpers: + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +``` + +Then you’d use that method like this: + +```scala +// without extension methods +CircleHelpers.circumference(aCircle) +``` + +But with extension methods you can create a `circumference` method to work on `Circle` instances: + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 +``` + +In this code: + +- `Circle` is the type that the extension method `circumference` will be added to +- The `c: Circle` syntax lets you reference the variable `c` in your extension method(s) + +Then in your code you use `circumference` just as though it was originally defined in the `Circle` class: + +```scala +aCircle.circumference +``` + + +## Discussion + +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: + +```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 +``` + + + diff --git a/_overviews/scala3-book/ca-given-imports.md b/_overviews/scala3-book/ca-given-imports.md new file mode 100644 index 0000000000..b1aae7459e --- /dev/null +++ b/_overviews/scala3-book/ca-given-imports.md @@ -0,0 +1,49 @@ +--- +title: Given Imports +type: section +description: This page demonstrates how 'given' import statements work in Scala 3. +num: 62 +previous-page: ca-context-bounds +next-page: ca-extension-methods +--- + + +To make it more clear where givens in the current scope are coming from, a special form of the `import` statement is used to import `given` instances. +The basic form is shown in this example: + +```scala +object A: + class TC + given tc as TC + def f(using TC) = ??? + +object B: + import A._ // import all non-given members + import A.given // import the given instance +``` + +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: + +```scala +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. + +These rules have two main benefits: + +- It’s more clear where givens in the current scope are coming from. + In particular, it’s not possible to hide imported givens in a long list of other wildcard imports. +- It enables importing all givens without importing anything else. + This is important because givens can be anonymous, so the usual use of named imports is not practical. + +More examples of the “import given” syntax are shown in the [Packaging and Imports chapter][imports]. + + +[imports]: {% link _overviews/scala3-book/packaging-imports.md %} diff --git a/_overviews/scala3-book/ca-given-using-clauses.md b/_overviews/scala3-book/ca-given-using-clauses.md new file mode 100644 index 0000000000..dd1b4ccc1b --- /dev/null +++ b/_overviews/scala3-book/ca-given-using-clauses.md @@ -0,0 +1,97 @@ +--- +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 it 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 new file mode 100644 index 0000000000..4a421da97b --- /dev/null +++ b/_overviews/scala3-book/ca-implicit-conversions.md @@ -0,0 +1,45 @@ +--- +title: Implicit Conversions +type: section +description: This page demonstrates how to implement Implicit Conversions in Scala 3. +num: 66 +previous-page: ca-multiversal-equality +next-page: ca-summary +--- + + +Implicit conversions are defined by `given` instances of the _scala.Conversion_ class. +For example, not accounting for possible conversion errors, this code defines an an implicit conversion from `String` to `Int`: + +```scala +given Conversion[String, Int] with + def apply(s: String): Int = Integer.parseInt(s) +``` + +Using an alias this can be expressed more concisely as: + +```scala +given Conversion[String, Int] = Integer.parseInt(_) +``` + +Using either of those conversions, you can now use a `String` in places where an `Int` is expected: + +```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") +``` + +## Discussion + +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(_) +``` diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md new file mode 100644 index 0000000000..a106ed0856 --- /dev/null +++ b/_overviews/scala3-book/ca-multiversal-equality.md @@ -0,0 +1,201 @@ +--- +title: Multiversal Equality +type: section +description: This page demonstrates how to implement Multiversal Equality in Scala 3. +num: 65 +previous-page: ca-type-classes +next-page: ca-implicit-conversions +--- + + +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. + +Universal equality is convenient, but it’s also dangerous since it undermines type safety. +For instance, let’s assume that after some refactoring, you’re left 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. + +A type-safe programming language can do better, and multiversal equality is an opt-in way to make universal equality safer. +It uses the binary type class `CanEqual` to indicate that values of two given types can be compared with each other. + + +## Allowing the comparison of class instances + +By default, in Scala 3 you can still create an equality comparison like this: + +```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 +``` + +But with Scala 3 you can disable such comparisons. +By (a) importing `scala.language.strictEquality` or (b) using the `-language:strictEquality` compiler flag, this comparison no longer compiles: + +```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 != +``` + + +## Enabling comparisons + +There are two ways to enable this comparison using the Scala 3 `CanEqual` type class. +For simple cases like this, your class can *derive* the `CanEqual` class: + +```scala +// Option 1 +case class Dog(name: String) derives CanEqual +``` + +As you’ll see in a few moments, when you need more flexibility you can also use this syntax: + +```scala +// Option 2 +case class Dog(name: String) +given CanEqual[Dog, Dog] = CanEqual.derived +``` + +Either of those two approaches now let `Dog` instances to be compared to each other. + + +## A more real-world example + +In a more real-world example, imagine you have an online bookstore and want to allow or disallow the comparison of physical, printed books, and audiobooks. +With Scala 3 you start by enabling multiversal equality as shown in the previous example: + +```scala +// [1] add this import, or this command line flag: -language:strictEquality +import scala.language.strictEquality +``` + +Then create your domain objects as usual: + +```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 +``` + +Finally, use `CanEqual` to define which comparisons you want to allow: + +```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 +``` + +The last line of code results in this compiler error message: + +```` +Values of types PrintedBook and AudioBook cannot be compared with == or != +```` + +This is how multiversal equality catches illegal type comparisons at compile time. + + +### Enabling “PrintedBook == AudioBook” + +That works as desired, but in some situations you may want to allow the comparison of physical books to audiobooks. +When you want this, create these two additional equality comparisons: + +```scala +// allow `PrintedBook == AudioBook`, and `AudioBook == PrintedBook` +given CanEqual[PrintedBook, AudioBook] = CanEqual.derived +given CanEqual[AudioBook, PrintedBook] = CanEqual.derived +``` + +Now you can compare physical books to audiobooks without a compiler error: + +```scala +println(pBook == aBook) // false +println(aBook == pBook) // false +``` + +#### Implement “equals” to make them really work + +While these comparisons are now allowed, they will always be `false` because their `equals` methods don’t know how to make these comparisons. +Therefore, the solution is to override the `equals` methods for each class. +For instance, when you override the `equals` method for `AudioBook`: + +```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 +``` + +You can now compare an `AudioBook` to a `PrintedBook`: + +```scala +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`. +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. + + +[ref-equal]: {{ site.scala3ref }}/contextual/multiversal-equality.html diff --git a/_overviews/scala3-book/ca-summary.md b/_overviews/scala3-book/ca-summary.md new file mode 100644 index 0000000000..28c399b2f2 --- /dev/null +++ b/_overviews/scala3-book/ca-summary.md @@ -0,0 +1,30 @@ +--- +title: Summary +type: section +description: This page provides a summary of the Contextual Abstractions lessons. +num: 67 +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 + +A few more advanced topics aren’t covered here, including: + +- Type Class Derivation +- Context Functions +- By-Name Context Parameters +- Relationship with Scala 2 Implicits + +Those topics are discussed in detail in the [Reference documentation][ref]. + + +[ref]: {{ site.scala3ref }}/contextual/motivation.html diff --git a/_overviews/scala3-book/ca-type-classes.md b/_overviews/scala3-book/ca-type-classes.md new file mode 100644 index 0000000000..8967d78ec5 --- /dev/null +++ b/_overviews/scala3-book/ca-type-classes.md @@ -0,0 +1,113 @@ +--- +title: Implementing 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 +next-page: ca-multiversal-equality +--- + + +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: + +- 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 + +In Scala 3, type classes are just traits with one or more parameters whose implementations are provided by `given` instances. + + +{% comment %} +TODO: As background, discuss where the name "type class" comes from. +{% endcomment %} + +## 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. + +### 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: + +```scala +// a type class +trait Showable[A]: + extension(a: A) def show: String +``` + +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: + +```scala +// a trait +trait Show: + def show: String +``` + +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. + +### 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: + +```scala +case class Person(firstName: String, lastName: String) +``` + +you’ll define a `given` value for `Showable[Person]`. +This code provides a concrete instance of `Showable` for the `Person` class: + +```scala +given Showable[Person] with + 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. + +### Using the type class + +Now you can use this type class like this: + +```scala +val person = Person("John", "Doe") +println(person.show) +``` + +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`. + +### Writing methods that use the type class + +As with inheritance, you can define methods that use `Showable` as a type parameter: + +```scala +def showAll[S: Showable](xs: List[S]): Unit = + xs.foreach(x => println(x.show)) + +showAll(List(Person("Jane"), Person("Mary"))) +``` + +### 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: + +```scala +trait HasLegs[A]: + extension (a: A) + def walk(): Unit + def run(): Unit +``` + +### 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]. + + +[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 new file mode 100644 index 0000000000..48c0c602b6 --- /dev/null +++ b/_overviews/scala3-book/collections-classes.md @@ -0,0 +1,619 @@ +--- +title: Collections Types +type: section +description: This page introduces the common Scala 3 collections types and some of their methods. +num: 37 +previous-page: collections-intro +next-page: collections-methods +--- + + +{% comment %} +TODO: add a correct hierarchy image +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 %} + + +This page demonstrates the common Scala 3 collections and their accompanying methods. +Scala comes with a wealth of collections types, but you can go a long way by starting with just a few of them, and later using the others as needed. +Similarly, each collection type has dozens of methods to make your life easier, but you can achieve a lot by starting with just a handful of them. + +Therefore, this section introduces and demonstrates the most common types and methods that you’ll need to get started. +When you need more flexibility, see these pages at the end of this section for more details. + + + +## Three main categories of collections + +Looking at Scala collections from a high level, there are three main categories to choose from: + +- Sequences +- Maps +- Sets + +A _sequence_ is a linear collection of elements and may be _indexed_ (like an array) or _linear_ (like a linked list). +A _map_ contains a collection of key/value pairs, like a Java `Map`, Python dictionary, or Ruby `Hash`. +A _set_ is an unordered sequence of unique elements. +All of those are basic types, and have subtypes for specific purposes, such as concurrency, caching, and streaming. + +In addition to these three main categories, there are other useful collection types, including ranges, stacks, and queues. +Ranges are demonstrated later in this section. + +The following sections introduce the common types you’ll use on a regular basis. + + + +## Common collections + +The main collections you’ll use on a regular basis are: + +| Collection Type | Immutable | Mutable | Description | +| ------------- | --------- | ------- | ----------- | +| `List` | ✓ | | A linear (linked list), immutable sequence | +| `Vector` | ✓ | | An indexed, immutable sequence | +| `LazyList` | ✓ | | A lazy immutable linked list, its elements are computed only when they’re needed; Good for large or infinite sequences. | +| `ArrayBuffer` | | ✓ | The go-to type for a mutable, indexed sequence | +| `ListBuffer` | | ✓ | Used when you want a mutable `List`; typically converted to a `List` | +| `Map` | ✓ | ✓ | An iterable sequence that consists of pairs of keys and values. | +| `Set` | ✓ | ✓ | An iterable collection with no duplicate elements | + +As shown, `Map` and `Set` come in both immutable and mutable versions. + +The basics of each type are demonstrated in the following sections. + +> In Scala, a _buffer_ — such as `ArrayBuffer` and `ListBuffer` — is a sequence that can grow and shrink. + + +### A note about immutable collections + +In the sections that follow, whenever the word _immutable_ is used, it’s safe to assume that the type is intended for use in a _functional programming_ (FP) style. +With these types you don’t modify the collection; you apply functional methods to the collection to create a new result. + + + +## Choosing a sequence + +When choosing a _sequence_ — a sequential collection of elements — you have two main decisions: + +- Should the sequence be indexed (like an array), allowing rapid access to any element, or should it be implemented as a linear linked list? +- Do you want a mutable or immutable collection? + +The recommended, general-purpose, “go to” sequential collections for the combinations of mutable/immutable and indexed/linear are shown here: + +| Type/Category | Immutable | Mutable | +| --------------------- | --------- | ------------ | +| Indexed | `Vector` |`ArrayBuffer` | +| Linear (Linked lists) | `List` |`ListBuffer` | + +For example, if you need an immutable, indexed collection, in general you should use a `Vector`. +Conversely, if you need a mutable, indexed collection, use an `ArrayBuffer`. + +> `List` and `Vector` are often used when writing code in a functional style. +> `ArrayBuffer` is commonly used when writing code in a mutable style. +> `ListBuffer` is used when you’re mixing styles, such as building a list. + +The next several sections briefly demonstrate the `List`, `Vector`, and `ArrayBuffer` types. + + + +## `List` + +[The List type](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) is a linear, immutable sequence. +This just means 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`. + +### Creating Lists + +This is how you create an initial `List`: + +```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 +``` + +You can also declare the `List`’s type, if you prefer, though it generally isn’t necessary: + +```scala +val ints: List[Int] = List(1, 2, 3) +val names: List[String] = List("Joel", "Chris", "Ed") +``` + +One exception is when you have mixed types in a collection; in that case you may want to explicitly specify its type: + +```scala +val things: List[Any] = List(1, "two", 3.0) +``` + +### 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`. +For instance, given this `List`: + +```scala +val a = List(1, 2, 3) +``` + +When working with a `List`, _prepend_ one element with `::`, and prepend another `List` with `:::`, as shown here: + +```scala +val b = 0 :: a // List(0, 1, 2, 3) +val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) +``` + +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. + +> Tip: If you want to prepend and append elements to an immutable sequence, use `Vector` instead. + +Because `List` is a linked-list, you shouldn’t try to access the elements of large lists by their index value. +For instance, if you have a `List` with one million elements in it, accessing an element like `myList(999_999)` will take a relatively long time, because that request has to traverse all those elements. +If you have a large collection and want to access elements by their index, use a `Vector` or `ArrayBuffer` instead. + +### How to remember the method names + +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: + +```scala +0 +: a +``` + +Similarly, when you use `:+` you know the list needs to be on the left: + +```scala +a :+ 4 +``` + +There are more technical ways to think about this, but this can be a helpful way to remember the method names. + +{% comment %} +LATER: Add a discussion of `:` on method names, right-associativity, and infix operators. +{% endcomment %} + +Also, a good thing about these symbolic method names is that they’re consistent. +The same method names are used with other immutable sequences, such as `Seq` and `Vector`. +You can also use non-symbolic method names to append and prepend elements, if you prefer. + +### How to loop over lists + +Given a `List` of names: + +```scala +val names = List("Joel", "Chris", "Ed") +``` + +you can print each string like this: + +```scala +for name <- names do println(name) +``` + +This is what it looks like in the REPL: + +```scala +scala> for name <- names do println(name) +Joel +Chris +Ed +``` + +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. + +### A little bit of history + +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: + +```scala +val ints = List(1, 2, 3) +``` + +you can also create the exact same list this way: + +```scala +val list = 1 :: 2 :: 3 :: Nil +``` + +The REPL shows how this works: + +```scala +scala> val list = 1 :: 2 :: 3 :: Nil +list: List[Int] = List(1, 2, 3) +``` + +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. + + +### Aside: The LazyList + +The Scala collections also include a [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html), which is a _lazy_ immutable linked list. +It’s called “lazy” — or non-strict — because it computes its elements only when they are needed. + +You can see how lazy a `LazyList` is in the REPL: + +```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>) +``` + +In all of those examples, nothing happens. +Indeed, nothing will happen until you force it to happen, such as by calling its `foreach` method: + +```` +scala> x.take(1).foreach(println) +1 +```` + +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. + +<!-- +Given that definition, collections can also be thought of in terms of being strict or lazy. In a _strict_ collection, memory for the elements is allocated immediately, and all of its elements are immediately evaluated when a transformer method is invoked. In a _lazy_ collection, memory for the elements is not allocated immediately, and transformer methods do not construct new elements until they are demanded. +--> + + + +## Vector + +[Vector](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 it provides random access and update in effectively constant time, so you can access `Vector` elements rapidly by their index value, such as accessing `listOfPeople(123_456_789)`. + +In general, except for the difference that (a) `Vector` is indexed and `List` is not, and (b) `List` has the `::` method, the two types work the same, so we’ll quickly run through the following examples. + +Here are a few ways you can create a `Vector`: + +```scala +val nums = Vector(1, 2, 3, 4, 5) + +val strings = Vector("one", "two") + +case class Person(val name: String) +val people = Vector( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` + +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`. +These examples show how to _append_ elements to a `Vector`: + +```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) +``` + +This is how you _prepend_ elements: + +```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) +``` + +In addition to fast random access and updates, `Vector` provides fast append and prepend times, so you can use these features as desired. + +> See the [Collections Performance Characteristics](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html) for performance details about `Vector` and other collections. + +Finally, you use a `Vector` in a `for` loop just like a `List`, `ArrayBuffer`, or any other sequence: + +```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 +``` + + + +## 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. +Because it’s indexed, random access of elements is fast. + +### Creating an ArrayBuffer + +To use an `ArrayBuffer`, first import it: + +```scala +import scala.collection.mutable.ArrayBuffer +``` + +If you need to start with an empty `ArrayBuffer`, just specify its type: + +```scala +var strings = ArrayBuffer[String]() +var ints = ArrayBuffer[Int]() +var people = ArrayBuffer[Person]() +``` + +If you know the approximate size your `ArrayBuffer` eventually needs to be, you can create it with an initial size: + +```scala +// ready to hold 100,000 ints +val buf = new ArrayBuffer[Int](100_000) +``` + +To create a new `ArrayBuffer` with initial elements, just specify its initial elements, just like a `List` or `Vector`: + +```scala +val nums = ArrayBuffer(1, 2, 3) +val people = ArrayBuffer( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` + +### Adding elements to an ArrayBuffer + +Append new elements to an `ArrayBuffer` with the `+=` and `++=` methods. +Or if you prefer methods with textual names you can also use `append`, `appendAll`, `insert`, `insertAll`, `prepend`, and `prependAll`. + +Here are some examples of `+=` and `++=`: + +```scala +var nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) +nums += 4 // ArrayBuffer(1, 2, 3, 4) +nums += (5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) +nums ++= List(7, 8) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8) +``` + +### Removing elements from an ArrayBuffer + +`ArrayBuffer` is mutable, so it has methods like `-=`, `--=`, `clear`, `remove`, and more. +These examples demonstrate the `-=` and `--=` methods: + +```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) +``` + +### Updating ArrayBuffer elements + +Update elements in an `ArrayBuffer` by either reassigning the desired element, or use the `update` method: + +```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) +``` + + + +## Maps + +A `Map` is an iterable sequence that consists of pairs of keys and values. +Scala has both mutable and immutable `Map` types, and this section demonstrates how to use the _immutable_ `Map`. + +### Creating an immutable Map + +Create an immutable `Map` like this: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) +``` + +Once you have a `Map` you can traverse its elements in a `for` loop like this: + +```scala +for ((k,v) <- states) println(s"key: $k, value: $v") +``` + +The REPL shows how this works: + +```` +scala> for ((k,v) <- states) println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +```` + +### Accessing Map elements + +Access map elements by specifying the desired key value in parentheses: + +```scala +val ak = states("AK") // ak: String = Alaska +val al = states("AL") // al: String = Alabama +``` + +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: + +```scala +val a = Map(1 -> "one") // a: Map(1 -> one) +val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) +val c = b + ( + 3 -> "three", + 4 -> "four" +) +// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) +``` + +### 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: + +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three", + 4 -> "four" +) + +a - 4 // Map(1 -> one, 2 -> two, 3 -> three) +a - 4 - 3 // Map(1 -> one, 2 -> two) +``` + +### Updating Map elements + +To update elements in an immutable map, use the `updated` method while assigning the result to a new variable: + +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three" +) + +val b = a.updated(3, "THREE!") // Map(1 -> one, 2 -> two, 3 -> THREE!) +``` + +### Traversing a Map + +As shown earlier, this is a common way to manually traverse elements in a map using a `for` loop: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for ((k,v) <- states) println(s"key: $k, value: $v") +``` + +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`. + +Scala has many more specialized `Map` types, including `CollisionProofHashMap`, `HashMap`, `LinkedHashMap`, `ListMap`, `SortedMap`, `TreeMap`, `WeakHashMap`, and more. + + + +## Working with Sets + +The Scala [Set]({{site.baseurl}}/overviews/collections-2.13/sets.html) is an iterable collection with no duplicate elements. + +Scala has both mutable and immutable `Set` types. +This section demonstrates the _immutable_ `Set`. + + +### Creating a Set + +Create new empty sets like this: + +```scala +val nums = Set[Int]() +val letters = Set[Char]() +``` + +Create sets with initial data like this: + +```scala +val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) +val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') +``` + + +### Adding elements to a Set + +Add elements to an immutable `Set` using `+` and `++`, remembering to assign the result to a new variable: + +```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) +``` + +Notice that when you attempt to add duplicate elements, they’re quietly dropped. + + +### Deleting elements from a Set + +Remove elements from an immutable set using `-` and `--`, again assigning the result to a new variable: + +```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) +``` + + + +## Range + +The Scala `Range` is often used to populate data structures and to iterate over `for` loops. +These REPL examples demonstrate how to create ranges: + +{% comment %} +LATER: the dotty repl currently shows results differently +{% endcomment %} + +```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) +``` + +You can use ranges to populate collections: + +```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) +``` + +They’re also used in `for` loops: + +```` +scala> for i <- 1 to 3 do println(i) +1 +2 +3 +```` + +There are also `range` methods on : + +```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) +``` + +When you’re running tests, ranges are also useful for generating test collections: + +```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") +``` + + +## More details + +When you need more information about specialized collections, see the following resources: + +- [Concrete Immutable Collection Classes](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html) +- [Concrete Mutable Collection Classes](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html) +- [How are the collections structured? Which one should I choose?](https://docs.scala-lang.org/tutorials/FAQ/collections.html) + + + +[strict]: {% link _overviews/core/architecture-of-scala-213-collections.md %} diff --git a/_overviews/scala3-book/collections-intro.md b/_overviews/scala3-book/collections-intro.md new file mode 100644 index 0000000000..7ecf0a835e --- /dev/null +++ b/_overviews/scala3-book/collections-intro.md @@ -0,0 +1,24 @@ +--- +title: Scala Collections +type: chapter +description: This page provides and introduction to the common collections classes and their methods in Scala 3. +num: 36 +previous-page: packaging-imports +next-page: collections-classes +--- + +This chapter introduces the most common Scala 3 collections and their accompanying methods. +Scala comes with a wealth of collections types, but you can go a long way by starting with just a few of them, and later using the others as needed. +Similarly, each type has dozens of methods to make your life easier, but you can achieve a lot by starting with just a handful of them. + +Therefore, this section introduces and demonstrates the most common collections types and methods that you’ll need to get started. + + +{% comment %} +LATER: Use more of the content from this page: + https://docs.scala-lang.org/overviews/index.html +{% endcomment %} + + + + diff --git a/_overviews/scala3-book/collections-methods.md b/_overviews/scala3-book/collections-methods.md new file mode 100644 index 0000000000..fc99e6f700 --- /dev/null +++ b/_overviews/scala3-book/collections-methods.md @@ -0,0 +1,424 @@ +--- +title: Collections Methods +type: section +description: This page demonstrates the common methods on the Scala 3 collections classes. +num: 38 +previous-page: collections-classes +next-page: collections-summary +--- + + + +A great strength of Scala collections is that they come with dozens of methods out of the box, and those methods are consistently available across the immutable and mutable collections types. +The benefits of this are that you no longer need to write custom `for` loops every time you need to work with a collection, and when you move from one project to another, you’ll find these same methods used, rather than more custom `for` loops. + +There are *dozens* of methods available to you, so they aren’t all shown here. +Instead, only some of the most commonly-used methods are shown, including: + +- `map` +- `filter` +- `foreach` +- `head` +- `tail` +- `take`, `takeWhile` +- `drop`, `dropWhile` +- `reduce` + +The following methods work on all of the sequence types, including `List`, `Vector`, `ArrayBuffer`, etc., but these examples use a `List` unless otherwise specified. + +> As a very important note, none of the methods on `List` mutate the list. +> They all work in a functional style, meaning that they return a new collection with the modified results. + + + +## Examples of common methods + +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 don’t use lambdas: + +```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) +``` + + +### Higher-order functions and lambdas + +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: + +```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 +``` + +In those numbered examples: + +1. The first example shows the longest form. + 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 `_`. + +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: + +```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) +``` + +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: + +```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) +``` + +In the last example, when an anonymous function consists of one statement 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: + +```scala +// yields `List(100, 200)` +a.filter(_ < 40) + .takeWhile(_ < 30) + .map(_ * 10) +``` + + + +## Sample data + +The examples in the following sections use these lists: + +```scala +val oneToTen = (1 to 10).toList +val names = List("adam", "brandy", "chris", "david") +``` + + + +## `map` + +The `map` method steps through each element in the existing list, applying the function you supply to each element, one at a time; +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: + +```scala +scala> val doubles = oneToTen.map(_ * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` + +You can also write anonymous functions using a long form, like this: + +```scala +scala> val doubles = oneToTen.map(i => i * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` + +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: + +```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) +``` + +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. + + + +## `filter` + +The `filter` method creates a new list containing the element that satisfy the provided predicate. +A predicate, or condition, is a function that returns a `Boolean` (`true` or `false`). +Here are a few examples: + +```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) +``` + +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`: + +```scala +oneToTen.filter(_ < 4).map(_ * 10) +``` + +The REPL shows the result: + +```scala +scala> oneToTen.filter(_ < 4).map(_ * 10) +val res1: List[Int] = List(10, 20, 30) +``` + + + +## `foreach` + +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: + +```scala +scala> names.foreach(println) +adam +brandy +chris +david +``` + + + +## `head` + +The `head` method comes from Lisp and other earlier functional programming languages. +It’s used to print the first element (the head element) of a list: + +```scala +oneToTen.head // Int = 1 +names.head // adam +``` + +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: + +```scala +"foo".head // Char = 'f' +"bar".head // Char = 'b' +``` + +`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: + +```scala +val emptyList = List[Int]() // emptyList: List[Int] = List() +emptyList.head // java.util.NoSuchElementException: head of empty list +``` + +Because of this you may want to use `headOption` instead of `head`, especially when programming in a functional style: + +```scala +emptyList.headOption // Option[Int] = None +``` + +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. + + + +## `tail` + +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: + +```scala +oneToTen.head // Int = 1 +oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) + +names.head // adam +names.tail // List(brandy, chris, david) +``` + +Just like `head`, `tail` also works on strings: + +```scala +"foo".tail // "oo" +"bar".tail // "ar" +``` + +`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. + +A list can also be matched, so you can write expressions like this: + +```scala +val x :: xs = names +``` + +Putting that code in the REPL shows that `x` is assigned to the head of the list, and `xs` is assigned to the tail: + +scala> val x :: xs = names +val x: String = adam +val xs: List[String] = List(brandy, chris, david) + +Pattern matching like this is useful in many situations, such as writing a `sum` method using recursion: + +```scala +def sum(list: List[Int]): Int = list match + case Nil => 0 + case x :: xs => x + sum(xs) +``` + + + +## `take`, `takeRight`, `takeWhile` + +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`: + +```scala +oneToTen.take(1) // List(1) +oneToTen.take(2) // List(1, 2) + +oneToTen.takeRight(1) // List(10) +oneToTen.takeRight(2) // List(9, 10) +``` + +Notice how these methods work with “edge” cases, where we ask for more elements than are in the sequence, or ask for zero elements: + +```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() +``` + +And this is `takeWhile`, which works with a predicate function: + +```scala +oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) +names.takeWhile(_.length < 5) // List(adam) +``` + + +## `drop`, `dropRight`, `dropWhile` + +`drop`, `dropRight`, and `dropWhile` are essentially the opposite of their “take” counterparts, dropping elements from a list. +Here are some examples: + +```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) +``` + +Again notice how these methods work with edge cases: + +```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) +``` + +And this is `dropWhile`, which works with a predicate function: + +```scala +oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) +names.dropWhile(_ != "chris") // List(chris, david) +``` + + + +## `reduce` + +When you hear the term, “map reduce,” the “reduce” part refers to methods like `reduce`. +It takes a function (or anonymous function) and applies that function to successive elements in the list. + +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: + +```scala +def add(x: Int, y: Int): Int = + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +``` + +Given that method and this list: + +```scala +val a = List(1,2,3,4) +``` + +this is what happens when you pass the `add` method into `reduce`: + +```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 +``` + +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: + +```scala +scala> a.reduce(_ + _) +res0: Int = 10 +``` + +Similarly, a “product” algorithm looks like this: + +```scala +scala> a.reduce(_ * _) +res1: Int = 24 +``` + +> 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. + + + +## Even more + +There are literally dozens of additional methods on the Scala collections types that will keep you from ever needing to write another `for` loop. See [Mutable and Immutable Collections][mut-immut-colls] and [The Architecture of Scala Collections][architecture] for many more details on the Scala collections. + +> As a final note, if you’re using Java code in a Scala project, you can convert Java collections to Scala collections. +> By doing this you can use those collections in `for` expressions, and can also take advantage of Scala’s functional collections methods. +> See the [Interacting with Java][interacting] section for more details. + + + +[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/_overviews/scala3-book/collections-summary.md b/_overviews/scala3-book/collections-summary.md new file mode 100644 index 0000000000..32aa74cd0c --- /dev/null +++ b/_overviews/scala3-book/collections-summary.md @@ -0,0 +1,30 @@ +--- +title: Summary +type: section +description: This page provides a summary of the Collections chapter. +num: 39 +previous-page: collections-methods +next-page: fp-intro +--- + +This chapter provides a summary of the common Scala 3 collections and their accompanying methods. +As shown, Scala comes with a wealth of collections and methods. + +When you need to see more details about the collections types shown in this chapter, see their Scaladoc pages: + +- [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) + +Also mentioned are the immutable `Map` and `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) + +and the mutable `Map` and `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/_overviews/scala3-book/concurrency.md b/_overviews/scala3-book/concurrency.md new file mode 100644 index 0000000000..e4140c2eb5 --- /dev/null +++ b/_overviews/scala3-book/concurrency.md @@ -0,0 +1,309 @@ +--- +title: Concurrency +type: chapter +description: This page discusses how Scala concurrency works, with an emphasis on Scala Futures. +num: 68 +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. + + + +## Introduction + +Here’s a description of the Scala `Future` from its Scaladoc: + +> “A `Future` represents a value which may or may not _currently_ be available, but will be available at some point, or an exception if that value could not be made available.” + +To demonstrate what that means, let’s first look at single-threaded programming. +In the single-threaded world you bind the result of a method call to a variable like this: + +```scala +def aShortRunningTask(): Int = 42 +val x = aShortRunningTask() +``` + +In this code, the value `42` is immediately bound to `x`. + +When you’re working with a `Future`, the assignment process looks similar: + +```scala +def aLongRunningTask(): Future[Int] = ??? +val x = aLongRunningTask() +``` + +But the main difference in this case is that because `aLongRunningTask` takes an indeterminate amount of time to return, the value in `x` may or may not be _currently_ available, but it will be available at some point -- in the future. + +Another way to look at this is in terms of blocking. +In this single-threaded example, the `println` statement isn’t printed until `aShortRunningTask` completes: + +```scala +def aShortRunningTask(): Int = + Thread.sleep(500) + 42 +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. + +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. + +> When you think about futures, it’s important to know that they’re intended as a one-shot, “Handle this relatively slow computation on some other thread, and call me back with a result when you’re done” construct. +> As a point of contrast, [Akka](https://akka.io) actors are intended to run for a long time and respond to many requests during their lifetime. +> While an actor may live forever, a future is intended to be run only once. + + + +## An example in the REPL + +A future is used to create a temporary pocket of concurrency. +For instance, you use a future when you need to call an algorithm that runs an indeterminate amount of time — such as calling a remote microservice — so you want to run it off of the main thread. + +To demonstrate how this works, let’s start with a `Future` example in the REPL. +First, paste in these required `import` statements: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +``` + +Now you’re ready to create a future. +For this example, first define a long-running, single-threaded algorithm: + +```scala +def longRunningAlgorithm = + Thread.sleep(10_000) + 42 +``` + +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 +scala> val f = Future(longRunningAlgorithm) +f: scala.concurrent.Future[Int] = Future(<not completed>) +``` + +Right away your future begins running. +If you immediately check the value of the variable `f`, you see that the future hasn’t completed yet: + +```scala +scala> f +val res1: scala.concurrent.Future[Int] = Future(<not completed>) +``` + +But if you check again after ten seconds, you’ll see that it completes successfully: + +```scala +scala> f +val res2: scala.concurrent.Future[Int] = Future(Success(42)) +``` + +While that’s a relatively simple example, it shows the basic approach: Just construct a new `Future` with your long-running algorithm. + +One thing to notice is that the `42` you expected is wrapped in a `Success`, which is further wrapped in a `Future`. +This is a key concept to understand: the value in a `Future` is always an instance of one of the *scala.util.Try* types: `Success` or `Failure`. +Therefore, when you work with the result of a future, you use the usual `Try`-handling techniques. + + +### 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`: + +```scala +scala> val a = f.map(_ * 2) +a: scala.concurrent.Future[Int] = Future(<not completed>) +``` + +As shown, for the future that was created with the `longRunningAlgorithm`, the initial output shows `Future(<not completed>)`. +But when you check `a`’s value after ten seconds you’ll see that it contains the expected result of `84`: + +```scala +scala> a +res1: scala.concurrent.Future[Int] = Future(Success(84)) +``` + +Once again, the successful result is wrapped inside a `Success` and a `Future`. + + +### Using callback methods with futures + +In addition to higher-order functions like `map`, you can also use callback methods with futures. +One commonly used callback method is `onComplete`, which takes a *partial function* in which you handle the `Success` and `Failure` cases: + +```scala +f.onComplete { + case Success(value) => println(s"Got the callback, value = $value") + case Failure(e) => e.printStackTrace +} +``` + +When you paste that code in the REPL you’ll see the result: + +```scala +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: + +- `filter` +- `flatMap` +- `map` + +Its callback methods are: + +- `onComplete` +- `andThen` +- `foreach` + +Other transformation methods include: + +- `fallbackTo` +- `recover` +- `recoverWith` + +See the [Futures and Promises][futures] page for a discussion of additional methods available to futures. + + + +## Running multiple futures and joining their results + +To run multiple futures in parallel and join their results when all of the futures complete, use a `for` expression. +The correct approach is: + +1. Create the futures +2. Merge their results in a `for` expression +3. Extract the merged result using `onComplete` or a similar technique + + +### An example + +The three steps of the correct approach are shown in the following example. +A key is that you first create the futures and then join them in the `for` expression: + +```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) create the 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) run them simultaneously 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) +``` + +When you run that application, you see output that looks like this: + +```` +creating the futures: 1 +before the 'sleep(3000)': 2 +in the 'yield': 806 +in the Success case: 806 +result = 6 +```` + +As that output shows, the futures are created very rapidly, and in just two milliseconds the print statement right before the `sleep(3000)` statement at the end of the method is reached. +All of that code is run on the JVM’s main thread. +Then, at 806 ms, the three futures complete and the code in the `yield` block is run. +Then the code immediately goes to the `Success` case in the `onComplete` method. + +The 806 ms output is a key to seeing that the three futures are run in parallel. +If they were run sequentially, the total time would be about 1,400 ms — the sum of the sleep times of the three futures. +But because they’re run in parallel, the total time is just slightly longer than the longest-running future: `f1`, which is 800 ms. + + +### A method that returns a future + +So far you’ve seen how to pass a single-threaded algorithm into a `Future` constructor. +You can use the same technique to create a method that returns a `Future`: + +```scala +// simulate a slow-running method +def slowlyDouble(x: Int, delay: Long): Future[Int] = Future { + sleep(delay) + x * 2 +} +``` + +As with the previous examples, just assign the result of the method call to a new variable. +Then when you check the result right away you’ll see that it’s not completed, but after the delay time the future will have a result: + +```` +scala> val f = slowlyDouble(2, 5_000L) +val f: concurrent.Future[Int] = Future(<not completed>) + +scala> f +val res0: concurrent.Future[Int] = Future(<not completed>) + +scala> f +val res1: concurrent.Future[Int] = Future(Success(4)) +```` + + + +## Key points about futures + +Hopefully those examples give you an idea of how Scala futures work. +To summarize, a few key points about futures are: + +- You construct futures to run tasks off of the main thread +- Futures are intended for one-shot, potentially long-running concurrent tasks that *eventually* return a value; they create a temporary pocket of concurrency +- A future starts running as soon as you construct it +- A benefit of futures over threads is that they work with `for` expressions, and come with a variety of callback methods that simplify the process of working with concurrent threads +- When you work with futures you don’t have to concern yourself with the low-level details of thread management +- You handle the result of a future with callback methods like `onComplete` and `andThen`, or transformation methods like `filter`, `map`, etc. +- The value inside a `Future` is always an instance of one of the `Try` types: `Success` or `Failure` +- If you’re using multiple futures to yield a single result, combine them in a `for` expression + +Also, as you saw with the `import` statements in these examples, the Scala `Future` depends on an `ExecutionContext`. + +For more details about futures, see [Futures and Promises][futures], an article that discusses futures, promises, and execution contexts. +It also provides a discussion of how a `for` expression is translated into a `flatMap` operation. + + + +[futures]: {% link _overviews/core/futures.md %} diff --git a/_overviews/scala3-book/control-structures.md b/_overviews/scala3-book/control-structures.md new file mode 100644 index 0000000000..b311651394 --- /dev/null +++ b/_overviews/scala3-book/control-structures.md @@ -0,0 +1,567 @@ +--- +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 +next-page: domain-modeling-intro +--- + + +Scala has the control structures you expect to find in a programming language, including: + +- `if`/`then`/`else` +- `for` loops +- `while` loops +- `try`/`catch`/`finally` + +It also has two other powerful constructs that you may not have seen before, depending on your programming background: + +- `for` expressions (also known as _`for` comprehensions_) +- `match` expressions + +These are all demonstrated in the following sections. + + + +## The if/then/else construct + +A one-line Scala `if` statement looks like this: + +```scala +if x == 1 then println(x) +``` + +When you need to run multiple lines of code after an `if` equality comparison, use this syntax: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +``` + +The `if`/`else` syntax looks like this: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +else + println("x was not 1") +``` + +And this is the `if`/`else if`/`else` syntax: + +```scala +if x < 0 then + println("negative") +else if x == 0 + println("zero") +else + println("positive") +``` + +You can optionally include an `end if` statement at the end of each expression, if you prefer: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +end if +``` + + +### `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: + +```scala +val minValue = if a < b then a else b +``` + +Because they return a value, you can use `if`/`else` expressions as the body of a method: + +```scala +def compare(a: Int, b: Int): Int = + if a < b then + -1 + else if a == b then + 0 + else + 1 +``` + +### 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_: + +```scala +val minValue = if a < b then a else b +``` + +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: + +```scala +if a == b then action() +println("Hello") +``` + +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: + +```scala +val ints = Seq(1, 2, 3) +for i <- ints do println(i) +``` + +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) +``` + +Regardless of which approach you use, this is what the result looks like in the Scala REPL: + +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for i <- ints do println(i) +1 +2 +3 +```` + +When you need a multiline block of code following the `if` condition, use either of these approaches: + +```scala +// option 1 +for + i <- ints +do + val x = i * 2 + println(s"i = $i, x = $x") + +// option 2 +for (i <- ints) + val x = i * 2 + println(s"i = $i, x = $x") + +// option 3 +for (i <- ints) { + val x = i * 2 + println(s"i = $i, x = $x") +} +``` + +### Multiple generators + +`for` loops can have multiple generators, as shown in this example: + +```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") +``` + +That expression prints this output: + +```` +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 +```` + +### Guards + +`for` loops can also contain `if` statements, which are known as _guards_: + +```scala +for + i <- 1 to 5 + if i % 2 == 0 +do + println(i) +``` + +The output of that loop is: + +```` +2 +4 +```` + +A `for` loop can have as many guards as needed. +This example shows one way to print the number `4`: + +```scala +for + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +do + println(i) +``` + +### 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: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AR" -> "Arizona" +) +``` + +You can print the keys and values using `for`, like this: + +```scala +for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +``` + +Here’s what that looks like in the REPL: + +```scala +scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` + +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: + +```scala +(abbrev, fullName) <- states +``` + +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`. + +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: + +```scala +val list = + for + i <- 10 to 12 + yield + i * 2 + +// result: list == Vector(20, 22, 24) +``` + +After that `for` expression runs, the variable `list` is a `Vector` that contains the values shown. +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`. + 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`. + The loop completes at this point and yields the final result, the `Vector(20,22,24)`. + +{% comment %} +NOTE: This is a place where it would be great to have a TIP or NOTE block: +{% endcomment %} + +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: + +```scala +val list = (10 to 12).map { i => i * 2} +``` + +`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. + +Here’s an example that shows how to use a block of code after the `yield`: + +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// result: List[String] = List(Olivia, Walter, Peter) +``` + + +### 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`: + +```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)) // result: List(3, 7) +``` + + + +## `while` loops + +Scala `while` loop syntax looks like this: + +```scala +var i = 0 + +while i < 3 do + println(i) + i += 1 +``` + +If you use parentheses around the test condition, it can also be written like this: + +```scala +var i = 0 + +while (i < 3) { + println(i) + i += 1 +} +``` + + + +## `match` expressions + +Pattern matching is a major feature of functional programming languages, and Scala includes a `match` expression that has many capabilities. + +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: + +```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 +``` + +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"`. + +> 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: + +```scala +i match + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what" ) +``` + +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. + + +### 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: + +```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") +``` + + +### Using `if` expressions in `case` statements + +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: + +```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") +``` + +Here’s another example, which shows how to match a given value against ranges of numbers: + +```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...") +``` + + +#### 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: + +```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!" +``` + + +### 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 `Boolean` value as an input parameter, and returns a `String`, based on the result of the `match` expression: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true +``` + +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: +The first one checks whether the given value is either the integer `0` or an empty string and returns `false` in this case. +In the default case, we return `true` for any other value. +These examples show how this method works: + +```scala +isTruthy(0) // false +isTruthy("") // false +isTruthy(1) // true +isTruthy(" ") // true +isTruthy(2F) // true +``` + +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: + +- Constant patterns (such as `case 3 => `) +- Sequence patterns (such as `case List(els : _*) =>`) +- Tuple patterns (such as `case (x, y) =>`) +- Constructor pattern (such as `case Person(first, last) =>`) +- Type test patterns (such as `case p: Person =>`) + +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`: + +```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" +``` + +{% 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 + +Like Java, Scala has a `try`/`catch`/`finally` construct to let you catch and manage exceptions. +For consistency, Scala uses the same syntax that `match` expressions use and supports pattern matching on the different possible exceptions that can occur. + +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`: + +```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.") +``` + +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 new file mode 100644 index 0000000000..05f10432a4 --- /dev/null +++ b/_overviews/scala3-book/domain-modeling-fp.md @@ -0,0 +1,483 @@ +--- +title: FP Modeling +type: section +description: This chapter provides an introduction to FP domain modeling with Scala 3. +num: 22 +previous-page: domain-modeling-oop +next-page: methods-intro +--- + + +This chapter provides an introduction to domain modeling using functional programming (FP) in Scala 3. +When modeling the world around us with FP, you typically use these Scala constructs: + +- Enumerations +- Case classes +- Traits + +> 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. + +The concept is similar to numerical algebra. +When you think about whole numbers whose values are greater than or equal to zero, you have a *set* of possible values that looks like this: + +```` +0, 1, 2 ... Int.MaxInt +```` + +Ignoring the division of whole numbers, the possible *operations* on those values are: + +```` ++, -, * +```` + +An FP design is implemented in a similar way: + +- You describe your set of values (your data) +- You describe operations that work on those values (your functions) + +> As we will see, reasoning about programs in this style is quite different from the object-oriented programming. +> Data in FP simply **is**: +> Separating functionality from your data let's you inspect your data without having to worry about behavior. + +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 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: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` +> 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: + +```scala +import CrustSize._ +import CrustType._ +import Topping._ + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` +> Data Types that aggregate multiple components (like `Pizza`) are also sometimes referred to as _product types_. + +And that’s it. +That’s the data model for an FP-style pizza system. +This solution is very concise because it doesn’t require the operations on a pizza to be combined with the data model. +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: + +```scala +val myFavPizza = Pizza(Small, Regular, Seq(Cheese, Pepperoni)) +println(myFavPizza.crustType) // prints Regular +``` + + +#### 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: + +```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 +) +``` + +#### “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? + +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. + +```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 +``` +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. + +```scala +def toppingPrice(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` +Similarly, since `toppingPrice` 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. +```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 +``` +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). +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 %} + + + +## 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. + +There are several different ways to implement and organize behaviors: + +- Define your functions in companion objects +- Use a modular programming style +- Use a “functional objects” approach +- Define the functionality in extension methods + +These different solutions are shown in the remainder of this section. + +### Companion Object + +A first approach is to define the behavior — the functions — in a companion object. + +> As discussed in the Domain Modeling [Tools section][modeling-tools], a _companion object_ is an `object` that has the same name as a class, and is declared in the same file as the class. + +With this approach, in addition to the enumeration or case class you also define an equally named companion object that contains the behavior. + +```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 +``` +With this approach you can create a `Pizza` and compute its price like this: + +```scala +val pizza1 = Pizza(Small, Thin, Seq(Cheese, Onions)) +Pizza.price(pizza1) +``` + +Grouping functionality this way has a few advantages: + +- It associates functionality with data and makes it easier to find for programmers (and the compiler). +- It creates a namespace and for instance let's us use `price` as a method name without having to rely on overloading. +- The implementation of `Topping.price` can access enumeration values like `Cheese` without having to import them. + +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`. + + +## 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.” +Let’s look at what this means. + +### Creating a `PizzaService` interface + +The first thing to think about are the `Pizza`s “behaviors”. +When doing this, you sketch a `PizzaServiceInterface` trait like this: + +```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 +``` + +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.” + +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: + +```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) +``` + +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. +> In the first step, you sketch the contract of your API as an *interface*. +> 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: + +```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 +``` + +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`: + +```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(p4.price) // prints 8.75 +``` + +### Functional Objects + +In the book, *Programming in Scala*, the authors define the term, “Functional Objects” as “objects that do not have any mutable state”. +This is also the case for types in `scala.collection.immutable`. +For example, methods on `List` do not mutate the interal state, but instead create a copy of the `List` as a result. + +You can think of this approach as a “hybrid FP/OOP design” because you: + +- Model the data using immutable `case` classes. +- Define the behaviors (methods) in the _same type_ as the data. +- Implement the behavior as pure functions: They don’t mutate any internal state; rather, they return a copy. + +> 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: + +```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) +``` + +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: + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +### 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. +This can have multiple advantages: + +- Our data model is again _very concise_ and does not mention any behavior. +- We can equip types with additional methods _retroactively_ without having to change the original definition. +- Other than companion objects or direct methods on the types, extension methods can be defined _externally_ in another file. + +Let us revisit our example once more. + +```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) +``` +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. + +This way, we can obtain the same API as before + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` +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. +Then, to model the behavior, define functions that operate on values of your data model. +We have seen different ways to organize your functions: + +- You can put your methods in companion objects +- You can use a modular programming style, separating interface and implementation +- 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 new file mode 100644 index 0000000000..6707e9ddfe --- /dev/null +++ b/_overviews/scala3-book/domain-modeling-intro.md @@ -0,0 +1,14 @@ +--- +title: Domain Modeling +type: chapter +description: This chapter provides an introduction to domain modeling in Scala 3. +num: 19 +previous-page: control-structures +next-page: domain-modeling-tools +--- + +This chapter shows how you can model the world around you with Scala 3: + +- The Tools section introduces the tools that are available to you, including classes, traits, enums, and more +- The OOP Modeling section looks at modeling attributes and behaviors in an object-oriented programming (OOP) style +- The FP Modeling section looks at domain modeling in a functional programming (FP) style diff --git a/_overviews/scala3-book/domain-modeling-oop.md b/_overviews/scala3-book/domain-modeling-oop.md new file mode 100644 index 0000000000..08e148e3bc --- /dev/null +++ b/_overviews/scala3-book/domain-modeling-oop.md @@ -0,0 +1,295 @@ +--- +title: OOP Modeling +type: section +description: This chapter provides an introduction to OOP domain modeling with Scala 3. +num: 21 +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. + + + +## Introduction + +Scala provides all the necessary tools for object-oriented design: + +- **Traits** let you specify (abstract) interfaces, as well as concrete implementations. +- **Mixin Composition** gives you the tools to compose components from smaller parts. +- **Classes** can implement the interfaces specified by traits. +- **Instances** of classes can have their own private state. +- **Subtyping** lets you use an instance of one class where an instance of a superclass is expected. +- **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. +They can serve to describe abstract interfaces like: + +```scala +trait Showable: + def show: String +``` + +and can also contain concrete implementations: +```scala +trait Showable: + def show: String + def showHtml = "<p>" + show + "</p>" +``` +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: + +- **abstract members** as _required_ services: they still need to be implemented by a subclass. +- **concrete members** as _provided_ services: they are provided to the subclass. + +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`: + +```scala +class Document(text: String) extends Showable: + def show = text +``` +#### Abstract Members +Abstract methods are not the only thing that can be left abstract in a trait. +A trait can contain: + +- abstract methods (`def m(): T`) +- abstract value definitions (`val x: T`) +- abstract type members (`type T`), potentially with bounds (`type T <: S`) +- abstract givens (`given t: T`) + +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: +```scala +trait GreetingService: + def translate(text: String): String + def sayHello = translate("Hello") + +trait TranslationService: + def translate(text: String): String = "..." +``` +To compose the two services, we can simply create a new trait extending them: +```scala +trait ComposedService extends GreetingService, TranslationService +``` +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.). + +## 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: + +{% comment %} +NOTE: I think “leaves” may technically be the correct word to use, but I prefer “leafs.” +{% endcomment %} + +```text +traits T1 T2 ... T3 +composed traits S extends T1, T2 ... S extends T2, T3 +classes C extends S, T3 +instances new C +``` +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): +```scala +class MyService(name: String) extends ComposedService, Showable: + def show = s"$name says $sayHello" +``` +#### Subtyping +We can create an instance of `MyService` as follows: +```scala +val s1: MyService = MyService("Service 1") +``` +Through the means of subtyping, our instance `s1` can be used everywhere that any of the extended traits is expected: +```scala +val s2: GreetingService = s1 +val s3: TranslationService = s1 +val s4: Showable = s1 +// ... and so on ... +``` + +#### Planning for Extension +As mentioned before, it is possible to extend another class: +```scala +class Person(name: String) +class SoftwareDeveloper(name: String, favoriteLang: String) + extends Person(name) +``` +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`][open]: +```scala +open class Person(name: String) +``` +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. + +{% 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 %} + + + +## Instances and Private Mutable State +Like in other languages with support for OOP, traits and classes in Scala can define mutable fields: +```scala +class Counter: + // can only be observed by the method `count` + private var currentCount = 0 + + def tick() = currentCount += 1 + def count: Int = currentCount +``` +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: +```scala +val c1 = Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +#### 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. +{% comment %} +TODO: I’m not sure what I added in that last sentence is accurate or helpful, but I wanted to add a note about why readers shouldn’t worry about understanding the example. +{% endcomment %} + +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): + +```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 + } +``` +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 `SubjectObserer` are free to chose 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`. + +The second trait, `Subject`, defines one private field `observers` to store all observers that subscribed to this particular subject. +Subscribing to a subject simply stores the object into this list. +Again, the type of parameter `obs` is `O`, not `Observer`. + +#### Selftype Annotations +Finally, you might have wondered what the `self: S =>` on trait `Subject` is supposed to mean. +This is called a _selftype annotation_. +It requires subtypes of `Subject` to also be subtypes of `S`. +This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. +If `S` was a _concrete_ type, the selftype 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: + +```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}") +``` +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: + +- 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. +- The class `Display` extends the trait `Observer`, and implements the missing method `notify`. +{% comment %} +NOTE: You might say “the abstract method `notify`” in that last sentence, but I like “missing.” +{% endcomment %} + +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: +```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 +``` +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 new file mode 100644 index 0000000000..32312e6a2d --- /dev/null +++ b/_overviews/scala3-book/domain-modeling-tools.md @@ -0,0 +1,704 @@ +--- +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 +previous-page: domain-modeling-intro +next-page: domain-modeling-oop +--- + + +Scala 3 provides us with many different tools to model the world around us: + +- Classes +- Objects +- Companion objects +- Traits +- Abstract classes +- Enums +- 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: + +```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) +``` +The examples above show that Scala has a very lightweight way to declare classes. +The definition of the class `Person` roughly corresponds to the following, more explicit, version + +```scala +class Person: + // fields + var name: String = null + var vocation: String = null + + // constructor + def this(_name: String, _vocation: String) = + // call to the super constructor + this() + // assigning the fields + name = _name + vocation = _vocation +``` +defining the two fields `name` and `vocation` together with a constructor that accepts values for the two fields and assigns them. + +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. +If you want them to be immutable — read only — create them as `val` fields instead. + +Prior to Scala 3, you used the `new` keyword to create a new instance of a class: + +```scala +val p = new Person("Robert Allen Zimmerman", "Harmonica Player") +// --- +``` + +However, with [creator applications][creator] this isn’t required in Scala 3: + +```scala +val p = Person("Robert Allen Zimmerman", "Harmonica Player") +``` + +Once you have an instance of a class, you can access its fields, which in this example are all constructor parameters: + +```scala +p.name // "Robert Allen Zimmerman" +p.vocation // "Harmonica Player" +``` + +As mentioned, all of these parameters were created as `var` fields, so you can also mutate them: + +```scala +p.name = "Bob Dylan" +p.vocation = "Musician" +``` + +### Fields and methods + +Classes can have 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: + +```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") +``` + +The following REPL session shows how to create a new `Person` instance with this class: + +```` +scala> val john = Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` + +Classes can also extend traits and abstract classes, which we cover in dedicated sections below. + +### Default parameter values + +As a quick look at a few other features, class constructor parameters can also have default values: + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000): + override def toString = s"timeout: $timeout, linger: $linger" +``` + +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: + +```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 +``` + +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: + +```scala +// option 1 +val s = Socket(10_000, 10_000) + +// option 2 +val s = Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +### Auxiliary constructors + +You can define a class to have multiple constructors so consumers of your class can build it in different ways. +For example, let’s assume that you need to write some code to model students in a college admission system. +While analyzing the requirements you’ve seen that you need to be able to construct a `Student` instance in three ways: + +- With a name and government ID, for when they first start the admissions process +- With a name, government ID, and an additional application date, for when they submit their application +- With a name, government ID, and their student ID, for after they’ve been admitted + +One way to handle this situation in an OOP style is with this code: + +```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 +``` + +{% comment %} +// for testing that code +override def toString = s""" +|Name: $name +|GovtId: $govtId +|StudentId: $_studentId +|Date Applied: $_applicationDate +""".trim.stripMargin +{% endcomment %} + +The class has three constructors, given by the numbered comments in the code: + +1. The primary constructor, given by the `name` and `govtId` in the class definition +2. An auxiliary constructor with the parameters `name`, `govtId`, and `applicationDate` +3. Another auxiliary constructor with the parameters `name`, `govtId`, and `studentId` + +Those constructors can be called like this: + +```scala +val s1 = Student("Mary", "123") +val s2 = Student("Mary", "123", LocalDate.now) +val s3 = Student("Mary", "123", 456) +``` + +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. +It’s initialized lazily when its members are referenced, similar to a `lazy val`. +Objects in Scala allow grouping methods and fields under one namespace, similar to how can use `static` members on a class in Java, Javascript (ES6) or `@staticmethod` in Python. + +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: + +```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 = + if s == null || s.trim.equals("") then true else false +``` + +We can use the object as follows: +```scala +StringUtil.truncate("Chuck Bartowski", 5) // "Chuck" +``` + +Importing in Scala is very flexible and allows us to import all members of an object: + +```scala +import StringUtils._ +truncate("Chuck Bartowski", 5) // "Chuck" +``` + +Objects can also contain fields, which are also accessed like static members: + +```scala +object MathConstants: + val PI = 3.14159 + val E = 2.71828 + +println(MathConstants.PI) // 3.14159 +``` + + + +## Companion objects + +An `object` that has the same name as a class, and is declared in the same file as the class, is called a _"companion object"_. +Similarly, the corresponding class is called the object’s companion class. +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: + +```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 +``` + +In this example the `area` method that’s available to each instance uses the `calculateArea` method that’s defined in the companion object. +If you’re familiar with Java, `calculateArea` is similar to a static method. +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. + +### Other uses + +Companion objects can be used for several purposes: + +- As shown, they can be used to group “static” methods under a namespace + - These methods can be public or private + - If `calculateArea` was public, it would be accessed as `Circle.calculateArea` +- They can contain `apply` methods, which — thanks to some syntactic sugar — work as factory methods to construct new instances +- They can contain `unapply` methods, which are used to deconstruct objects, such as with pattern matching + +Here’s a quick look at how `apply` methods that can be used as factory methods to create new objects: + +```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 +``` + +The `unapply` method isn’t covered here, but it’s covered in the [Reference documentation][unapply]. + + + +## Traits + +If you’re familiar with Java, a Scala trait is similar to an interface in Java 8+. Traits can contain: + +- Abstract methods and fields +- Concrete methods and fields + +In a basic use, a trait can be used as an interface, defining only abstract members that will be implemented by other classes: + +```scala +trait Employee: + def id: Int + def firstName: String + def lastName: String +``` +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: + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +``` + +Here’s another trait with an abstract member and two concrete implementations: + +```scala +trait HasTail: + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +``` + +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: + +```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" +``` + +Notice that the `IrishSetter` class implements the abstract members that are defined in `HasLegs` and `HasTail`. +Now you can create new `IrishSetter` instances: + +```scala +val d = IrishSetter("Big Red") // "Big Red is a Dog" +``` + +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 %} +TODO: I have some notes on when to use abstract classes, and can update this section. +{% endcomment %} + +When you want to write a class, but you know it will have abstract members, you can either create a trait or an abstract class. +In most situations you’ll use traits, but but historically there have been two situations where it’s better to use an abstract class than a trait: + +- You want to create a base class that takes constructor arguments +- The code will be called from Java code + +### A base class that takes constructor arguments + +Prior to Scala 3, when a base class needed to take constructor arguments, you’d declare it as an `abstract class`: + +```scala +abstract class Pet(name: String): + def greeting: String + def age: Int + override def toString = s"I say $greeting, and I’m $age" + +class Dog(name: String, age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +However, with Scala 3, traits can now have [parameters][trait-params], so you can now use traits in the same situation: + +```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) +``` +Traits are more flexible to compose (you can mix in multiple traits, but only extend one class) and should most of the time be preferred to classes. +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 + +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: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +To use them in other code, first import them, and then use them: + +```scala +import CrustSize._ +val currentCrustSize = Small +``` + +Enum values can be compared using equals (`==`) and matched on: + +```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") +``` + +### Additional Enum Features + +Enumerations can also be parameterized: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +And they can also have members (like fields and methods): + +```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 ... +``` + +### 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: + +```scala +enum Color extends Enum[Color] { case Red, Green, Blue } +``` + +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. + +After defining `Color` like that, you can use it like you would a Java enum: + +```` +scala> Color.Red.compareTo(Color.Green) +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 +case class Person(name: String, relation: String) +``` +Since we declared `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: +```scala +val christina = Person("Christina", "niece") +``` +Note that the fields can’t be mutated: +```scala +christina.name = "Fred" // error: reassignment to val +``` +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 + +These additional features are demonstrated in the below example: +```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) +``` + + +### Support for functional programming + +As mentioned, case classes support functional programming (FP): + +- In FP you try to avoid mutating data structures. + It thus makes sense that constructor fields default to `val`. + Since instances of case classes are not changed, they can easily be shared without fearing mutation or race conditions. +- Instead of mutating one 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. + +### An `unapply` method + +A great thing about a case class is that it automatically generates an `unapply` method for your class, so you don’t have to write one. + +To demonstrate this, imagine that you have this trait: + +```scala +trait Person: + def name: String +``` + +Then, create these case classes to extend that trait: + +```scala +case class Student(name: String, year: Int) extends Person +case class Teacher(name: String, specialty: String) extends Person +``` + +Because those are defined as case classes — and they have built-in `unapply` methods — you can write a match expression like this: + +```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." +``` + +Notice these two patterns in the `case` statements: + +```scala +case Student(name, year) => +case Teacher(name, whatTheyTeach) => +``` + +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*. + +> The Scala standard is that an `unapply` method returns the case class constructor fields in a tuple that’s wrapped in an `Option`. +> The “tuple” part of the solution was shown in the previous lesson. + +To show how that code works, create an instance of `Student` and `Teacher`: + +```scala +val s = Student("Al", 1) +val t = Teacher("Bob Donnan", "Mathematics") +``` + +Next, this is what the output looks like in the REPL when you call `getPrintableString` with those two instances: + +```scala +scala> getPrintableString(s) +res0: String = Al is a student in Year 1. + +scala> getPrintableString(t) +res1: String = Bob Donnan teaches Mathematics. +``` + +> 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 + +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: + +```scala +class Person(var name: String, var age: Int) +object Person: + def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) +``` + +Because it defines an `unapply` method, and because that method returns a tuple, you can now use `Person` with a `match` expression: + +```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" +``` +{% endcomment %} + + + +## Case objects + +Case objects are to objects what case classes are to classes: they provide a number of automatically-generated methods to make them more powerful. +They’re particularly useful whenever you need a singleton object that needs a little extra functionality, such as being used with pattern matching in `match` expressions. + +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: + +```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 +``` + +Then in other parts of your code you can write methods like this, which use pattern matching to handle the incoming message: + +```scala +def handleMessages(msg: Message) = message match + case PlaySong(name) => playSong(name) + case IncreaseVolume(amt) => changeVolume(amt) + case DecreaseVolume(amt) => changeVolume(-amt) + case StopPlaying => stopPlayingMusic +``` +[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/_overviews/scala3-book/first-look-at-types.md b/_overviews/scala3-book/first-look-at-types.md new file mode 100644 index 0000000000..2e3a3e0630 --- /dev/null +++ b/_overviews/scala3-book/first-look-at-types.md @@ -0,0 +1,279 @@ +--- +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. +num: 17 +previous-page: taste-summary +next-page: control-structures +--- + + + +## All values have a type + +In Scala, all values have a type, including numerical values and functions. +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"_. +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`. +`Unit` is a value type which carries no meaningful information. +There is exactly one instance of `Unit` which we can refer to as: `()`. + +*`AnyRef`* represents reference types. +All non-value types are defined as reference types. +Every user-defined type in Scala is a subtype of `AnyRef`. +If Scala is used in the context of a Java runtime environment, `AnyRef` corresponds to `java.lang.Object`. + +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: + +```scala +def printIt(a: Any): Unit = println(a) +``` + +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: + +```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)) +``` + +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. + +Here’s the output of the program: + +``` +a string +732 +c +true +<function> +``` + +## Scala’s “value types” + +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: + +```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 +``` + +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: + +```scala +val i = 123 // defaults to Int +val x = 1.0 // defaults to Double +``` + +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: + +```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-324d to 1.79769313486231570e+308d | +| 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) +``` + +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: + +```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 +``` + + + +## 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 +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +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 +println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +``` + + +#### 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: + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +One drawback of this basic approach is that the lines after the first line are indented, and look like this: + +```scala +"The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting." +``` + +When spacing is important, put a `|` symbol in front of all lines after the first line, and call the `stripMargin` method after the string: + +```scala +val quote = """The essence of Scala: + |Fusion of functional and object-oriented + |programming in a typed setting.""".stripMargin +``` + +Now all of the lines are left-justified inside the string: + +```scala +"The essence of Scala: +Fusion of functional and object-oriented +programming in a typed setting." +``` + + + +## Type casting + +Value types can be cast in the following way: +<a href="{{ site.baseurl }}/resources/images/tour/type-casting-diagram.svg"><img style="width:100%" src="{{ site.baseurl }}/resources/images/tour/type-casting-diagram.svg" alt="Scala Type Hierarchy"></a> + +For example: + +```scala +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +Casting is unidirectional. +This will not compile: + +``` +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 +val z: Long = y // Does not conform +``` + +You can also cast a reference type to a subtype. +This will be covered later in the tour. + + + +## `Nothing` and `null` + +`Nothing` is a subtype of all types, also called the **bottom type**. +There is no value that has the type `Nothing`. +A common use is to signal non-termination, such as a thrown exception, program exit, or an infinite loop — i.e., it is the type of an expression which does not evaluate to a value, or a method that does not return normally. + +`Null` is a subtype of all reference types (i.e. any subtype of `AnyRef`). +It has a single value identified by the keyword literal `null`. +`Null` is provided mostly for interoperability with other JVM languages and should almost never be used in Scala code. +Alternatives to `null` are discussed in the [Functional Programming chapter][fp] of this book, and the [API documentation][option-api]. + + + +[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 %} +[option-api]: https://dotty.epfl.ch/api/scala/Option.html diff --git a/_overviews/scala3-book/fp-functional-error-handling.md b/_overviews/scala3-book/fp-functional-error-handling.md new file mode 100644 index 0000000000..24ffe3919c --- /dev/null +++ b/_overviews/scala3-book/fp-functional-error-handling.md @@ -0,0 +1,329 @@ +--- +title: Functional Error Handling +type: section +description: This section provides an introduction to functional error handling in Scala 3. +num: 45 +previous-page: fp-functions-are-values +next-page: fp-summary +--- + + + +Functional programming is like writing a series of algebraic equations, and because algebra doesn’t have null values or throw exceptions, you don’t use these features in FP. +This brings up an interesting question: In the situations where you might normally use a null value or exception in OOP code, what do you do? + +Scala’s solution is to use constructs like the `Option`/`Some`/`None` classes. +This lesson provides an introduction to using these techniques. + +Two notes before we jump in: + +- The `Some` and `None` classes are subclasses of `Option`. +- Instead of repeatedly saying “`Option`/`Some`/`None`,” the following text generally just refers to “`Option`” or “the `Option` classes.” + + + +## A first example + +While this first example doesn’t deal with null values, it’s a good way to introduce the `Option` classes, so we’ll start with it. + +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: + +```scala +def makeInt(s: String): Int = + try + Integer.parseInt(s.trim) + catch + case e: Exception => 0 +``` + +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. +For instance, the method might have received `"0"`, but it may have also received `"foo"`, `"bar"`, or an infinite number of other strings that will throw an exception. +This is a real problem: How do you know when the method really received a `"0"`, or when it received something else? +The answer is that with this approach, there’s no way to know. + + + +## Using Option/Some/None + +A common solution to this problem in Scala is to use a trio of classes known as `Option`, `Some`, and `None`. +The `Some` and `None` classes are subclasses of `Option`, so the solution works like this: + +- You declare that `makeInt` returns an `Option` type +- If `makeInt` receives a string it *can* convert to an `Int`, the answer is wrapped inside a `Some` +- If `makeInt` receives a string it *can’t* convert, it returns a `None` + +Here’s the revised version of `makeInt`: + +```scala +def makeInt(s: String): Option[Int] = + try + Some(Integer.parseInt(s.trim)) + catch + case e: Exception => None +``` + +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: + +```scala +val a = makeInt("1") // Some(1) +val b = makeInt("one") // None +``` + +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. +As shown, this technique is used so methods can return *values* instead of *exceptions*. +In other situations, `Option` values are also used to replace `null` values. + +Two notes: + +- You’ll find this approach used throughout Scala library classes, and in third-party Scala libraries. +- A key point of this example is that functional methods don’t throw exceptions; instead they return values like `Option`. + + + +## Being a consumer of makeInt + +Now imagine that you’re a consumer of the `makeInt` method. +You know that it returns a subclass of `Option[Int]`, so the question becomes, how do you work with these return types? + +There are two common answers, depending on your needs: + +- Use a `match` expression +- Use a `for` expression + +> There are other approaches that can be used. +> See the [Reference documentation][reference] for details on those approaches. + + + +## Using a `match` expression + +One possible solution is to use a `match` expression: + +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn’t work.") +``` + +In this example, if `x` can be converted to an `Int`, the first `case` statement is executed; if `x` can’t be converted to an `Int`, the second `case` statement is executed. + + + +## Using a `for` expression + +Another common solution is to use a `for` expression — i.e., the `for`/`yield` combination that was shown earlier in this book. +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`: + +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` + +After that expression runs, `y` will be one of two things: + +- If *all* three strings convert to `Int` values, `y` will be a `Some[Int]`, i.e., an integer wrapped inside a `Some` +- If *any* of the three strings can’t be converted to an `Int`, `y` will be a `None` + +You can test this for yourself: + +```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 +``` + +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`: + +```scala +y: Option[Int] = None +``` + + +## Thinking of Option as a container + +Mental models can often help us understand new situations, so if you’re not familiar with the `Option` classes, one way to think about them is as a *container*: + +- `Some` is a container with one item in it +- `None` is a container, but it has nothing in it + +If you prefer to think of the `Option` classes as being like a box, `None` is like an empty box. +It could have had something in it, but it doesn’t. + + +{% 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? + +```scala +makeInt("1").foreach(println) +makeInt("x").foreach(println) +``` + +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: + +```scala +makeInt(aString).foreach(println) +``` + +And the `for` expression looks like this: + +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` + +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: + +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn't work.") +``` + +> There are several other ways to handle `Option` values. +> See the reference documentation for more details. +{% endcomment %} + + + +## Using `Option` to replace `null` + +Getting back to `null` values, a place where a `null` value can silently creep into your code is with a class like this: + +```scala +class Address: + var street1: String, + var street2: String, + var city: String, + var state: String, + var zip: String +``` + +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: + +```scala +val santa = new Address( + "1 Main Street", + null, // <-- D’oh! A null value! + "North Pole", + "Alaska", + "99705" +) +``` + +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: + +```scala +class Address: + var street1: String, + var street2: Option[String], // an optional value + var city: String, + var state: String, + var zip: String +``` + +Now developers can write more accurate code like this: + +```scala +val santa = new Address( + "1 Main Street", + None, // 'street2' has no value + "North Pole", + "Alaska", + "99705" +) +``` + +or this: + +```scala +val santa = new Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` + + + +## `Option` isn’t the only solution + +While this section focuses on the `Option` classes, Scala has a few other alternatives. + +For example, a trio of classes known as `Try`/`Success`/`Failure` work in the same manner, but (a) you primarily use these classes when your code can throw exceptions, and (b) you want to use the `Failure` class because it gives you access to the exception message. +For example, these `Try` classes are commonly used when writing methods that interact with files, databases, and internet services, as those functions can easily throw exceptions. + + + +## A quick review + +This section was long, so let’s give it a quick review: + +- Functional programmers don’t use `null` values +- A main replacement for `null` values is to use the `Option` classes +- Functional methods don’t throw exceptions; instead they return values like `Option`, `Try`, or `Either` +- Common ways to work with `Option` values are `match` and `for` expressions +- Options can be thought of as containers of one item (`Some`) and no items (`None`) +- Options can also be used for optional constructor or method parameters + + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/fp-functions-are-values.md b/_overviews/scala3-book/fp-functions-are-values.md new file mode 100644 index 0000000000..b15313a455 --- /dev/null +++ b/_overviews/scala3-book/fp-functions-are-values.md @@ -0,0 +1,102 @@ +--- +title: Functions Are Values +type: section +description: This section looks at the use of functions as values in functional programming. +num: 44 +previous-page: fp-pure-functions +next-page: fp-functional-error-handling +--- + + +While every programming language ever created probably lets you write pure functions, a second important Scala FP feature is that *you can create functions as values*, just like you create `String` and `Int` values. + +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: + +```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) +``` + +In those examples, anonymous functions are passed into `map` and `filter`. + +> Anonymous functions are also known as *lambdas*. + +In addition to passing anonymous functions into `filter` and `map`, you can also supply them with *methods*: + +```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) +``` + +This ability to treat methods and functions as values is a powerful feature that functional programming languages provide. + +> Technically, a a function that takes another function as an input parameter is known as a *Higher-Order Function*. +> (If you like humor, as someone once wrote, that’s like saying that a class that takes an instance of another class as a constructor parameter is a Higher-Order Class.) + + + +## Functions, anonymous functions, and methods + +As you saw in those examples, this is an anonymous function: + +```scala +_ * 2 +``` + +As shown in the [higher-order functions][hofs] discussion, that’s a shorthand version of this syntax: + +```scala +(i: Int) => i * 2 +``` + +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: + +```scala +val double = (i: Int) => i * 2 +``` + +Now you have a named function, one that’s assigned to a variable. +You can use this function just like you use a method: + +```scala +double(2) // 4 +``` + +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]. + +This ability to seamlessly pass functions around as variables is a distinguishing feature of functional programming languages like Scala. +And as you’ve seen in the `map` and `filter` examples throughout this book, the ability to pass functions into other functions helps you create code that is concise and still readable — *expressive*. + +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: + +```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) +``` + + +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_overviews/scala3-book/fp-immutable-values.md b/_overviews/scala3-book/fp-immutable-values.md new file mode 100644 index 0000000000..02ba4cd794 --- /dev/null +++ b/_overviews/scala3-book/fp-immutable-values.md @@ -0,0 +1,75 @@ +--- +title: Immutable Values +type: section +description: This section looks at the use of immutable values in functional programming. +num: 42 +previous-page: fp-what-is-fp +next-page: fp-pure-functions +--- + + +In pure functional programming, only immutable values are used. +In Scala this means: + +- All variables are created as `val` fields +- Only immutable collections classes are used, such as `List`, `Vector`, and the immutable `Map` and `Set` classes + +Using only immutable variables raises an interesting question: If everything is immutable, how does anything ever change? + +When it comes to using collections, one answer is that you don’t mutate an existing collection; instead, you apply a function to an existing collection to create a new collection. +This is where higher-order functions like `map` and `filter` come in. + +{% comment %} +TODO: need a better example +{% endcomment %} + +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: + +```scala +val a = List("jane", "jon", "mary", "joe") +val b = a.filter(_.startsWith("j")) + .map(_.capitalize) +``` + +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`. + +Similarly, in FP you don’t create classes with mutable `var` constructor parameters. +That is, you don’t write this: + +```scala +// don’t do this in FP +class Person(var firstName: String, var lastName: String) + --- --- +``` + +Instead, you typically create `case` classes, whose constructor parameters are `val` by default: + +```scala +case class Person(firstName: String, lastName: String) +``` + +Now you create a `Person` instance as a `val` field: + +```scala +val reginald = Person("Reginald", "Dwight") +``` + +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: + +```scala +val elton = p.copy( + firstName = "Elton", // update the first name + lastName = "John" // update the last name +) +``` + +There are other techniques for working with immutable collections and variables, but hopefully these examples give you a taste of the techniques. + +> Depending on your needs, you may create enums, traits, or classes instead of `case` classes. +> See the [Data Modeling][modeling] chapter for more details. + + + +[modeling]: {% link _overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_overviews/scala3-book/fp-intro.md b/_overviews/scala3-book/fp-intro.md new file mode 100644 index 0000000000..2f3e26c1cf --- /dev/null +++ b/_overviews/scala3-book/fp-intro.md @@ -0,0 +1,25 @@ +--- +title: Functional Programming +type: chapter +description: This chapter provides an introduction to functional programming in Scala 3. +num: 40 +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: + +- Functions for the logic +- Objects for the modularity + +This chapter assumes that you’re comfortable with OOP and less comfortable with FP, so it provides a gentle introduction to several main functional programming concepts: + +- What is functional programming? +- Immutable values +- Pure functions +- Functions are values +- Functional error handling + + diff --git a/_overviews/scala3-book/fp-pure-functions.md b/_overviews/scala3-book/fp-pure-functions.md new file mode 100644 index 0000000000..a6da5a630e --- /dev/null +++ b/_overviews/scala3-book/fp-pure-functions.md @@ -0,0 +1,122 @@ +--- +title: Pure Functions +type: section +description: This section looks at the use of pure functions in functional programming. +num: 43 +previous-page: fp-immutable-values +next-page: fp-functions-are-values +--- + + +{% comment %} +TODO: Use someone else’s definition? +{% endcomment %} + +Another feature that Scala offers to help you write functional code is the ability to write pure functions. +A _pure function_ can be defined like this: + +- 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 + +As a result of this definition, any time you call a pure function with the same input value(s), you’ll always get the same result. +For example, you can call a `double` function an infinite number of times with the input value `2`, and you’ll always get the result `4`. + + + +## Examples of pure functions + +Given that definition, as you can imagine, methods like these in the *scala.math._* package are pure functions: + +- `abs` +- `ceil` +- `max` + +These `String` methods are also pure functions: + +- `isEmpty` +- `length` +- `substring` + +Most methods on the Scala collections classes also work as pure functions, including `drop`, `filter`, `map`, and many more. + +> In Scala, *functions* and *methods* are almost completely interchangeable, so even though we use the common industry term “pure function,” this term can be used to describe both functions and methods. +> If you’re interested in how methods can be used like functions, see the [Eta Expansion][eta] discussion. + + + +## Examples of impure functions + +Conversely, the following functions are *impure* because they violate the definition. + +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 doesn’t return anything, 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 inputs,* in these examples. + +Additionally, methods that interact with the console, files, databases, web services, sensors, etc., are all impure. + +In general, impure functions do one or more of these things: + +- Read hidden inputs, i.e., they access variables and data not explicitly passed into the function as input parameters +- Write hidden outputs +- Mutate the parameters they’re given, or mutate hidden variables, such as fields in their containing class +- Perform some sort of I/O with the outside world + + + +## But impure functions are needed ... + +Of course an application isn’t very useful if it can’t read or write to the outside world, so people make this recommendation: + +> Write the core of your application using pure functions, and then write an impure “wrapper” around that core to interact with the outside world. +> As someone once said, this is like putting a layer of impure icing on top of a pure cake. + +It’s important to note that there are ways to make impure interactions with the outside world feel more pure. +For instance, you’ll hear about using an `IO` Monad to deal with input and output. +These topics are beyond the scope of this document, so to keep things simple it can help to think that FP applications have a core of pure functions that are wrapped with other functions to interact with the outside world. + + + +## Writing pure functions + +**Note**: In this section the common industry term “pure function” is often used to refer to Scala methods. + +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: + +```scala +def double(i: Int): Int = i * 2 +``` + +If you’re comfortable with recursion, here’s a pure function that calculates the sum of a list of integers: + +```scala +def sum(xs: List[Int]): Int = xs match + case Nil => 0 + case head :: tail => head + sum(tail) +``` + +If you understand that code, you’ll see that it meets the pure function definition. + + + +## Key points + +The first key point of this section 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 doesn’t modify any values in the outside world. + +A second key point is that every real-world application interacts with the outside world. +Therefore, a simplified way to think about functional programs is that they consist of a core of pure functions that are wrapped with other functions that interact with the outside world. + + + +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_overviews/scala3-book/fp-summary.md b/_overviews/scala3-book/fp-summary.md new file mode 100644 index 0000000000..9857501e84 --- /dev/null +++ b/_overviews/scala3-book/fp-summary.md @@ -0,0 +1,26 @@ +--- +title: Summary +type: section +description: This section summarizes the previous functional programming sections. +num: 46 +previous-page: fp-functional-error-handling +next-page: types-introduction +--- + + +This chapter provides a high-level introduction to functional programming in Scala. +The topics covered are: + +- What is functional programming? +- Immutable values +- Pure functions +- Functions are values +- Functional error handling + +As mentioned, functional programming is a big topic, so all we can do in this book is to touch on these introductory concepts. +See the [Reference documentation][reference] for more details. + + + +[reference]: {{ site.scala3ref }}/overview.html + diff --git a/_overviews/scala3-book/fp-what-is-fp.md b/_overviews/scala3-book/fp-what-is-fp.md new file mode 100644 index 0000000000..3f8529d681 --- /dev/null +++ b/_overviews/scala3-book/fp-what-is-fp.md @@ -0,0 +1,35 @@ +--- +title: What is Functional Programming? +type: section +description: This section provides an answer to the question, what is functional programming? +num: 41 +previous-page: fp-intro +next-page: fp-immutable-values +--- + + + +[Wikipedia defines *functional programming*](https://en.wikipedia.org/wiki/Functional_programming) like this: + + +{% comment %} +TODO: Update the CSS so this extra paragraph isn’t needed. +{% endcomment %} + +<blockquote> +<p>Functional programming is a programming paradigm where programs are constructed by applying and composing functions. +It is a declarative programming paradigm in which function definitions are trees of expressions that each return a value, rather than a sequence of imperative statements which change the state of the program.</p> +<p> </p> +<p>In functional programming, functions are treated as first-class citizens, meaning that they can be bound to names (including local identifiers), passed as arguments, and returned from other functions, just as any other data type can. +This allows programs to be written in a declarative and composable style, where small functions are combined in a modular manner.</p> +</blockquote> + +It can also be helpful to know that experienced functional programmers have a strong desire to see their code as math, that combining pure functions together is like combining a series of algebraic equations. + +When you write functional code you feel like a mathematician, and once you understand the paradigm, you want to write pure functions that always return *values* — not exceptions or null values — so you can combine (compose) them together to create solutions. +The feeling that you’re writing math-like equations (expressions) is the driving desire that leads you 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 one chapter, but hopefully the following sections will provide an overview of the main topics, and show some of the tools Scala provides for writing functional code. + + + diff --git a/_overviews/scala3-book/fun-anonymous-functions.md b/_overviews/scala3-book/fun-anonymous-functions.md new file mode 100644 index 0000000000..0809519393 --- /dev/null +++ b/_overviews/scala3-book/fun-anonymous-functions.md @@ -0,0 +1,140 @@ +--- +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 +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: + +```scala +val ints = List(1, 2, 3) +``` + +You can create a new list by doubling each element in `ints`, using the `List` class `map` method and your custom anonymous function: + +```scala +val doubledInts = ints.map(_ * 2) // List(2, 4, 6) +``` + +As the comment shows, `doubledInts` contains the list, `List(2, 4, 6)`. +In that example, this portion of the code is an anonymous function: + +```scala +_ * 2 +``` + +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: + +```scala +val doubledInts = ints.map(_ * 2) +``` + +you can also write it using these forms: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +val doubledInts = ints.map((i) => i * 2) +val doubledInts = ints.map(i => i * 2) +``` + +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: + +```java +List<Integer> ints = List.of(1, 2, 3); +List<Integer> doubledInts = ints.stream() + .map(i -> i * 2) + .collect(Collectors.toList()); +``` + + + +## Shortening anonymous functions + +When you want to be explicit, you can write an anonymous function using this long form: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +The anonymous function in that expression is this: + +```scala +(i: Int) => i * 2 +``` + +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: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +Because the Scala compiler can infer from the data in `ints` that `i` is an `Int`, the `Int` declaration can be removed: + +```scala +val doubledInts = ints.map((i) => i * 2) +``` + +Because there’s only one argument, the parentheses around the parameter `i` aren’t needed: + +```scala +val doubledInts = ints.map(i => i * 2) +``` + +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: + +```scala +val doubledInts = ints.map(_ * 2) +``` + +### 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: + +```scala +ints.foreach((i:Int) => println(i)) +``` + +As before, the `Int` declaration isn’t required, and because there’s only one argument, the parentheses around `i` aren’t needed: + +```scala +ints.foreach(i => println(i)) +``` + +Because `i` is used only once in the body of the function, the expression can be further simplified with the `_` symbol: + +```scala +ints.foreach(println(_)) +``` + +Finally, if an anonymous function consists of one statement that takes a single argument, you don’t need to explicitly name and specify the argument, so the statement can finally be reduced to this: + +```scala +ints.foreach(println) +``` + + diff --git a/_overviews/scala3-book/fun-eta-expansion.md b/_overviews/scala3-book/fun-eta-expansion.md new file mode 100644 index 0000000000..a632a46086 --- /dev/null +++ b/_overviews/scala3-book/fun-eta-expansion.md @@ -0,0 +1,91 @@ +--- +title: Eta Expansion +type: section +description: This page discusses Eta Expansion, the Scala technology that automatically and transparently converts methods into functions. +num: 30 +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*: + +```scala +def map[B](f: (A) => B): List[B] + ----------- +``` + +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: + +```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*? + +The technology 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 + +{% 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 %} + + +{% comment %} +TODO: link to Toplevel definitions +{% endcomment %} + +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 and [extension methods][extension]. + +Unlike methods, *functions* are complete objects themselves, making them first-class entities. + +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: + +```scala +def isEvenMethod(i: Int) = i % 2 == 0 // a method +val isEvenFunction = (i: Int) => i % 2 == 0 // a function +``` + +The function truly is an object, so you can use it just like any other variable, such as putting it in a list: + +```scala +val functions = List(isEvenFunction) +``` + +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: + +```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`. +``` + +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: + +```scala +val functions = List(isEvenFunction) // works +val methods = List(isEvenMethod) // works +``` + +For the purpose of this introductory book, the important things to know are: + +- 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 + +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 new file mode 100644 index 0000000000..c5a10508e7 --- /dev/null +++ b/_overviews/scala3-book/fun-function-variables.md @@ -0,0 +1,118 @@ +--- +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 +previous-page: fun-anonymous-functions +next-page: fun-eta-expansion +--- + + + +Going back to this example from the previous section: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +We noted that this part of the expression is an anonymous function: + +```scala +(i: Int) => i * 2 +``` + +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*: + +```scala +val double = (i: Int) => i * 2 +``` + +This creates a function variable named `double`. +In this expression, the original function literal is on the right side of the `=` symbol: + +```scala +val double = (i: Int) => i * 2 + ----------------- +``` + +the new variable name is on the left side: + +```scala +val double = (i: Int) => i * 2 + ------ +``` + +and the function’s parameter list is underlined here: + +```scala +val double = (i: Int) => i * 2 + -------- +``` + +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`: + +```scala +scala> val double = (i: Int) => i * 2 +val double: Int => Int = ... +``` + + +### Invoking the function + +Now you can call the `double` function like this: + +```scala +val x = double(2) // 4 +``` + +You can also pass `double` into a `map` call: + +```scala +List(1, 2, 3).map(double) // List(2, 4, 6) +``` + +Furthermore, when you have other functions of the `Int => Int` type: + +```scala +val triple = (i: Int) => i * 3 +``` + +you can store them in a `List` or `Map`: + +```scala +val functionList = List(double, triple) + +val functionMap = Map( + "2x" -> double, + "3x" -> triple +) +``` + +If you paste those expressions into the REPL, you’ll see that they have these types: + +```` +// 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] +```` + + + +## Key points + +The important parts here are: + +- To create a function variable, just assign a variable name to a function literal +- Once you have a function, you can treat it like any other variable, i.e., like a `String` or `Int` variable + +And thanks to the improved [Eta Expansion][eta_expansion] functionality in Scala 3, you can treat *methods* in the same way. + + + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_overviews/scala3-book/fun-hofs.md b/_overviews/scala3-book/fun-hofs.md new file mode 100644 index 0000000000..7a9c472ba0 --- /dev/null +++ b/_overviews/scala3-book/fun-hofs.md @@ -0,0 +1,305 @@ +--- +title: Higher-Order Functions +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +num: 31 +previous-page: fun-eta-expansion +next-page: fun-write-map-function +--- + + +A higher-order function (HOF) is often defined as a function that (a) takes other functions as input parameters or (b) returns a function as a result. +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`. +In the next few sections you’ll see how to be a *creator* of HOFs, including: + +- How to write methods that take functions as input parameters +- How to return a function from a method + +In the process you’ll see: + +- The syntax you use to define function input parameters +- How to call a function once you have a reference to it + +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` class: + +```scala +def filter(p: (A) => Boolean): List[A] +``` + +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. +So `filter` takes a predicate `p` as an input parameter, and returns a `List[A]`, where `A` is the type held in the list; if you call `filter` on a `List[Int]`, `A` is the type `Int`. + +At this point, if you don’t know the purpose of the `filter` method, all you’d know is that its algorithm somehow uses the predicate `p` to create and return the `List[A]`. + +Looking specifically at the function parameter `p`, this part of `filter`’s description: + +```scala +p: (A) => Boolean +``` + +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: + +```scala +p: (Int) => Boolean +``` + +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 %} + +## Writing methods that take function parameters + +Given that background, let’s start writing methods that take functions as input parameters. + +**Note:** To make the following discussion clear, we’ll refer to the code you’re writing as a *method*, and the code you’re accepting as an input parameter as a *function*. + + +### A first example + +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: + +```scala +def sayHello(f: () => Unit): Unit = f() +``` + +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: + +```scala +f: () => Unit +``` + +Here’s how this works: + +- `f` is the name of the function input parameter. + It’s just like naming a `String` parameter `s` or an `Int` parameter `i`. +- The type signature of `f` specifies the *type* of the functions this method will accept. +- The `()` portion of `f`’s signature (on the left side of the `=>` symbol) states that `f` takes no input parameters. +- The `Unit` portion of the signature (on the right side of the `=>` symbol) indicates that `f` should return nothing. +- Looking back at the body of the `sayHello` method (on the right side of the `=` symbol), the `f()` statement there invokes the function that’s passed in. + +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: + +```scala +def helloJoe(): Unit = println("Hello, Joe") +``` + +Because the type signatures match, you can pass `helloJoe` into `sayHello`: + +```scala +sayHello(helloJoe) // prints "Hello, Joe" +``` + +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`: + +```scala +def bonjourJulien(): Unit = println("Bonjour, Julien") +``` + +Here it is in the REPL: + +```` +scala> sayHello(bonjourJulien) +Bonjour, Julien +```` + +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: + +```scala +def sayHello(f: () => Unit) +``` + +We noted that the type signature for `f` is: + +```scala +() => Unit +``` + +We know that this means, “a function that takes no input parameters and returns nothing (given by `Unit`).” + +To demonstrate more type signature examples, here’s a function that takes a `String` parameter and returns an `Int`: + +```scala +f: (String) => Int +``` + +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`: + +```scala +f: (Int, Int) => Int +``` + +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: + +```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 +``` + +As you can infer from these examples, the general syntax for defining function parameter type signatures is: + +```scala +variableName: (parameterTypes ...) => returnType +``` + +> 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. +For a class like `List`, its `map` method already has data to work on: the data in the `List`. +But for a standalone HOF that doesn’t have its own data, it should also accept data as other input parameters. + +For instance, here’s a method named `executeNTimes` that has two input parameters: a function, and an `Int`: + +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for i <- 1 to n do f() +``` + +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: + +```scala +// a method of type `() => Unit` +def helloWorld(): Unit = println("Hello, world") +``` + +Then pass that method into `executeNTimes` along with an `Int`: + +```` +scala> executeNTimes(helloWorld, 3) +Hello, world +Hello, world +Hello, world +```` + +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: + +```scala +def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = + println(f(i, j)) +``` + +Because these `sum` and `multiply` methods match that type signature, they can be passed into `executeAndPrint` along with two `Int` values: + +```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 +``` + + + +## Function type signature consistency + +A great thing about learning about Scala’s function type signatures is that the syntax you use to define function input parameters is the same syntax you use to write anonymous functions and function variables. + +For instance, if you were to write an anonymous function that calculates the sum of two integers, you’d write it like this: + +```scala +(Int, Int) => Int = (a, b) => a + b +``` + +That code consists of the type signature: + +```` +(Int, Int) => Int = (a, b) => a + b +----------------- +```` + +The input parameters: + +```` +(Int, Int) => Int = (a, b) => a + b + ------ +```` + +and the body of the function: + +```` +(Int, Int) => Int = (a, b) => a + b + ----- +```` + +Scala’s consistency is shown here, where this anonymous function type signature: + +```` +(Int, Int) => Int = (a, b) => a + b +----------------- +```` + +is the same as the type signature you use to define a function input parameter: + +```` +def executeAndPrint(f: (Int, Int) => Int, ... + ----------------- +```` + +Once you’re comfortable with this syntax, you’ll use it to define function parameters, anonymous functions, and function variables, and it becomes easier to read the Scaladoc for higher-order functions. + + + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_overviews/scala3-book/fun-intro.md b/_overviews/scala3-book/fun-intro.md new file mode 100644 index 0000000000..ba22b69d7a --- /dev/null +++ b/_overviews/scala3-book/fun-intro.md @@ -0,0 +1,13 @@ +--- +title: Functions +type: chapter +description: This chapter looks at all topics related to functions in Scala 3. +num: 27 +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. + diff --git a/_overviews/scala3-book/fun-summary.md b/_overviews/scala3-book/fun-summary.md new file mode 100644 index 0000000000..e2e9488629 --- /dev/null +++ b/_overviews/scala3-book/fun-summary.md @@ -0,0 +1,35 @@ +--- +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 +previous-page: fun-write-method-returns-function +next-page: packaging-imports +--- + +This was a long chapter, so let’s review the key points that are covered. + +A higher-order function (HOF) is often defined as a function that takes other functions as input parameters or returns a function as its value. +In Scala this is possible because functions are first-class values. + +Moving through the sections, first you saw: + +- You can write anonymous functions as small code fragments +- You can pass them into the dozens of HOFs (methods) on the collections classes, i.e., methods like `filter`, `map`, etc. +- With these small code fragments and powerful HOFs, you create a lot of functionality with just a little code + +After looking at anonymous functions and HOFs, you saw: + +- Function variables are simply anonymous functions that have been bound to a variable + +After seeing how to be a *consumer* of HOFs, you then saw how to be a *creator* of HOFs. +Specifically, you saw: + +- How to write methods that take functions as input parameters +- How to return a function from a method + +A beneficial side effect of this chapter is that you saw many examples of how to declare type signatures for functions. +The benefits of that are that you use that same syntax to define function parameters, anonymous functions, and function variables, and it also becomes easier to read the Scaladoc for higher-order functions like `map`, `filter`, and others. + + + diff --git a/_overviews/scala3-book/fun-write-map-function.md b/_overviews/scala3-book/fun-write-map-function.md new file mode 100644 index 0000000000..bd00627941 --- /dev/null +++ b/_overviews/scala3-book/fun-write-map-function.md @@ -0,0 +1,87 @@ +--- +title: Write Your Own map Method +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +num: 32 +previous-page: fun-hofs +next-page: fun-write-method-returns-function +--- + + +Now that you’ve seen how to write your own higher-order functions, let’s take a quick look at a more real-world example. + +Imagine for a moment that the `List` class doesn’t have its own `map` method, and you want to write your own. +A good first step when creating functions is to accurately state the problem. +Focusing only on a `List[Int]`, you state: + +> I want to write a `map` method that can be used to 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: + +```scala +def map(f: (Int) => A +``` + +The syntax for using a generic type requires declaring that type symbol before the parameter list, so you add that: + +```scala +def map[A](f: (Int) => A +``` + +Next, you know that `map` should also accept a `List[Int]`: + +```scala +def map[A](f: (Int) => A, xs: List[Int]) +``` + +Finally, you also know that `map` returns a transformed `List` that contains elements of the generic type `A`: + +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = ??? +``` + +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: + +```scala +for x <- xs yield f(x) +``` + +`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]`: + +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for x <- xs yield f(x) +``` + + +### 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`: + +```scala +def map[A,B](f: (B) => A, xs: List[B]): List[A] = + for x <- xs yield f(x) +``` + +Now you have a `map` method that works with any `List`. + +These examples demonstrate that `map` works as desired: + +```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) +``` + +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 new file mode 100644 index 0000000000..61d51de8e9 --- /dev/null +++ b/_overviews/scala3-book/fun-write-method-returns-function.md @@ -0,0 +1,168 @@ +--- +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 +previous-page: fun-write-map-function +next-page: fun-summary +--- + + +Thanks to Scala’s consistency, writing a method that returns a function is similar to everything you’ve seen in the previous sections. +For example, imagine that you want to write a `greet` method that returns a function. +Once again we start with a problem statement: + +> I want to create a `greet` method that returns a function. +> That function will take a string parameter and print it using `println`. +> To simplify this first example, `greet` won’t take any input parameters; it will just build a function and return it. + +Given that statement, you can start building `greet`. +You know it’s going to be a method: + +```scala +def greet() +``` + +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`: + +```scala +def greet(): String => Unit = ??? + ---------------- +``` + +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: + +```scala +(name: String) => println(s"Hello, $name") +``` + +Now you just return that function from the method: + +```scala +// a method that returns a function +def greet(): String => Unit = + (name: String) => println(s"Hello, $name") +``` + +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: + +```` +scala> val greetFunction = greet() +val greetFunction: String => Unit = Lambda.... + ----------------------------- +```` + +Now you can call `greetFunction`: + +```scala +greetFunction("Joe") // prints "Hello, Joe" +``` + +Congratulations, you just created a method that returns a function, and then executed that function. + + + +## Improving the method + +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`: + +```scala +def greet(theGreeting: String): String => Unit = + (name: String) => println(s"$theGreeting, $name") +``` + +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: + +```` +scala> val sayHello = greet("Hello") +val sayHello: String => Unit = Lambda..... + ------------------------ +```` + +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: + +```scala +sayHello("Joe") // prints "Hello, Joe" +``` + +You can also change the greeting to create new functions, as desired: + +```scala +val sayCiao = greet("Ciao") +val sayHola = greet("Hola") + +sayCiao("Isabella") // prints "Ciao, Isabella" +sayHola("Carlos") // prints "Hola, Carlos" +``` + + + +## A more real-world example + +This technique can be even more useful when your method returns one of many possible functions, like a factory that returns custom-built functions. + +For instance, imagine that you want to write a method that returns functions that greet people in different languages. +We’ll limit this to functions that greet in English or French, depending on a parameter that’s passed into the method. + +A first thing you know is that you want to create a method that (a) takes a “desired language” as an input, and (b) returns a function as its result. +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: + +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = ??? +``` + +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: + +```scala +(name: String) => println(s"Hello, $name") +(name: String) => println(s"Bonjour, $name") +``` + +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: + +```scala +val englishGreeting = (name: String) => println(s"Hello, $name") +val frenchGreeting = (name: String) => println(s"Bonjour, $name") +``` + +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: + +```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 +``` + +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: + +```scala +val greetInFrench = createGreetingFunction("french") +greetInFrench("Jonathan") // prints "Bonjour, Jonathan" +``` + +And this is how it builds an English-greeting function: + +```scala +val greetInEnglish = createGreetingFunction("english") +greetInEnglish("Joe") // prints "Hello, Joe" +``` + +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 new file mode 100644 index 0000000000..dd852c762e --- /dev/null +++ b/_overviews/scala3-book/interacting-with-java.md @@ -0,0 +1,359 @@ +--- +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: 70 +previous-page: scala-tools +next-page: scala-for-java-devs +--- + + +## Introduction + +This section looks at how to use Java code in Scala, and the opposite, how to use Scala code in Java. + +In general, using Java code in Scala is pretty seamless. +There are only a few points where you’ll want to use Scala utilities to convert Java concepts to Scala, including: + +- Java collections classes +- The Java `Optional` class + +Similarly, if you’re writing Java code and want to use Scala concepts, you’ll want to convert Scala collections and the Scala `Option` class. + +These following sections demonstrate the most common conversions you’ll need: + +- How to use Java collections in Scala +- How to use Java `Optional` in Scala +- Extending Java interfaces in Scala +- How to use Scala collections in Java +- How to use Scala `Option` in Java +- How to use Scala traits in Java +- How to handle Scala methods that throw exceptions in Java code +- How to use Scala varargs parameters in Java +- Create alternate names to use Scala methods in Java + +Note that the Java examples in this section assume that you’re using Java 11 or newer. + + + +## 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. + +Here’s an example of how this works. +Given this Java `ArrayList`: + +```java +// java +public class JavaClass { + public static List<String> getStrings() { + return new ArrayList<String>(List.of("a", "b", "c")); + } +} +``` + +You can convert that Java list to a Scala `Seq`, using the conversion utilities in the Scala _scala.jdk.CollectionConverters_ package: + +```scala +// scala +import scala.jdk.CollectionConverters._ +import java.util.List + +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) +``` + +Of course that code can be shortened, but the individual steps are shown here to demonstrate exactly how the conversion process works. + + + +## 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`. + +To demonstrate this, here’s a Java class with two `Optional<String>` values, one containing a string and the other one empty: + +```java +// java +import java.util.Optional; + +public class JavaClass { + static Optional<String> oString = Optional.of("foo"); + static Optional<String> oEmptyString = Optional.empty(); +} +``` + +Now in your Scala code you can access those fields. +If you just access them directly, they’ll both be `Optional` values: + +```scala +// scala +import java.util.Optional + +val optionalString = JavaClass.oString // Optional[foo] +val eOptionalString = JavaClass.oEmptyString // Optional.empty +``` + +But by using the _scala.jdk.OptionConverters_ methods, you can convert them to Scala `Option` values: + +```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 +``` + + + +## 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: + +```java +// java +interface Animal { + void speak(); +} + +interface Wagging { + void wag(); +} + +interface Running { + // an implemented method + default void run() { + System.out.println("I’m running"); + } +} +``` + +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: + +```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 +``` + + + +## 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: + +```scala +// scala +class ScalaClass: + val strings = List("a", "b") +``` + +You can access that Scala `List` in your Java code like this: + +```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<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); +``` + +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()` + + + +## 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: + +```scala +// scala +object ScalaObject: + val someString = Option("foo") + val noneString: Option[String] = None +``` + +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: + +```java +// java +import java.util.Optional; +import static scala.jdk.javaapi.OptionConverters.toJava; + +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); + } +} +``` + +The two Scala `Option` fields are now available as Java `Optional` values. + + + +## 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. +For example, given these two Scala traits, one with an implemented method and one with only an interface: + +```scala +// scala +trait ScalaAddTrait: + def sum(x: Int, y: Int) = x + y // implemented + +trait ScalaMultiplyTrait: + def multiply(x: Int, y: Int): Int // abstract +``` + +A Java class can implement both of those interfaces, and define the `multiply` method: + +```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 +``` + + + +## How to handle Scala methods that throw exceptions in Java code + +When you’re writing Scala code using Scala programming idioms, you’ll never write a method that throws an exception. +But if for some reason you have a Scala method that does throw an exception, and you want Java developers to be able to use that method, add the `@throws` annotation to your Scala method so Java consumers will know the exceptions they can throw. + +For example, this Scala `exceptionThrower` method is annotated to declare that it throws an `Exception`: + +```scala +// scala +object SExceptionThrower: + @throws(classOf[Exception]) + def exceptionThrower = + throw new Exception("Idiomatic Scala methods don’t throw exceptions") +``` + +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: + +```java +// java: won’t compile because the exception isn’t handled +public class ScalaExceptionsInJava { + public static void main(String[] args) { + SExceptionThrower.exceptionThrower(); + } +} +``` + +The compiler gives this error: + +```` +[error] ScalaExceptionsInJava: unreported exception java.lang.Exception; + must be caught or declared to be thrown +[error] SExceptionThrower.exceptionThrower() +```` + +This is good — it’s what you want: the annotation tells the Java compiler that `exceptionThrower` can throw an exception. +Now when you’re writing Java code you must handle the exception with a `try` block or declare that your Java method throws an exception. + +Conversely, if you leave the annotation off of the Scala `exceptionThrower` method, the Java code _will compile_. +This is probably not what you want, because the Java code may not account for the Scala method throwing the exception. + + + +## How to use Scala varargs parameters in Java + +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: + +```scala +// scala +import scala.annotation.varargs + +object VarargsPrinter: + @varargs def printAll(args: String*): Unit = args.foreach(println) +``` + +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 +public class JVarargs { + public static void main(String[] args) { + VarargsPrinter.printAll("Hello", "world"); + } +} +``` + +When this code is run, it results in the following output: + +```` +Hello +world +```` + + + +## Create alternate names to use Scala methods in Java + +In Scala you might want to create a method name using a symbolic character: + +```scala +def +(a: Int, b: Int) = a + b +``` + +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: + +```scala +import scala.annotation.alpha + +class Adder: + @alpha("add") def +(a: Int, b: Int) = a + b +``` + +Now in your Java code you can use the aliased method name `add`: + +```scala +var adder = new Adder(); +int x = adder.add(1,1); +System.out.printf("x = %d\n", x); +``` + + diff --git a/_overviews/scala3-book/introduction.md b/_overviews/scala3-book/introduction.md new file mode 100644 index 0000000000..c2e131e26c --- /dev/null +++ b/_overviews/scala3-book/introduction.md @@ -0,0 +1,30 @@ +--- +title: Introduction +type: chapter +description: This page begins the overview documentation of the Scala 3 language. +num: 1 +previous-page: +next-page: scala-features +--- + +Welcome to the Scala 3 Book. +The goal of this book is to provide an informal introduction to the Scala language. +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. + +Over the course of this book, we hope to demonstrate that Scala is a beautiful, expressive programming language, with a clean, modern syntax, and supports functional programming (FP), object-oriented programming (OOP), and a fusion of FP and OOP in a typed setting. +Scala’s syntax, grammar, and features have been re-thought, debated in an open process, and updated in 2020 to be more clear 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 %} + +> We are still in the process of writing the book. +> You can [help us improve it][contributing] + +[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 new file mode 100644 index 0000000000..a6bf3e2fff --- /dev/null +++ b/_overviews/scala3-book/methods-intro.md @@ -0,0 +1,19 @@ +--- +title: Methods +type: chapter +description: This section introduces methods in Scala 3. +num: 23 +previous-page: domain-modeling-fp +next-page: methods-most +--- + + +In Scala 2, _methods_ can be defined inside classes, traits, objects, case classes, and case objects. +But it gets better: In Scala 3 they can also be defined _outside_ any of those constructs with a new feature named Toplevel definitions. +In short, methods can now be defined anywhere. + +Many features of methods are demonstrated in the next section. +Because `main` methods require a little more explanation, they’re described in the separate section that follows. + + + diff --git a/_overviews/scala3-book/methods-main-methods.md b/_overviews/scala3-book/methods-main-methods.md new file mode 100644 index 0000000000..7570f32486 --- /dev/null +++ b/_overviews/scala3-book/methods-main-methods.md @@ -0,0 +1,139 @@ +--- +title: main Methods +type: section +description: This page describes how 'main' methods and the '@main' annotation work in Scala 3. +num: 25 +previous-page: methods-most +next-page: methods-summary +--- + + +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: + +```scala +@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 +``` + +Then run it with `scala`: + +```bash +$ scala hello +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. + + + +### 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: + +```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 +``` + +When you compile that code, it creates a main program named `happyBirthday` that’s called like this: + +``` +$ scala happyBirthday 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. +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 +Illegal command line after first argument: more arguments expected + +$ scala happyBirthday sixty Fred +Illegal command line: java.lang.NumberFormatException: For input string: "sixty" +``` + + + +## The details + +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: 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. + +For instance, the `happyBirthday` method above generates additional code equivalent to the following class: + +```scala +final class happyBirthday { + import scala.util.{CommandLineParser => CLP} + <static> 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**: In this generated code, the `<static>` modifier expresses that the `main` method is generated as a static method of class `happyBirthday`. +> This feature is not available for user programs in Scala. +> Regular “static” members are generated in Scala using objects instead. + + + +## Scala 3 compared to 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: + +```scala +object happyBirthday: + def main(args: Array[String]) = println("Hello, world") +``` + +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: + +```bash +$ scalac happyBirthday.scala + +$ scala happyBirthday +Hello, world +``` diff --git a/_overviews/scala3-book/methods-most.md b/_overviews/scala3-book/methods-most.md new file mode 100644 index 0000000000..80da9b31ca --- /dev/null +++ b/_overviews/scala3-book/methods-most.md @@ -0,0 +1,403 @@ +--- +title: Method Features +type: section +description: This section introduces Scala 3 methods, including main methods, extension methods, and more. +num: 24 +previous-page: methods-intro +next-page: methods-main-methods +--- + +This section introduces the various aspects of how to define and call methods in Scala 2. + +## Defining Methods + +Scala methods have many features, including these: + +- Generic (type) parameters +- Automatically provided `using` parameters +- Default parameter values +- Multiple parameter groups +- By-name parameters +- ... + +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: + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +end methodName // this is optional +``` + +In that syntax: + +- The keyword `def` is used to define a method +- The Scala standard is to name methods using the camel case convention +- Method parameters are always defined with their type +- Declaring the method return type is optional +- Methods can consist of many lines, or just one line +- Providing the `end methodName` portion after the method body is also optional, and is only recommended for long methods + +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: + +```scala +def add(a: Int, b: Int): Int = a + b +def add(a: Int, b: Int) = a + b +``` + +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: + +```scala +val x = add(1, 2) // 3 +``` + +The Scala collections classes have dozens of built-in methods. +These examples show how to call them: + +```scala +val x = List(1,2,3) + +x.size // 3 +x.contains(1) // true +x.map(_ * 10) // List(10, 20, 30) +``` + +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: + +```scala +def addThenDouble(a: Int, b: Int): Int = + // imagine that this body requires multiple lines + val sum = a + b + sum * 2 +``` + +In that method: + +- `sum` is an immutable local variable; it can’t be accessed outside of the method +- The last line doubles the value of `sum`; this value is returned from the method + +When you paste that code into the REPL, you’ll see that it works as desired: + +```scala +scala> addThenDouble(1, 1) +res0: Int = 4 +``` + +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: + +```scala +def addThenDouble(a: Int, b: Int): Int = (a + b) * 2 +``` + +The body of a method can use all the different features of the language: + +- `if`/`else` expressions +- `match` expressions +- `while` loops +- `for` loops and `for` expressions +- Variable assignments +- Calls to other methods + +As an example of a real-world multiline method, this `getStackTraceAsString` method converts its `Throwable` input parameter into a well-formatted `String`: + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = StringWriter() + t.printStackTrace(PrintWriter(sw)) + sw.toString +``` + +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: + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // more code here ... +``` + +Because the parameters have default values, the method can be called in these ways: + +```scala +makeConnection() // timeout = 5000, protocol = http +makeConnection(2_000) // timeout = 2000, protocol = http +makeConnection(3_000, "https") // timeout = 3000, protocol = https +``` + +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` +- In the second example, `2_000` is supplied for the `timeout` value, so it’s used, along with the default value for the `protocol` +- In the third example, values are provided for both parameters, so they’re both used + +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: + +```scala +makeConnection(timeout=10_000) +makeConnection(protocol="https") +makeConnection(timeout=10_000, protocol="https") +makeConnection(protocol="https", timeout=10_000) +``` + +In some frameworks named parameters are heavily used. +They’re also very useful when multiple method parameters have the same type: + +```scala +engage(true, true, true, false) +``` + +Without help from an IDE that code can be hard to read, but this code is much more clear and obvious: + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` + + + +## A suggestion about methods that take no parameters + +When a method takes no parameters, it’s said to have an _arity_ level of _arity-0_. +Similarly, when a method takes one parameter it’s an _arity-1_ method. +When you create arity-0 methods: + +- If the method has side effects, such as calling `println`, declare the method with empty parentheses +- If the method does not have side effects — such as getting the size of a collection, which is similar to accessing a field on the collection — leave the parentheses off + +For example, this method has a side effect, so it’s declared with empty parentheses: + +```scala +def speak() = println("hi") +``` + +Doing this requires callers of the method to use open parentheses when calling the method: + +```scala +speak // error: "method speak must be called with () argument" +speak() // prints "hi" +``` + +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 has 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`: + +```scala +def isTruthy(a: Any) = + if a == 0 || a == "" + false + else + true +``` + +These examples show how that method works: + +```scala +isTruthy(0) // false +isTruthy("") // false +isTruthy("hi") // true +isTruthy(1.0) // true +``` + + + +## 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 : + +```scala +def isTruthy(a: Any) = a match + case 0 | "" => false + case _ => true +``` + +This method works just like the previous method that used an `if`/`else` expression. + + + +## Controlling visibility in classes + +In classes, objects, and traits, Scala methods are public by default, so the `Dog` instance created here can access the `speak` method: + +```scala +class Dog: + def speak() = println("Woof") + +val d = new Dog +d.speak() // prints "Woof" +``` + +Methods can also be marked as `private`. +This makes them private to the current class, and they can’t be overridden in subclasses: + +```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") +``` + +If you want to make a method private to the current class and also allow subclasses to override it, mark the method as `protected`, as shown with the `speak` method in this example: + +```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 +``` + +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. +The Scala `object` keyword is used to create a singleton class, and an object can also contain methods. +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: + +```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 +``` + + + +## 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: + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +When you want to add methods to this class, you can define them as extension methods, like this: + +```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 +``` + +Now when you have a `Circle` instance named `aCircle`, you can call those methods like this: + +```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. + + + +## Even more + +There’s even more to know about methods, including how to: + +- Call methods on superclasses +- Define and use by-name parameters +- Write a method that takes a function parameter +- Create inline methods +- 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. + + + +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[reference_extension_methods]: {{ site.scala3ref }}/contextual/extension-methods.html +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/methods-summary.md b/_overviews/scala3-book/methods-summary.md new file mode 100644 index 0000000000..9894c6d4c9 --- /dev/null +++ b/_overviews/scala3-book/methods-summary.md @@ -0,0 +1,26 @@ +--- +title: Summary +type: section +description: This section summarizes the previous sections on Scala 3 methods. +num: 26 +previous-page: methods-main-methods +next-page: fun-intro +--- + + +There’s even more to know about methods, including how to: + +- Call methods on superclasses +- Define and use by-name parameters +- Write a method that takes a function parameter +- Create inline methods +- 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. + + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/packaging-imports.md b/_overviews/scala3-book/packaging-imports.md new file mode 100644 index 0000000000..6dc8b64eb0 --- /dev/null +++ b/_overviews/scala3-book/packaging-imports.md @@ -0,0 +1,394 @@ +--- +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 +previous-page: fun-summary +next-page: collections-intro +--- + + +Scala uses *packages* to create namespaces that let you modularize programs and help prevent namespace collisions. +Scala supports the package-naming style used by Java, and also the “curly brace” namespace notation used by languages like C++ and C#. + +The Scala approach to importing members is also similar to Java, and more flexible. +With Scala you can: + +- Import packages, classes, objects, traits, and methods +- Place import statements anywhere +- Hide and rename members when you import them + +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: + +```scala +package com.acme.myapp.model + +class Person ... +``` + +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. + + +### Curly brace packaging style + +The other way to declare packages in Scala is by using the curly brace namespace notation used in languages like C, C++, and C#: + +```scala +package users { + package administrators { + class AdminUser + } + package normalusers { + class NormalUser + } +} +``` + +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. +Import statements fall into two main categories: + +- Importing classes, traits, objects, functions, and methods +- Importing `given` clauses + +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: + +```` +import users._ // import everything from the `users` package +import users.User // import only the `User` class +import users.{User, UserPreferences} // only import the selected members +import users.{UserPreferences => UPrefs} // rename a member as you import it +```` + +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. + +Import statements are also used to import `given` instances into scope. +Those are discussed at the end of this chapter. + +Two notes before moving on: + +- Import clauses are not required for accessing members of the same package. +- When the `_` character is used in Scala import statements, it’s similar to the `*` character in Java. + One reason for this difference is that in Scala, the `*` character can be used for method names. + + +### Importing one or more members + +In Scala you can import one member from a package like this: + +```scala +import java.io.File +``` + +and multiple members like this: + +```scala +import java.io.File +import java.io.IOException +import java.io.FileNotFoundException +``` + +When importing multiple members, you can import them more concisely like this: + +```scala +import java.io.{File, IOException, FileNotFoundException} +``` + +When you want to import everything from the *java.io* package, use this syntax: + +```scala +import java.io._ +``` + + +### 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: + +```scala +import java.util.{List => JavaList} +``` + +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: + +```scala +import java.util.{Date => JDate, HashMap => 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.” + + +### 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: + +```scala +import java.util.{Random => _, _} +``` + +If you try to access the `Random` class it won’t work, but you can access all other members from that package: + +```scala +val r = new Random // won’t compile +new ArrayList // works +``` + +#### Hiding multiple members + +To hide multiple members during the import process, list them before using the final wildcard import: + +```scala +scala> import java.util.{List => _, Map => _, Set => _, _} +import java.util.{List=>_, Map=>_, Set=>_, _} +``` + +Once again those classes are hidden, but you can use all other classes in *java.util*: + +```scala +scala> new ArrayList[String] +val res0: java.util.ArrayList[String] = [] +``` + +Because those Java classes are hidden, you can also use the Scala `List`, `Set`, and `Map` classes without having a naming collision: + +```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) +``` + + +### Use imports anywhere + +In Scala, `import` statements can be anywhere. +They can be used at the top of a source code file: + +```scala +package foo + +import scala.util.Random + +class ClassA: + def printRandom: + val r = new Random // use the imported class + // more code here... +``` + +You can also use `import` statements closer to the point where they are needed, if you prefer: + +```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 +``` + + +### “Static” imports + +When you want to import members in a way similar to the Java “static import” approach — so you can refer to the member names directly, without having to prefix them with their class name — use the following approach. + +Use this syntax to import all static members of the Java `Math` class: + +```scala +import java.lang.Math._ +``` + +Now you can access static `Math` class methods like `sin` and `cos` without having to precede them with the class name: + +```scala +import java.lang.Math._ + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` + + +### Packages imported by default + +Two packages are implicitly imported into the scope of all of your source code files: + +- java.lang._ +- scala._ + +The Scala `Predef` object is 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_`: + +``` +package accounts + +import _root_.users._ +``` + + + +## 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. +The basic form is shown in this example: + +```scala +object A: + class TC + given tc as TC + def f(using TC) = ??? + +object B: + import A._ // import all non-given members + import A.given // import the given instance +``` + +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: + +```scala +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. + +These rules have two main benefits: + +- It’s more clear where givens in scope are coming from. + In particular, it’s not possible to hide imported givens in a long list of other wildcard imports. +- 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: + +```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 example, when you have this `object`: + +```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] +``` + +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: + +```scala +import Instances.{given Ordering[?], given ExecutionContext} +``` + +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`: + +```scala +import Instances.{im, given Ordering[?]} +``` + + +### An example + +As a concrete example, imagine that you have this `MonthConversions` object that contains two `given` definitions: + +```scala +object MonthConversions: + trait MonthConverter[A]: + def convert(a: A): String + + given intMonthConverter as MonthConverter[Int]: + 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 = + s match + case "jan" => "January" + case "feb" => "February" + // more cases here ... +} +``` + +To import those givens into the current scope, use these two `import` statements: + +```scala +import MonthConversions._ +import MonthConversions.{given MonthConverter[?]} +``` + +Now you can create a method that uses those `given` instances: + +```scala +def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = + monthConverter.convert(a) +``` + +Then you can use that method in your application: + +```scala +@main def main = + println(genericMonthConverter(1)) // January + println(genericMonthConverter("jan")) // January +``` + +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 new file mode 100644 index 0000000000..d8cbb88d7a --- /dev/null +++ b/_overviews/scala3-book/scala-features.md @@ -0,0 +1,505 @@ +--- +title: Scala 3 Features +type: chapter +description: This page discusses the main features of the Scala 3 programming language. +num: 2 +previous-page: introduction +next-page: why-scala-3 +--- + + +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: + +- High-level language features +- Lower-level language features +- Scala ecosystem features + + + +{% comment %} +I think of this section as being like an “elevator pitch.” +{% endcomment %} + +## High-level features + +Looking at Scala from the proverbial “30,000 foot view,” you can make the following statements about it: + +- 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 pure functional programming (FP) language +- It’s a pure 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 + +The following sections take a quick look at these features. + + +### A high-level language + +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. + +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: + +```scala +def double(ints: List[Int]): List[Int] = { + val buffer = new ListBuffer[Int]() + for (i <- ints) { + buffer += i * 2 + } + buffer.toList + foo + bar +} + +val newNumbers = double(oldNumbers) +``` + +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 achieve the same effect: + +```scala +val newNumbers = oldNumbers.map(_ * 2) +``` + +As you can see, that code is much more concise, easier to read, and easier to maintain. + + +### Concise syntax + +Scala has a concise, readable syntax. +For instance, variables are created concisely, and their types are clear: + +```scala +val nums = List(1,2,3) +val p = Person("Martin", "Odersky") +``` + +Higher-order functions and lambdas make for concise code that’s readable: + +```scala +nums.map(i => i * 2) // long form +nums.map(_ * 2) // short form + +nums.filter(i => i > 1) +nums.filter(_ > 1) +``` + +And traits, classes, and methods are defined with a clean, light syntax: + +```scala +trait Animal: + def speak(): Unit + +trait HasTail: + def wagTail(): Unit + +class Dog extends Animal, HasTail: + def speak() = println("Woof") + def wagTail() = println("⎞⎜⎛ ⎞⎜⎛") +``` + +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. + + +### A dynamic feel + +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: + +```scala +val s = "Hello" +val p = Person("Al", "Pacino") +val sum = ints.reduceLeft(_ + _) +val y = for i <- nums yield i * 2 +val z = nums.filter(_ > 100) + .filter(_ < 10_000) + .map(_ * 2) +``` + +Because Scala is considered to be a [strong, statically-typed language](https://heather.miller.am/blog/types-in-scala.html), you get all the benefits of static types: + +- Correctness: you catch most errors at compile-time +- Great IDE support + - Code completion + - Catching errors at compile-time means catching mistakes as you type + - Easy and reliable refactoring + - Reliable code completion +- You can refactor your code with confidence +- Method type declarations tell readers what the method does, and help serve as documentation +- Types make your code easier to maintain +- Scalability: types help ensure correctness across arbitrarily large applications and development teams +- Strong types enable Scala features like implicits (TODO: I need help on this wording and description) + +{% comment %} +In that list: +- 'Correctness' and 'Scalability' come from Heather Miller’s page +- the IDE-related quotes in this section come from the Scala.js website: + - catch most errors in the IDE + - Easy and reliable refactoring + - Reliable code completion +{% endcomment %} + +{% comment %} +In this section or the next section: +- TODO: Add a note about the benefits of the DOT calculus +- TODO: Also add a note about TASTy? +{% endcomment %} + + +### Expressive type system + +{% comment %} +- this text comes from the current [ScalaTour](https://docs.scala-lang.org/tour/tour-of-scala.html). +- TODO: all of the URLs will have to be updated + +- i removed these items until we can replace them: +* [Compound types](/tour/compound-types.html) +* [Implicit parameters](/tour/implicit-parameters.html) and [conversions](/tour/implicit-conversions.html) +* [Explicitly typed self references](/tour/self-types.html) +{% endcomment %} + +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: + +- [Type inference](/tour/type-inference.html) +- [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 + +In combination, these features provide a powerful basis for the safe reuse of programming abstractions and for the type-safe extension of software. + + +### A pure FP language + +Scala is a functional programming (FP) language, meaning: + +- Functions are variables, and can be passed around like any other variable +- 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 collections classes in the standard library +- Those collections classes come with dozens of functional methods: they don’t mutate the collection, but instead return an updated copy of the data + + +### A pure OOP language + +Scala is a _pure_ object-oriented programming (OOP) language. +Every variable is an object, and every “operator” is a method. + +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. + +{% comment %} +Add the “types hierarchy” image here? +{% endcomment %} + + +### Supports FP/OOP fusion + +{% comment %} +NOTE: This text in the first line comes from this slide: https://twitter.com/alexelcu/status/996408359514525696 +{% endcomment %} + +The essence of Scala is the fusion of functional programming and object-oriented programming in a typed settings: + +- 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.” + + +### Term inference, made clearer + +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, an implicit parameter directly leads to an inferred argument term that could also be written down explicitly. + +Use cases for this concept include implementing type classes, 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/motivation.html). + + +### Client & server + +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. + + +### Seamless Java interaction + +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. + +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`: + +```scala +import java.io._ +val br = BufferedReader(FileReader(filename)) +// read the file with `br` ... +``` + +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: + +```scala +import scala.jdk.CollectionConverters._ +val scalaList: Seq[Integer] = JavaClass.getJavaList().asScala.toSeq +``` + + +### Wealth of libraries + +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. + + + +{% comment %} +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 %} + +## 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. +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: + +- 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 `@alpha` 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]. + + + +{% comment %} +CHECKLIST OF ALL ADDED, UPDATED, AND REMOVED FEATURES +===================================================== + +New Features +------------ +- trait parameters +- super traits +- creator applications +- export clauses +- opaque type aliases +- open classes +- parameter untupling +- kind polymorphism +- tupled function +- threadUnsafe annotation +- new control syntax +- optional braces (experimental) +- explicit nulls +- safe initialization + +CHANGED FEATURES +---------------- +- numeric literals +- structural types +- operators +- wildcard types +- type checking +- type inference +- implicit resolution +- implicit conversions +- overload resolution +- match expressions +- vararg patterns +- pattern bindings +- pattern matching +- eta expansion +- compiler plugins +- lazy vals initialization +- main functions + +DROPPED FEATURES +---------------- +- DelayedInit +- macros +- existential types +- type projection +- do/while syntax +- procedure syntax +- package objects +- early initializers +- class shadowing +- limit 22 +- XML literals +- symbol literals +- auto-application +- weak conformance +- nonlocal returns +- [this] qualifier + - private[this] and protected[this] access modifiers are deprecated + and will be phased out +{% endcomment %} + + + + +## Scala ecosystem + +{% 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 %} + +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. + + + +### Web development + +- 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 + +HTTP(S) libraries: + +- [Akka-http](https://akka.io) +- [Finch](https://github.com/finagle/finch) +- [Http4s](https://github.com/http4s/http4s) +- [Sttp](https://github.com/softwaremill/sttp) + +JSON libraries: + +- [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) + +Serialization: + +- [ScalaPB](https://github.com/scalapb/ScalaPB) + +Science and data analysis: + +- [Algebird](https://github.com/twitter/algebird) +- [Spire](https://github.com/typelevel/spire) +- [Squants](https://github.com/typelevel/squants) + + +### Big data + +- [Apache Spark](https://github.com/apache/spark) +- [Apache Flink](https://github.com/apache/flink) + + +### 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) + + +### FP & FRP + +FP: + +- [Cats](https://github.com/typelevel/cats) +- [Zio](https://github.com/zio/zio) + +Functional reactive programming (FRP): + +- [fs2](https://github.com/typelevel/fs2) +- [monix](https://github.com/monix/monix) + + +### Build tools + +- [sbt](https://www.scala-sbt.org) +- [Gradle](https://gradle.org) +- [Mill](https://github.com/lihaoyi/mill) + + + +## 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. + + + +[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 %} + + + + diff --git a/_overviews/scala3-book/scala-for-java-devs.md b/_overviews/scala3-book/scala-for-java-devs.md new file mode 100644 index 0000000000..a12cf990f7 --- /dev/null +++ b/_overviews/scala3-book/scala-for-java-devs.md @@ -0,0 +1,1283 @@ +--- +title: Scala for Java Developers +type: chapter +description: This page is for Java developers who are interested in learning about Scala 3. +num: 71 +previous-page: interacting-with-java +next-page: scala-for-javascript-devs +--- + +{% include_relative scala4x.css %} +<div markdown="1" class="scala3-comparison-page"> + + +This page provides a comparison between the Java and Scala programming languages by sharing side-by-sde examples of each language. +It’s intended for programmers who know Java and want to learn about Scala, specifically by seeing how Scala features compare to Java. + + + +## Overview + +Before getting into the examples, this first section provides a relatively brief introduction and summary of the sections that follow. +It presents the similarities and differences between Java and Scala at a high level, and then introduces the differences you’ll experience every day as you write code. + +### High level similarities + +At a high level, Scala shares these similarities with Java: + +- Scala code is compiled to *.class* files, packaged in JAR files, and runs on the JVM +- It’s an object-oriented programming (OOP) language +- It’s statically typed +- Both languages have support for immutable collections, lambdas, and higher-order functions +- They can both be used with IDEs like IntelliJ IDEA and Microsoft VS Code +- Projects can be built with build tools like Gradle, Ant, and Maven +- It has terrific libraries and frameworks for building server-side, network-intensive applications, including web server applications, microservices, machine learning, and more +- Both Java and Scala can use Scala libraries: + - They can use the [Akka actors library](https://akka.io) to build actor-based concurrent systems, and Apache Spark to build data-intensive applications + - They can use the [Play Framework](https://www.playframework.com) to develop server-side applications +- You can use [GraalVM](https://www.graalvm.org) to compile your projects into native executables +- Scala can seamlessly use the wealth of libraries that have been developed for Java + +### High level differences + +Also at a high level, the differences between Java and Scala are: + +- Scala has a concise but readable syntax; we call it *expressive* +- Though it’s statically typed, Scala often feels like a dynamic language +- Scala is a pure OOP language, so every object is an instance of a class, and symbols like `+` and `+=` that look like operators are really methods; this means that you can create your own operators +- In addition to being a pure OOP language, Scala is also a pure FP language; in fact, it encourages a fusion of OOP and FP, with functions for the logic and objects for modularity +- 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 (`final`) variables and immutable collections +- The Scala ecosystem has other build tools in sbt, Mill, and others +- 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](http://www.scala-native.org) project adds low-level constructs to let you write “systems” level code, and also compiles to native executables + +{% comment %} +TODO: Need a good, simple way to state that Scala has a sound type system +{% endcomment %} + + +### Programming level differences + +Finally, these are some of the differences you’ll see every day when writing code: + +{% comment %} +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’s syntax is extremely consistent +- Variables and parameters are defined as `val` (immutable, like `final` in Java) or `var` (mutable) +- *Type inference* makes your code feel dynamically typed, and helps to keep your code brief +- In addition to simple `for` loops, Scala has powerful `for` comprehensions that yield results based on your algorithms +- Pattern matching and `match` expressions will change the way you write code +- Writing immutable code by default leads to writing *expressions* rather than *statements*; in time you see that writing expressions simplifies your code (and your tests) +- *Toplevel definitions* let you put method, field, and other definitions anywhere, also leading to concise, expressive code +- You can create *mixins* by “mixing” multiple traits into classes and objects (traits are similar to interfaces in Java 8 and newer) +- Classes are closed by default, supporting Joshua Bloch’s *Effective Java* idiom, “Design and document for inheritance or else forbid it” +- Scala’s *contextual abstractions* and *term inference* provide a collection of features: + - *Extension methods* let you add new functionality to closed classes + - *Given* instances let you define terms that the compiler can synthesize at *using* points, making your code less verbose and essentially letting the compiler write code for you + - *Multiversal equality* lets you limit equality comparisons — at compile time — to only those comparisons that make sense +- Scala has state of the art, third-party, open source functional programming libraries +- Scala case classes are like records in Java 14; they help you model data when writing FP code, with built-in support for concepts like pattern matching and cloning +- Thanks to features like by-name parameters, infix notation, optional parentheses, extension methods, and higher-order functions, you can create your own “control structures” and DSLs +- Scala files do not have to be named according to the classes or traits they contain +- Many other goodies: companion classes and objects, macros, union and intersection types, toplevel definitions, numeric literals, multiple parameter lists, default values for parameters, named arguments, and more + +### Features compared with examples + +Given that introduction, the following sections provide side-by-side comparisons of Java and Scala programming language features. + + + +## OOP style classes and methods + +This section provides comparisons of features related to OOP-style classes and methods. + +### Comments: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>// + <br>/* ... */ + <br>/** ... */</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// + <br>/* ... */ + <br>/** ... */</code> + </td> + </tr> + </tbody> +</table> + +### OOP style class, primary constructor: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>class Person { + <br>  private String firstName; + <br>  private String lastName; + <br>  private int age; + <br>  public Person( + <br>    String firstName, + <br>    String lastName, int age + <br>  ) { + <br>    this.firstName = firstName; + <br>    this.lastName = lastName; + <br>    this.age = age; + <br>  } + <br>  override String toString() { + <br>    return String.format("%s %s is %d years old.", firstName, lastName, age); + <br>  } + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class Person ( + <br>  var firstName: String, + <br>  var lastName: String, + <br>  var age: Int + <br>):   + <br>  override def toString = s"$firstName $lastName is $age years old." + </code> + </td> + </tr> + </tbody> +</table> + +### Auxiliary constructors: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public class Person { + <br>  private String firstName; + <br>  private String lastName; + <br>  private int age; + <br> + <br>  // primary constructor + <br>  public Person( + <br>    String firstName, + <br>    String lastName, + <br>    int age + <br>  ) { + <br>    this.firstName = firstName; + <br>    this.lastName = lastName; + <br>    this.age = age; + <br>  } + <br> + <br>  // zero-arg constructor + <br>  public Person( + <br>    String firstName, + <br>    String lastName, + <br>    int age + <br>  ) { + <br>    this("", "", 0); + <br>  } + <br> + <br>  // one-arg constructor + <br>  public Person(String firstName) { + <br>    this(firstName, "", 0); + <br>  } + <br> + <br>  // two-arg constructor + <br>  public Person( + <br>    String firstName, + <br>    String lastName + <br>  ) { + <br>    this(firstName, lastName, 0); + <br>  } + <br> + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class Person ( + <br>  var firstName: String, + <br>  var lastName: String, + <br>  var age: Int + <br>): + <br>    // zero-arg auxiliary constructor + <br>    def this() = this("", "", 0) + <br> + <br>    // one-arg auxiliary constructor + <br>    def this(firstName: String) = + <br>      this(firstName, "", 0) + <br> + <br>    // two-arg auxiliary constructor + <br>    def this( + <br>      firstName: String, + <br>      lastName: String + <br>    ) = + <br>      this(firstName, lastName, 0) + <br> + <br>end Person</code> + </td> + </tr> + </tbody> +</table> + + +### Classes closed by default: +“Plan for inheritance or else forbid it.” + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>final class Person</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class Person</code> + </td> + </tr> + </tbody> +</table> + + +### A class that’s open for extension: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>class Person</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>open class Person</code> + </td> + </tr> + </tbody> +</table> + + +### One-line method: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public int add(int a, int b) { + <br>  return a + b; + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def add(a: Int, b: Int): Int = a + b</code> + </td> + </tr> + </tbody> +</table> + + +### Multiline method: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public void walkThenRun() { + <br>  System.out.println("walk"); + <br>  System.out.println("run"); + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def walkThenRun() = + <br>  println("walk") + <br>  println("run")</code> + </td> + </tr> + </tbody> +</table> + + +### Immutable fields: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>final int i = 1;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val i = 1</code> + </td> + </tr> + </tbody> +</table> + + +### Mutable fields: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>int i = 1; + <br>var i = 1;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>var i = 1</code> + </td> + </tr> + </tbody> +</table> + + + +## Interfaces, traits, and inheritance + +This section compares Java interfaces to Scala traits, including how classes extend interfaces and traits. + + +### Interfaces/traits: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public interface Marker;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>trait Marker</code> + </td> + </tr> + </tbody> +</table> + + +### Simple interface: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public interface Adder { + <br>  public int add(int a, int b); + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>trait Adder: + <br>  def add(a: Int, b: Int): Int</code> + </td> + </tr> + </tbody> +</table> + + +### Interface with a concrete method: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public interface Adder { + <br>  int add(int a, int b); + <br>  default int multiply( + <br>    int a, int b + <br>  ) { + <br>    return a * b; + <br>  } + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>trait Adder: + <br>  def add(a: Int, b: Int): Int + <br>  def multiply(a: Int, b: Int): Int = + <br>    a * b</code> + </td> + </tr> + </tbody> +</table> + + +### Inheritance: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>class Dog extends Animal, HasLegs, HasTail</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class Dog extends Animal, HasLegs, HasTail</code> + </td> + </tr> + </tbody> +</table> + + +### Extend multiple interfaces + +These interfaces and traits have concrete, implemented methods (default methods): + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>interface Adder { + <br>  default int add(int a, int b) { + <br>    return a + b; + <br>  } + <br>} + <br> + <br>interface Multiplier { + <br>  default int multiply ( + <br>    int a, + <br>    int b) + <br>  { + <br>    return a * b; + <br>  } + <br>} + <br> + <br>public class JavaMath <br>implements Adder, Multiplier {} + <br> + <br>JavaMath jm = new JavaMath(); + <br>jm.add(1,1); + <br>jm.multiply(2,2);</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>trait Adder: + <br>  def add(a: Int, b: Int) = a + b + <br> + <br>trait Multiplier: + <br>  def multiply(a: Int, b: Int) = a * b + <br> + <br>class ScalaMath extends Adder, Multiplier + <br> + <br>val sm = new ScalaMath + <br>sm.add(1,1) + <br>sm.multiply(2,2)</code> + </td> + </tr> + </tbody> +</table> + + +### Mixins: + +<table> + <tbody> + <tr> + <td class="java-block"> + N/A + </td> + </tr> + <tr> + <td class="scala-block"> + <code>class DavidBanner + <br> + <br>trait Angry: + <br>  def beAngry() = + <br>    println("You won’t like me ...") + <br> + <br>trait Big: + <br>  println("I’m big") + <br> + <br>trait Green: + <br>  println("I’m green") + <br> + <br>// mix in the traits as DavidBanner + <br>// is created + <br>val hulk = new DavidBanner with Big, + <br>  Angry, Green</code> + </td> + </tr> + </tbody> +</table> + + + +## Control structures + +This section compares control structures in Java and Scala. + +### `if` statement, one line: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>if (x == 1) { System.out.println(1); }</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x == 1 then println(x)</code> + </td> + </tr> + </tbody> +</table> + + +### `if` statement, multiline: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>if (x == 1) { + <br>  System.out.println("x is 1, as you can see:") + <br>  System.out.println(x) + <br>}</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="java-block"> + <code>if (x < 0) { + <br>  System.out.println("negative") + <br>} else if (x == 0) { + <br>  System.out.println("zero") + <br>} else { + <br>  System.out.println("positive") + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x < 0 then + <br>  println("negative") + <br>else if x == 0 + <br>  println("zero") + <br>else + <br>  println("positive")</code> + </td> + </tr> + </tbody> +</table> + + +### `if` as the method body: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>public int min(int a, int b) { + <br>  return (a < b) ? a : b; + <br>}</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> + + +### Return a value from `if`: + +Called a _ternary operator_ in Java: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>int minVal = (a < b) ? a : b;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val minValue = if a < b then a else b</code> + </td> + </tr> + </tbody> +</table> + + +### `while` loop: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>while (i < 3) { + <br>  System.out.println(i); + <br>  i++; + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>while i < 3 do + <br>  println(i) + <br>  i += 1</code> + </td> + </tr> + </tbody> +</table> + + +### `for` loop, single line: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>for (int i: ints) { + <br>  System.out.println(i); + <br>}</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> + + +### `for` loop, multiple lines: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>for (int i: ints) { + <br>  int x = i * 2; + <br>  System.out.println(x); + <br>}</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> + + +### `for` loop, multiple generators: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>for (int i: ints1) { + <br>  for (int j: chars) { + <br>    for (int k: ints2) { + <br>      System.out.printf("i = %d, j = %d, k = %d\n", i,j,k); + <br>    } + <br>  } + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- 1 to 2 + <br>  j <- 'a' to 'b' + <br>  k <- 1 to 10 by 5 + <br>do + <br>  println(s"i = $i, j = $j, k = $k")</code> + </td> + </tr> + </tbody> +</table> + + +### Generator with guards (`if`) expressions: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>List ints = + <br>  ArrayList(1,2,3,4,5,6,7,8,9,10); + <br> + <br>for (int i: ints) { + <br>  if (i % 2 == 0 && i < 5) { + <br>    System.out.println(x); + <br>  } + <br>}</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> + + +### `for` comprehension: + +<table> + <tbody> + <tr> + <td class="java-block"> + N/A + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val list = + <br>  for + <br>    i <- 1 to 3 + <br>  yield + <br>    i * 10 + <br>// list: Vector(10, 20, 30)</code> + </td> + </tr> + </tbody> +</table> + + +### switch/match: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>String monthAsString = ""; + <br>switch(day) { + <br>  case 1: monthAsString = "January"; + <br>          break; + <br>  case 2: monthAsString = "February"; + <br>          break; + <br>  default: monthAsString = "Other"; + <br>          break; + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val monthAsString = day match + <br>  case 1 => "January" + <br>  case 2 => "February" + <br>  _ => "Other" + </code> + </td> + </tr> + </tbody> +</table> + + +### switch/match, multiple conditions per case: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>String numAsString = ""; + <br>switch (i) { + <br>  case 1: case 3: + <br>  case 5: case 7: case 9: + <br>    numAsString = "odd"; + <br>    break; + <br>  case 2: case 4: + <br>  case 6: case 8: case 10: + <br>    numAsString = "even"; + <br>    break; + <br>  default: + <br>    numAsString = "too big"; + <br>    break; + <br>}</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="java-block"> + <code>try { + <br>  writeTextToFile(text); + <br>} catch (IOException ioe) { + <br>  println(ioe.getMessage()) + <br>} catch (NumberFormatException nfe) { + <br>  println(nfe.getMessage()) + <br>} finally { + <br>  println("Clean up resources here.") + <br>}</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 nfe: NumberFormatException => + <br>    println(nfe.getMessage) + <br>finally + <br>  println("Clean up resources here.")</code> + </td> + </tr> + </tbody> +</table> + + + +## Collections classes + +This section compares the collections classes that are available in Java and Scala. + + +### Immutable collections classes + +Examples of how to create instances of immutable collections. + + +### Sequences: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>List strings = List.of("a", "b", "c");</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val strings = List("a", "b", "c") + <br>val strings = Vector("a", "b", "c")</code> + </td> + </tr> + </tbody> +</table> + + +### Sets: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>Set set = Set.of("a", "b", "c");</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val set = Set("a", "b", "c")</code> + </td> + </tr> + </tbody> +</table> + + +### Maps: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>Map map = Map.of( + <br>  "a", 1, + <br>  "b", 2, + <br>  "c", 3 + <br>);</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val map = Map( + <br>  "a" -> 1, + <br>  "b" -> 2, + <br>  "c" -> 3 + <br>)</code> + </td> + </tr> + </tbody> +</table> + + +### Mutable collections classes + +Scala has mutable collections classes like `ArrayBuffer`, `Map`, and `Set`, in its *scala.collection.mutable* package. +After importing them into the current scope, they’re created just like the immutable `List`, `Vector`, `Map`, and `Set` examples just shown. + +You can also convert between Java and Scala collections classes with the Scala `CollectionConverters` objects. +There are two objects in different packages, one for converting from Java to Scala, and another for converting from Scala to Java. +This table shows the possible conversions: + +<table> + <tbody> + <tr> + <th>Java</th> + <th>Scala</th> + </tr> + <tr> + <td valign="top">java.util.Collection</td> + <td valign="top">scala.collection.Iterable</td> + </tr> + <tr> + <td valign="top">java.util.List</td> + <td valign="top">scala.collection.mutable.Buffer</td> + </tr> + <tr> + <td valign="top">java.util.Set</td> + <td valign="top">scala.collection.mutable.Set</td> + </tr> + <tr> + <td valign="top">java.util.Map</td> + <td valign="top">scala.collection.mutable.Map</td> + </tr> + <tr> + <td valign="top">java.util.concurrent.ConcurrentMap</td> + <td valign="top">scala.collection.mutable.ConcurrentMap</td> + </tr> + <tr> + <td valign="top">java.util.Dictionary</td> + <td valign="top">scala.collection.mutable.Map</td> + </tr> + </tbody> +</table> + + + +## Methods on collections classes + +With the ability to treat Java collections as streams, Java and Scala now have many of the same common functional methods available to them: + +- `map` +- `filter` +- `forEach`/`foreach` +- `findFirst`/`find` +- `reduce` + +If you’re used to using these methods with lambda expressions in Java, you’ll find it easy to use the same methods on Scala’s collection classes. + +Scala also has *dozens* of other collections methods, including `head`, `tail`, `drop`, `take`, `distinct`, `flatten`, and many more. +At first you may wonder why there are so many methods, but after working with Scala you’ll realize that because of these methods, you rarely ever need to write custom `for` loops any more. + +(This also means that you rarely need to *read* custom `for` loops, as well. +Because developers tend to spend on the order of ten times as much time *reading* code as *writing* code, this is significant.) + + + +## Tuples + +Java tuples are created like this: + +```scala +Pair<String, Integer> pair = + new Pair<String, Integer>("Eleven", 11); + +Triplet<String, Integer, Double> triplet = + Triplet.with("Eleven", 11, 11.0); +Quartet<String, Integer, Double,Person> triplet = + Triplet.with("Eleven", 11, 11.0, new Person("Eleven")); +``` + +Other Java tuple names are Quintet, Sextet, Septet, Octet, Ennead, Decade. + +Tuples of any size in Scala are created by putting the values inside parentheses, like this: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + + + +## Enums + +This section compares enumerations in Java and Scala. + + +### Basic enum: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>enum Color { + <br>  RED, GREEN, BLUE + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>enum Color: + <br>  case Red, Green, Blue</code> + </td> + </tr> + </tbody> +</table> + + +### Parameterized enum: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>enum Color { + <br>  Red(0xFF0000), + <br>  Green(0x00FF00), + <br>  Blue(0x0000FF); + <br> + <br>  private int rgb; + <br> + <br>  Color(int rgb) { + <br>    this.rgb = rgb; + <br>  } + <br>}</code> + </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> + + +### User-defined enum members: + +<table> + <tbody> + <tr> + <td class="java-block"> + <code>enum Planet { + <br>  MERCURY (3.303e+23, 2.4397e6), + <br>  VENUS   (4.869e+24, 6.0518e6), + <br>  EARTH   (5.976e+24, 6.37814e6); + <br>  // more planets ... + <br> + <br>  private final double mass; + <br>  private final double radius; + <br> + <br>  Planet(double mass, double radius) { + <br>    this.mass = mass; + <br>    this.radius = radius; + <br>  } + <br> + <br>  public static final double G = + <br>    6.67300E-11; + <br> + <br>  private double mass() { + <br>    return mass; + <br>  } + <br> + <br>  private double radius() { + <br>    return radius; + <br>  } + <br> + <br>  double surfaceGravity() { + <br>    return G * mass / + <br>      (radius * radius); + <br>  } + <br> + <br>  double surfaceWeight( + <br>    double otherMass + <br>  ) { + <br>    return otherMass * + <br>      surfaceGravity(); + <br>  } + <br> + <br>}</code> + </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>  private final val G = 6.67300E-11 + <br> + <br>  def surfaceGravity = <br>    G * mass / (radius * radius) + <br> + <br>  def surfaceWeight(otherMass: Double) + <br>    = otherMass * surfaceGravity</code> + </td> + </tr> + </tbody> +</table> + + + +## Exceptions and error handling + +This section covers the differences between exception handling in Java and Scala. + +### Java uses checked exceptions + +Java uses checked exceptions, so in Java code you have historically written `try`/`catch`/`finally` blocks, along with `throws` clauses on methods: + +```scala +public int makeInt(String s) +throws NumberFormatException { + // code here to convert a String to an int +} +``` + +### Scala doesn’t use checked exceptions + +The Scala idiom is to *not* use checked exceptions like this. +When working with code that can throw exceptions, you can use `try`/`catch`/`finally` blocks to catch exceptions from code that throws them, but how you proceed from there is different. + +The best way to explain this is that Scala code consists of *expressions*, which return values. +As a result, you end up writing your code as a series of algebraic expressions: + +```scala +val a = f(x) +val b = g(a,z) +val c = h(b,y) +``` + +This is nice, it’s just algebra. +You create equations to solve small problems, and then combine equations to solve larger problems. + +And very importantly — as you remember from algebra courses — algebraic expressions don’t short circuit — they don’t throw exceptions that blow up a series of equations. + +Therefore, in Scala our methods don’t throw exceptions. +Instead, they return types like `Option`. +For example, this `makeInt` method catches a possible exception and returns an `Option` value: + +```scala +def makeInt(s: String): Option[Int] = + try + Some(s.toInt) + catch + case e: NumberFormatException => None +``` + +The Scala `Option` is similar to the Java `Optional` class. +As shown, if the string-to-int conversion succeeds, the `Int` is returned inside a `Some` value, and if it fails, a `None` value is returned. +`Some` and `None` are subtypes of `Option`, so the method is declared to return the `Option[Int]` type. + +When you have an `Option` value, such as the one returned by `makeInt`, there are many ways to work with it, depending on your needs. +This code shows one possible approach: + +```scala +makeInt(aString) match + case Some(i) => println(s"Int i = $i") + case None => println(s"Could not convert $aString to an Int.") +``` + +`Option` is commonly used in Scala, and it’s built into many classes in the standard library. +Other similar sets of classes like Try/Success/Failure and Either/Left/Right offer even more flexibility. + +For more information on dealing with errors and exceptions in Scala, see the [Functional Error Handling][error-handling] section. + + + +## Concepts that are unique to Scala + +That concludes are comparison of the Java and Scala languages. + +Currently there are other concepts in Scala which currently have no equal in Java 11. +This includes: + +- Everything related to Scala’s contextual abstractions +- Several Scala method features: + - Multiple parameter lists + - Default parameter values + - Using named arguments when calling methods +- Case classes (like “records” in Java 14) and case objects +- Companion classes and objects +- The ability to create your own control structures and DSLs +- [Toplevel definitions][toplevel] +- Pattern matching +- Advanced features of `match` expressions +- Type lambdas +- Trait parameters +- [Opaque type aliases][opaque] +- [Multiversal equality][equality] +- [Type classes][type-classes] +- Infix methods +- Macros and metaprogramming + + +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} +[opaque]: {% link _overviews/scala3-book/types-opaque-types.md %} +[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} +[error-handling]: {% link _overviews/scala3-book/fp-functional-error-handling.md %} + +</div> + diff --git a/_overviews/scala3-book/scala-for-javascript-devs.md b/_overviews/scala3-book/scala-for-javascript-devs.md new file mode 100644 index 0000000000..42037b1940 --- /dev/null +++ b/_overviews/scala3-book/scala-for-javascript-devs.md @@ -0,0 +1,1365 @@ +--- +title: Scala for JavaScript Developers +type: chapter +description: This chapter provides an introduction to Scala 3 for JavaScript developers +num: 72 +previous-page: scala-for-java-devs +next-page: scala-for-python-devs +--- + +{% include_relative scala4x.css %} +<div markdown="1" class="scala3-comparison-page"> + + +This page provides a comparison between the JavaScript and Scala programming languages. +It’s intended for programmers who know JavaScript and want to learn about Scala, specifically by seeing examples of how JavaScript language features compare to Scala. + + + +## Overview + +This section provides a relatively brief introduction and summary of the sections that follow. +It presents the similarities and differences between JavaScript and Scala at a high level, and then introduces the differences you’ll experience every day as you write code. + +### High-level similarities + +At a high level, Scala shares these similarities with JavaScript: + +- Both are considered high-level programming languages, where you don’t have to concern yourself with low-level concepts like pointers and manual memory management +- Both have a relatively simple, concise syntax +- Both support a C/C++/Java style curly-brace syntax for writing methods and other block of code +- Both include features (like classes) for object-oriented programming (OOP) +- Both include features (like lambdas) for functional programming (FP) +- JavaScript runs in the browser and other environments like Node.js. + The [Scala.js](https://www.scala-js.org) flavor of Scala targets JavaScript and Scala programs can thus run in the same environments. +- Developers write server-side applications in JavaScript and Scala using [Node.js](https://nodejs.org); projects like the [Play Framework](https://www.playframework.com/) also let you write server-side applications in Scala +- Both languages have similar `if` statements, `while` loops, and `for` loops +- Starting [at this Scala.js page](https://www.scala-js.org/libraries/index.html), you’ll find dozens of libraries to support React, Angular, jQuery, and many other JavaScript and Scala libraries +- JavaScript objects are mutable; Scala objects _can_ be mutable when writing in an imperative style +- Both JavaScript and Scala support *promises* as a way of running asynchronous computations (Scala uses futures and promises) + +### High-level differences + +Also at a high level, some of the differences between JavaScript and Scala are: + +- JavaScript is dynamically typed, and Scala is statically typed + - Although Scala is statically typed, features like type inference make it feel like a dynamic language (as you’ll see in the examples that follow) +- Scala idioms favor immutability by default: you’re encouraged to use immutable variables and immutable collections +- Scala has a concise but readable syntax; we call it *expressive* +- Scala is a pure OOP language, so every object is an instance of a class, and symbols like `+` and `+=` that look like operators are really methods; this means that you can create your own methods that work as operators +- 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 + +### Programming level differences + +At a lower level, these are some of the differences you’ll see every day when writing code: + +- Scala variables and parameters are defined with `val` (immutable, like a JavaScript `const`) or `var` (mutable, like a JavaScript `var` or `let`) +- Scala does not use semi-colons at the end of lines +- Scala is statically-typed, though in many situations you don’t need to declare the type +- Scala uses traits as interfaces and to create *mixins* +- In addition to simple `for` loops, Scala has powerful `for` comprehensions that yield results based on your algorithms +- Pattern matching and `match` expressions will change the way you write code +- Scala’s *contextual abstractions* and *term inference* provide a collection of features: + - *Extension methods* let you add new functionality to closed classes without breaking modularity, by being available only in specific scopes (as opposed to monkey-patching, which can pollute other areas of the code) + - *Given* instances let you define terms that the compiler can use to synthesize code for you + - Type safety and *multiversal equality* let you limit equality comparisons — at compile time — to only those comparisons that make sense +- Thanks to features like by-name parameters, infix notation, optional parentheses, extension methods, and higher-order functions, you can create your own “control structures” and DSLs +- Many other goodies that you can read about throughout this book: case classes, companion classes and objects, macros, union and intersection types, multiple parameter lists, named arguments, and more... + + + +## Variables and Types + +### Comments + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>// + <br>/* ... */ + <br>/** ... */</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// + <br>/* ... */ + <br>/** ... */</code> + </td> + </tr> + </tbody> +</table> + + +### Mutable variables + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let   // now preferred for mutable + <br>var   // old mutable style</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>var  // used for mutable variables</code> + </td> + </tr> + </tbody> +</table> + + +### Immutable values + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>const</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val</code> + </td> + </tr> + </tbody> +</table> + +The rule of thumb in Scala is to declare variables using `val`, unless there’s a specific reason you need a mutable variable. + + + +## Naming standards + +JavaScript and Scala generally use the same *CamelCase* naming standards. +Variables are named like `myVariableName`, methods are named like `lastIndexOf`, and classes and object are named like `Animal` and `PrintedBook`. + + + +## Strings + +Many uses of strings are similar in JavaScript and Scala, though Scala only uses double-quotes for simple strings, and triple-quotes for multiline strings. + + +### String basics + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>// use single- or double-quotes + <br>let msg = 'Hello, world'; + <br>let msg = "Hello, world";</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// use only double-quotes + <br>val msg = "Hello, world"</code> + </td> + </tr> + </tbody> +</table> + + +### Interpolation + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let name = 'Joe'; + <br> + <br>// JavaScript uses backticks + <br>let msg = `Hello, ${name}`;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val name = "Joe" + <br>val age = 42 + <br>val weight = 180.5 + <br> + <br>// use `s` before a string for simple interpolation + <br>println(s"Hi, $name")   // "Hi, Joe" + <br>println(s"${1 + 1}")    // "2" + <br> + <br>// `f` before a string allows printf-style formatting. + <br>// this example prints: + <br>// "Joe is 42 years old, and weighs" + <br>// "180.5 pounds." + <br>println(f"$name is $age years old, and weighs $weight%.1f pounds.")</code> + </td> + </tr> + </tbody> +</table> + + +### Multiline strings with interpolation + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let name = "joe"; + <br>let str = ` + <br>Hello, ${name}. + <br>This is a multiline string. + <br>`; + </code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val name = "Martin Odersky" + <br> + <br>val quote = s""" + <br> |$name says + <br> |Scala is a fusion of + <br> |OOP and FP. + <br>""".stripMargin.replaceAll("\n", " ").trim + <br> + <br>// result: + <br>// "Martin Odersky says Scala is a fusion of OOP and FP." + </code> + </td> + </tr> + </tbody> +</table> + +JavaScript and Scala also have similar methods to work with strings, including `charAt`, `concat`, `indexOf`, and many more. +Escape characters like `\n`, `\f`, `\t` are also the same in both languages. + + + +## Numbers and arithmetic + +Numeric operators are similar between JavaScript and Scala. +The biggest difference is that Scala doesn’t offer `++` and `--` operators. + + +### Numeric operators: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let x = 1; + <br>let y = 2.0; + <br>  + <br>let a = 1 + 1; + <br>let b = 2 - 1; + <br>let c = 2 * 2; + <br>let d = 4 / 2; + <br>let e = 5 % 2; + </code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = 1 + <br>val y = 2.0 + <br>  + <br>val a = 1 + 1 + <br>val b = 2 - 1 + <br>val c = 2 * 2 + <br>val d = 4 / 2 + <br>val e = 5 % 2 + </code> + </td> + </tr> + </tbody> +</table> + + +### Increment and decrement: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>i++; + <br>i += 1; + <br> + <br>i--; + <br>i -= 1;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>i += 1; + <br>i -= 1;</code> + </td> + </tr> + </tbody> +</table> + +Perhaps the biggest difference is that “operators” like `+` and `-` are really *methods* in Scala, not operators. +Scala numbers also have these related methods: + +```scala +var a = 2 +a *= 2 // 4 +a /= 2 // 2 +``` + +Scala's `Double` type most closely corresponds to JavaScript’s default `number` type, +`Int` represents signed 32-bit integer values, and `BigInt` corresponds to JavaScript's `bigint`. + +These are Scala `Int` and `Double` values. +Notice that the type doesn’t have to be explicitly declared: + +```scala +val i = 1 // Int +val d = 1.1 // Double +``` + +You can also use other numeric types as needed: + +```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) +``` + + +### Boolean values + +Both languages use `true` and `false` for boolean values: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let a = true; + <br>let b = false;</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val a = true + <br>val b = false</code> + </td> + </tr> + </tbody> +</table> + + + +## Dates + +Dates are another commonly used type in both languages. + +### Get the current date: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let d = new Date();<br> + <br>// result: + <br>// Sun Nov 29 2020 18:47:57 GMT-0700 (Mountain Standard Time) + </code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// different ways to get the current date and time + <br>import java.time._ + <br> + <br>val a = LocalDate.now + <br>    // 2020-11-29 + <br>val b = LocalTime.now + <br>    // 18:46:38.563737 + <br>val c = LocalDateTime.now + <br>    // 2020-11-29T18:46:38.563750 + <br>val d = Instant.now + <br>    // 2020-11-30T01:46:38.563759Z</code> + </td> + </tr> + </tbody> +</table> + + +### Specify a different date: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let d = Date(2020, 1, 21, 1, 0, 0, 0); + <br>let d = Date(2020, 1, 21, 1, 0, 0); + <br>let d = Date(2020, 1, 21, 1, 0); + <br>let d = Date(2020, 1, 21, 1); + <br>let d = Date(2020, 1, 21);</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val d = LocalDate.of(2020, 1, 21) + <br>val d = LocalDate.of(2020, Month.JANUARY, 21) + <br>val d = LocalDate.of(2020, 1, 1).plusDays(20) + </code> + </td> + </tr> + </tbody> +</table> + +In this case, Scala uses the date and time classes that come with Java. +Many date/time methods are similar between JavaScript and Scala. +See [the *java.time* package](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/package-summary.html) for more details. + + + +## Functions + +In both JavaScript and Scala, functions are objects, so their functionality is similar, but their syntax and terminology is a little different. + +### Named functions, one line: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>function add(a, b) { + <br>  return a + b; + <br>} + <br>add(2, 2);   // 4</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// technically this is a “method,” not a function + <br>def add(a: Int, b: Int) = a + b + <br>add(2, 2)   // 4</code> + </td> + </tr> + </tbody> +</table> + +### Named functions, multiline: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>function addAndDouble(a, b) { + <br>  // imagine this requires + <br>  // multiple lines + <br>  return (a + b) * 2 + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>def addAndDouble(a: Int, b: Int): Int = + <br>  // imagine this requires + <br>  // multiple lines + <br>  (a + b) * 2</code> + </td> + </tr> + </tbody> +</table> + +In Scala, showing the `Int` return type is optional. +It’s _not_ shown in the `add` example and _is_ shown in the `addThenDouble` example, so you can see both approaches. + + + +## Anonymous functions + +Both JavaScript and Scala let you define anonymous functions, which you can pass into other functions and methods. + +### Arrow and anonymous functions + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>// arrow function + <br>let log = (s) => console.log(s) + <br> + <br>// anonymous function + <br>let log = function(s) { + <br>  console.log(s); + <br>} + <br> + <br>// use either of those functions here + <br>function printA(a, log) { + <br>  log(a); + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// a function (an anonymous function assigned to a variable) + <br>val log = (s: String) => console.log(s) + <br> + <br>// a scala method. methods tend to be used much more often, + <br>// probably because they’re easier to read. + <br>def log(a: Any) = console.log(a) + <br> + <br>// a function or a method can be passed into another + <br>// function or method + <br>def printA(a: Any, f: log: Any => Unit) = log(a) + </code> + </td> + </tr> + </tbody> +</table> + +In Scala you rarely define a function using the first syntax shown. +Instead, you often define anonymous functions right at the point of use. +Many collections methods are higher-order functions and accept function parameters, so you write code like this: + +```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) +``` + + + +## Classes + +Scala has both classes and case classes. +A *class* is similar to a JavaScript class, and is generally intended for use in OOP style applications (though they can also be used in FP code), and *case classes* have additional features that make them very useful in FP style applications. + +The following example shows how to create several types as enumerations, and then defines an OOP-style `Pizza` class. +At the end, a `Pizza` instance is created and used: + +```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) +``` + + + +## Interfaces, traits, and inheritance + +Scala uses traits as interfaces, and also to create mixins. +Traits can have both abstract and concrete members, including methods and fields. + +This example shows how to define two traits, create a class that extends and implements those traits, and then create and use an instance of that class: + +```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" +``` + + + +## Control Structures + +Except for the use of `===` and `!==` in JavaScript, comparison and logical operators are almost identical in JavaScript and Scala. + +{% comment %} +TODO: Sébastien mentioned that `===` is closest to `eql` in Scala. Update this area. +{% endcomment %} + +### Comparison operators + +<table> + <tbody> + <tr> + <th valign="top">JavaScript</th> + <th valign="top">Scala</th> + </tr> + <tr> + <td valign="top"> + <code>==</code></td> + <td valign="top"> + <code>==</code></td> + </tr> + <tr> + <td valign="top"> + <code>===</code></td> + <td valign="top"> + <code>==</code></td> + </tr> + <tr> + <td valign="top"> + <code>!=</code></td> + <td valign="top"> + <code>!=</code></td> + </tr> + <tr> + <td valign="top"> + <code>!==</code></td> + <td valign="top"> + <code>!=</code></td> + </tr> + <tr> + <td valign="top"> + <code>></code></td> + <td valign="top"> + <code>></code></td> + </tr> + <tr> + <td valign="top"> + <code><</code></td> + <td valign="top"> + <code><</code></td> + </tr> + <tr> + <td valign="top"> + <code>>=</code></td> + <td valign="top"> + <code>>=</code></td> + </tr> + <tr> + <td valign="top"> + <code><=</code></td> + <td valign="top"> + <code><=</code></td> + </tr> + </tbody> +</table> + +### Logical operators + +<table> + <tbody> + <tr> + <th valign="top">JavaScript</th> + <th valign="top">Scala</th> + </tr> + <tr> + <td valign="top"> + <code>&& + <br>|| + <br>!</code> + </td> + <td valign="top"> + <code>&& + <br>|| + <br>!</code> + </td> + </tr> + </tbody> +</table> + + + +## if/then/else expressions + +JavaScript and Scala if/then/else statements are similar. +In Scala 2 they were almost identical, but with Scala 3, curly braces are no longer necessary (though they can still be used). + +### `if` statement, one line: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>if (x == 1) { console.log(1); }</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x == 1 then println(x)</code> + </td> + </tr> + </tbody> +</table> + +### `if` statement, multiline: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>if (x == 1) { + <br>  console.log("x is 1, as you can see:") + <br>  console.log(x) + <br>}</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="javascript-block"> + <code>if (x < 0) { + <br>  console.log("negative") + <br>} else if (x == 0) { + <br>  console.log("zero") + <br>} else { + <br>  console.log("positive") + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>if x < 0 then + <br>  println("negative") + <br>else if x == 0 + <br>  println("zero") + <br>else + <br>  println("positive")</code> + </td> + </tr> + </tbody> +</table> + +### Returning a value from `if`: + +JavaScript uses a ternary operator, and Scala uses its `if` expression as usual: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let minVal = a < b ? a : 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` as the body of a method: + +Scala methods tend to be very short, and you can easily use `if` as the body of a method: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>function min(a, b) { + <br>  return (a < b) ? a : b; + <br>}</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> + +In Scala 3 you can still use the “curly brace” style, if you prefer. +For instance, you can write an if/else-if/else expression like this: + +```scala +if (i == 0) { + println(0) +} else if (i == 1) { + println(1) +} else { + println("other") +} +``` + + + +## Loops + +Both JavaScript and Scala have `while` loops and `for` loops. +Scala used to have do/while loops, but they have been removed from the language. + +### `while` loop: + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>let i = 0; + <br>while (i < 3) { + <br>  console.log(i); + <br>  i++; + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>var i = 0; + <br>while i < 3 do + <br>  println(i) + <br>  i += 1</code> + </td> + </tr> + </tbody> +</table> + +The Scala code can also be written like this, if you prefer: + +```scala +var i = 0 +while (i < 3) { + println(i) + i += 1 +} +``` + +The following examples show `for` loops in JavaScript and Scala. +They assume that you have these collections to work with: + +```scala +// JavaScript +let nums = [1, 2, 3]; + +// Scala +val nums = List(1, 2, 3) +``` + +### `for` loop, single line + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>// newer syntax + <br>for (let i of nums) { + <br>  console.log(i); + <br>} + <br> + <br>// older + <br>for (i=0; i<nums.length; i++) { + <br>  console.log(nums[i]); + <br>}</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> + +### `for` loop, multiple lines in the body + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>// preferred + <br>for (let i of nums) { + <br>  let j = i * 2; + <br>  console.log(j); + <br>} + <br> + <br>// also available + <br>for (i=0; i<nums.length; i++) { + <br>  let j = nums[i] * 2; + <br>  console.log(j); + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>// preferred + <br>for i <- ints do + <br>  val i = i * 2 + <br>  println(j) + <br> + <br>// also available + <br>for (i <- nums) { + <br>  val j = i * 2 + <br>  println(j) + <br>}</code> + </td> + </tr> + </tbody> +</table> + +### Multiple generators in a `for` loop + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <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>      let c = str.charAt(j); + <br>      console.log(`i: ${i} j: ${c} k: ${k}`); + <br>    } + <br>  } + <br>}</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>for + <br>  i <- 1 to 2 + <br>  j <- 'a' to 'b' + <br>  k <- 1 to 10 by 5 + <br>do + <br>  println(s"i: $i, j: $j, k: $k")</code> + </td> + </tr> + </tbody> +</table> + +### Generator with guards + +A _guard_ is a name for an `if` expression inside a `for` expression. + +<table> + <tbody> + <tr> + <td class="javascript-block"> + <code>for (let i = 0; i < 10; i++) { + <br>  if (i % 2 == 0 && i < 5) { + <br>    console.log(i); + <br>  } + <br>}</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> + +### `for` comprehension + +A `for` comprehension is a `for` loop that uses `yield` to return (yield) a value. They’re used often in Scala. + +<table> + <tbody> + <tr> + <td class="javascript-block"> + N/A + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val list = + <br>  for + <br>    i <- 1 to 3 + <br>  yield + <br>    i * 10 + <br>// result: Vector(10, 20, 30)</code> + </td> + </tr> + </tbody> +</table> + + + +## switch & match + +Where JavaScript has `switch` statements, Scala has `match` expressions. +Like everything else in Scala, these truly are *expressions*, meaning they return a result: + +```scala +val day = 1 + +// later in the code ... +val monthAsString = day match + case 1 => "January" + case 2 => "February" + case _ => "Other" +``` + +`match` expressions can handle multiple matches in each `case` statement: + +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` + +They can also be used as the body of a method: + +```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` expressions have many other pattern-matching options. + + + +## Collections classes + +Scala has different collections classes for different needs. + +Common *immutable* sequences are: + +- `List` +- `Vector` + +Common *mutable* sequences are: + +- `Array` +- `ArrayBuffer` + +Scala also has mutable and immutable Maps and Sets. + +This is how you create the common Scala collection types: + +```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 +) +``` + +### Methods on collections + +The following examples show many different ways to work with Scala collections. + +### Populating lists: + +```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) +``` + +### Functional methods on sequences: + +```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 has *many* more methods that are available to you. +The benefits of all these methods are: + +- You don’t have to write custom `for` loops to solve problems +- When you read someone else’s code, you won’t have to read their custom `for` loops; you’ll just find common methods like these, so it’s easier to read code from different projects + + +### Tuples + +When you want to put multiple data types in the same list, JavaScript lets you do this: + +```scala +let stuff = ["Joe", 42, 1.0]; +``` + +In Scala you do this: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + +In Scala these types are called tuples, and as shown, they can contain one or more elements, and the elements can have different types. +You access their elements just like you access elements of a `List`, `Vector`, or `Array`: + +```scala +d(0) // "eleven" +d(1) // 11 +``` + +### Enumerations + +JavaScript doesn’t have enumerations, but you can do this: + +```javascript +let Color = { + RED: 1, + GREEN: 2, + BLUE: 3 +}; +Object.freeze(Color); +``` + +In Scala 3 you can do quite a few things with enumerations. +You can create an equivalent of that code: + +```scala +enum Color: + case Red, Green, Blue +``` + +You can create a parameterized enum: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +You can also create user-defined enum members: + +```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 Code + +Scala.js lets you write Scala code that is compiled to JavaScript code that can then be used in the browser. +The approach is similar to TypeScript, ReScript, and other languages that are compiled to JavaScript. + +Once you include the necessary libraries, and import the necessary packages in your project, writing Scala.js code looks very similar to writing JavaScript code: + +```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) +``` + +Note that although Scala is a type-safe language, no types are declared in the above code. +Scala’s strong type inference capabilities often make Scala code look like it’s dynamically typed. +But it is type-safe, so you catch many classes of errors early in the development cycle. + + + +## Other Scala.js resources + +The Scala.js website has an excellent collection of tutorials for JavaScript developers interested in using Scala.js. +Here are some of their initial tutorials: + +- [Basic tutorial (creating a first Scala.js project)](https://www.scala-js.org/doc/tutorial/basic/) +- [Scala.js for JavaScript developers](https://www.scala-js.org/doc/sjs-for-js/) +- [From ES6 to Scala: Basics](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part1.html) +- [From ES6 to Scala: Collections](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part2.html) +- [From ES6 to Scala: Advanced](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part3.html) + + + +## Concepts that are unique to Scala + +There are other concepts in Scala which currently have no equivalent in JavaScript: + +- Almost everything related to [contextual abstractions][contextual] +- Method features: + - Multiple parameter lists + - Using named arguments when calling methods +- Using traits as interfaces +- Case classes +- Companion classes and objects +- The ability to create your own control structures and DSLs +- Advanced features of `match` expressions and pattern matching +- `for` comprehensions +- Infix methods +- Macros and metaprogramming +- More ... + + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} + +</div> + diff --git a/_overviews/scala3-book/scala-for-python-devs.md b/_overviews/scala3-book/scala-for-python-devs.md new file mode 100644 index 0000000000..b14ef53fc2 --- /dev/null +++ b/_overviews/scala3-book/scala-for-python-devs.md @@ -0,0 +1,1318 @@ +--- +title: Scala for Python Developers +type: chapter +description: This page is for Python developers who are interested in learning about Scala 3. +num: 73 +previous-page: scala-for-javascript-devs +next-page: +--- + +{% 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 %} + + +This section provides a comparison between the Python and Scala programming languages. +It’s intended for programmers who know Python and want to learn about Scala, specifically by seeing examples of how Python language features compare to Scala. + + + +## Introduction + +Before getting into the examples, this first section provides a relatively brief introduction and summary of the sections that follow. +The two languages are first compared at a high level, and then at an everyday programming level. + +### High level similarities + +At a high level, Scala shares these *similarities* with Python: + +- Both are high-level programming languages, where you don’t have to concern yourself with low-level concepts like pointers and manual memory management +- Both have a relatively simple, concise syntax +- Both are functional programming (FP) languages +- Both are object-oriented programming (OOP) languages +- Both have comprehensions: Python has list comprehensions and Scala has `for` comprehensions +- Both languages have support for lambdas and higher-order functions +- Both can be used with [Apache Spark](https://spark.apache.org) for big data processing +- Both have a wealth of terrific libraries + +### High level differences + +Also at a high level, the *differences* between Python and Scala are: + +- Python is dynamically typed, and Scala is statically typed + - 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 +- 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 + +### Programming level similarities + +This section looks at the similarities you’ll see between Python and Scala when you write code on an everyday basis: + +- Scala’s type inference often makes it feel like a dynamically typed language +- Neither language uses semicolons to end expressions +- Both languages support the use of significant indentation rather than braces and parentheses +- The syntax for defining methods is similar +- Both have lists, dictionaries (maps), sets, and tuples +- Both have comprehensions for mapping and filtering +- Both have higher-order functions and strong support for lambdas +- With Scala 3’s toplevel definitions 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 def` method is required to start a Scala application + +### Programming level differences + +Also at a programming level, these are some of the differences you’ll see every day when writing code: + +- Scala’s syntax is extremely consistent: + - Lists, maps, sets, and tuples are all created and accessed similarly + - Collections classes generally have most of the same higher-order functions + - `val` and `var` fields are used consistently to define fields and parameters + - Pattern matching is used consistently throughout the language +- 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 +- Pattern matching and `match` expressions are used extensively in Scala (and will change the way you write code) +- Traits are used heavily in Scala; interfaces and abstract classes are used less often in Python +- Scala’s *contextual abstractions* and *term inference* provide a collection of different features: + - *Extension methods* let you easily add new functionality to classes using a clear syntax + - *Multiversal equality* lets you limit equality comparisons — at compile time — to only those comparisons that make sense +- Scala has state-of-the-art open source functional programming libraries +- 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 and GraalVM) for high performance +- Many other goodies: case classes, companion classes and objects, macros, union and intersection types, toplevel definitions, numeric literals, multiple parameter lists, and more + + + +### Features compared with examples + +Given that introduction, the following sections provide side-by-side comparisons of Python and Scala programming language features. + +> The general Python standard is to indent code with four spaces, but in the following examples only two spaces are used. +> This is only done so the examples can be shown side by side. + + +## Comments + +Python uses `#` for comments, while the Scala comment syntax is the same as languages like C, C++, and 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> + + + +## Variable assignment + +These examples demonstrate how to create variables in Python and Scala. + +### Create integer and string variables: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = 1 + <br>x = "Hi"</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = 1 + <br>val x = "Hi"</code> + </td> + </tr> + </tbody> +</table> + +### Lists: + +<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> + +### Dictionary/Map: + +<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 movies = Map( + <br>  "Toy Story" -> 8.3, + <br>  "Forrest Gump" -> 8.8, + <br>  "Cloud Atlas" -> 7.4 + <br>)</code> + </td> + </tr> + </tbody> +</table> + +### Set: + +<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> + +### Tuple: + +<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> + +If a Scala field is going to be mutable, use `var` instead of `val` for variable assignment: + +```scala +var x = 1 +x += 1 +``` + +However, the rule of thumb in Scala is to always use `val` unless the variable specifically needs to be mutated. + + + +## OOP style classes and methods + +This section provides comparisons of features related to OOP-style classes and methods. + +### OOP style class, primary constructor: + +<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('Hello, my name is %s' % 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> + +### Create and use an instance: + +<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> + +### One-line method: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>def add(a,b) = 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> + +### Multiline method: + +<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> + + + +## 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. +Therefore, rather than attempt to compare the two side by side, this example shows how to use Scala traits to build a small solution to a simulated math problem: + +```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 +``` + +There are many other ways to use traits with classes and objects, but this gives you a little idea of how they can be used to organize concepts into logical groups of behavior, and then merge them as needed to create a complete solution. + + + +## Control structures + +This section compares control structures in Python and Scala. +Both languages have constructs like `if`/`else`, `while`, `for` loops, and `try`. +Scala also has `match` expressions. + +### `if` statement, one line: + +<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` statement, multiline: + +<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> + +### Returning a value from `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` as the body of a method: + +<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` loop: + +<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> + +### `for` loop with range: + +<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` loop with a list: + +<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` loop, multiple lines: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>for i in ints: + <br>  x = i * 2 + <br>  s = "i = {}, x = {}" + <br>  print(s.format(i,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> + +### Multiple “range” generators: + +<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>      s= "i = {}, j = {}, k = {}" + <br>      print(s.format(i,j,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> + +### Generator with guards (`if` expressions): + +<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> + +### Multiple `if` conditions per line: + +<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> + +### Comprehensions: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>x = [i*10 for i in range(1,4)] + <br># x: [10,20,30]</code> + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val x = + <br>  for + <br>    i <- 1 to 3 + <br>  yield + <br>    i * 10 + <br>// x: Vector(10, 20, 30)</code> + </td> + </tr> + </tbody> +</table> + +### `match` expressions: + +<table> + <tbody> + <tr> + <td class="python-block"> + N/A (but you can use dictionaries for basic “switch” functionality) + </td> + </tr> + <tr> + <td class="scala-block"> + <code>val monthAsString = day 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"> + N/A + </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 nfe: FileNotFoundException => + <br>    println(fnf.getMessage) + <br>finally + <br>  println("Finally")</code> + </td> + </tr> + </tbody> +</table> + +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 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: + +<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> + +### Accessing list elements: + +<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> + +### Update list elements: + +<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> + +### Combine two lists: + +<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> + +### Iterate over a list: + +<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’s main sequence classes are `List`, `Vector`, and `ArrayBuffer`. +`List` and `Vector` are the main classes to use when you want an immutable sequence, and `ArrayBuffer` is the main class to use when you want a mutable sequence. +(A “buffer” in Scala is a sequence that can grow and shrink.) + +### Dictionary/Map + +The Python dictionary is like the _mutable_ Scala `Map` class. +However, the default Scala map is _immutable_, and has a number of transformation methods to let you easily create new maps. + +#### Dictionary/Map creation: + +<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> + +#### Accessing dictionary/map elements: + +<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> + +#### Dictionary/Map with a `for` loop: + +<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 + <br>  (key,value) <- myMap + <br>do + <br>  println(key) + <br>  println(value)</code> + </td> + </tr> + </tbody> +</table> + +Scala has other specialized `Map` classes for different needs. + +### Sets + +The Python set is similar to the _mutable_ Scala `Set` class. + +#### Set creation: + +<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> + +#### Duplicate elements: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>set = {1,2,1} + <br># set: {1,2}</code></td> + </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 has other specialized `Set` classes for different needs. + +### Tuples + +Python and Scala tuples are also similar. + +#### Tuple creation: + +<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> + +#### Accessing tuple elements: + +<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> + + + +## Methods on collections classes + +Python and Scala have several of the same common functional methods available to them: + +- `map` +- `filter` +- `reduce` + +If you’re used to using these methods with lambda expressions in Python, you’ll see that Scala has a similar approach with methods on its collections classes. +To demonstrate this functionality, here are two sample lists: + +```scala +numbers = (1,2,3) // python +val numbers = List(1,2,3) // scala +``` + +Those lists are used in the following table, that shows how to apply mapping and filtering algorithms to it. + +### Mapping with a comprehension: + +<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> + +### Filtering with a comprehension: + +<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)</code> + </td> + </tr> + </tbody> +</table> + +### Mapping & filtering with a comprehension: + +<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) + <br>          .map(_ * 10)</code> + </td> + </tr> + </tbody> +</table> + +### Mapping: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>def times_10(n): return n * 10 + <br>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> + +### Filtering: + +<table> + <tbody> + <tr> + <td class="python-block"> + <code>f = lambda x: x if x > 1 else 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 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 those method examples: + +- `c` refers to a collection +- `p` is a predicate +- `f` is a function, anonymous function, or method +- `n` refers to an integer value + +These are some of the filtering methods that are available: + +| Method | Description | +| -------------- | ------------- | +| `c1.diff(c2)` | Returns the difference of the elements in `c1` and `c2`. | +| `c.distinct` | Returns the unique elements in `c`. | +| `c.drop(n)` | Returns all elements in the collection except the first `n` elements. | +| `c.filter(p)` | Returns all elements from the collection for which the predicate is `true`. | +| `c.head` | Returns the first element of the collection. (Throws a `NoSuchElementException` if the collection is empty.) | +| `c.tail` | Returns all elements from the collection except the first element. (Throws a `UnsupportedOperationException` if the collection is empty.) | +| `c.take(n)` | Returns the first `n` elements of the collection `c`. | + +Here are a few transformer methods: + +| Method | Description | +| --------------- | ------------- | +| `c.flatten` | Converts a collection of collections (such as a list of lists) to a single collection (single list). | +| `c.flatMap(f)` | Returns a new collection by applying `f` to all elements of the collection `c` (like `map`), and then flattening the elements of the resulting collections. | +| `c.map(f)` | Creates a new collection by applying `f` to all elements of the collection `c`. | +| `c.reduce(f)` | Applies the “reduction” function `f` to successive elements in `c` to yield a single value. | +| `c.sortWith(f)` | Returns a version of `c` that’s sorted by the comparison function `f`. | + +Some common grouping methods: + +| Method | Description | +| ---------------- | ------------- | +| `c.groupBy(f)` | Partitions the collection into a `Map` of collections according to `f`. | +| `c.partition(p)` | Returns two collections according to the predicate `p`. | +| `c.span(p)` | Returns a collection of two collections, the first created by `c.takeWhile(p)`, and the second created by `c.dropWhile(p)`. | +| `c.splitAt(n)` | Returns a collection of two collections by splitting the collection `c` at element `n`. | + +Some informational and mathematical methods: + +| Method | Description | +| -------------- | ------------- | +| `c1.containsSlice(c2)` | Returns `true` if `c1` contains the sequence `c2`. | +| `c.count(p)` | Counts the number of elements in `c` where `p` is `true`. | +| `c.distinct` | Returns the unique elements in `c`. | +| `c.exists(p)` | Returns `true` if `p` is `true` for any element in the collection. | +| `c.find(p)` | Returns the first element that matches `p`. The element is returned as `Option[A]`. | +| `c.min` | Returns the smallest element from the collection. (Can throw _java.lang.UnsupportedOperationException_.) | +| `c.max` | Returns the largest element from the collection. (Can throw _java.lang.UnsupportedOperationException_.) | +|`c slice(from, to)` | Returns the interval of elements beginning at element `from`, and ending at element `to`. | +| `c.sum` | Returns the sum of all elements in the collection. (Requires an `Ordering` be defined for the elements in the collection.) | + +Here are a few examples that demonstrate how these methods work on a list: + +```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) +``` + +These methods show a common pattern in Scala: Functional methods that are available on objects. +None of these methods mutate the initial list `a`; instead, they all return the data shown after the comments. + +There are many more methods available, but hopefully these descriptions and examples give you a taste of the power that’s available in the pre-built collections methods. + + + +## Enums + +This section compares enums (enumerations) in Python and Scala 3. + +### Creating enums: + +<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> + +### Values and comparison: + +<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> + +### Parameterized enums: + +<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> + +### User-defined enum members: + +<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> + + + +## Concepts that are unique to Scala + +There are other concepts in Scala which currently don’t have equivalent functionality in Python. +Follow the links below for more details: + +- Most concepts related to [contextual abstractions][contextual], such as [extension methods][extension], [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 + + +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[type_classes]: {% link _overviews/scala3-book/types-type-classes.md %} +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[control_structures]: {% link _overviews/scala3-book/control-structures.md %} + +</div> + diff --git a/_overviews/scala3-book/scala-tools.md b/_overviews/scala3-book/scala-tools.md new file mode 100644 index 0000000000..ef4453170b --- /dev/null +++ b/_overviews/scala3-book/scala-tools.md @@ -0,0 +1,443 @@ +--- +title: Scala Tools +type: chapter +description: This chapter looks at two commonly-used Scala tools, sbt and ScalaTest. +num: 69 +previous-page: concurrency +next-page: interacting-with-java +--- + + +In this chapter you’ll see two tools that are commonly used in Scala projects: + +- The [sbt](https://www.scala-sbt.org) build tool +- [ScalaTest](https://www.scalatest.org), a source code testing framework + +We’ll start by showing how to use sbt to build your Scala projects, and then we’ll show how to use sbt and ScalaTest together to test your Scala projects. + +> If you want to learn about tools to help you migrate your Scala 2 code to Scala 3, see our [Scala 3 Migration Guide](https://scalacenter.github.io/scala-3-migration-guide/). + + + +## Building Scala projects with sbt + +You can use several different tools to build your Scala projects, including Ant, Maven, Gradle, Mill, and more. +But a tool named *sbt* was the first build tool that was specifically created for Scala, and these days it’s supported by [Lightbend](https://www.lightbend.com), the company that also maintains [Akka](https://akka.io), the [Play framework](https://www.playframework.com), the [Lagom framework](https://www.lagomframework.com), and more. + +> To install sbt, see [its download page](https://www.scala-sbt.org/download.html) or our [Getting Started][getting_started] page. + + + +### Creating a “Hello, world” project + +You can create an sbt “Hello, world” project in just a few steps. +First, create a directory to work in, and move into that directory: + +```bash +$ mkdir hello +$ cd hello +``` + +Then create a file named *build.sbt* that contains this line: + +```scala +scalaVersion := "{{ site.scala-3-version }}" +``` + +Now create a file named something like *Hello.scala* — the first part of the name doesn’t matter — with this line: + +```scala +@main def helloWorld = println("Hello, world") +``` + +That’s all you have to do. +Now run the project with this `sbt` command: + +```bash +$ sbt run +``` + +You should see output that looks like this, including the `"Hello, world"` from your program: + +```bash +$ sbt run +[info] welcome to sbt 1.4.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 +``` + +When you look at your directory, you’ll see that sbt has created two directories named *project* and *target*. +These are working directories that sbt uses. + +As you can see, creating and running a little Scala project with sbt takes just a few simple steps. + +{% comment %} +NOTE: This currently requires this line in a *project/plugins.sbt* file: +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "{{ site.scala-3-plugin-version }}") +{% endcomment %} + + +### Using sbt with larger projects + +For a little project, that’s all that sbt requires to run. +For larger projects that require many source code files, dependencies, or sbt plugins, you’ll want to create an organized directory structure. +The rest of this section demonstrates the structure that sbt uses. + + +### The sbt directory structure + +Like Maven, sbt uses a standard project directory structure. +A nice benefit of that is that once you’re comfortable with its structure, it makes it easy to work on other Scala/sbt projects. + +The first thing to know is that underneath the root directory of your project, sbt expects a directory structure that looks like this: + +```bash +build.sbt +project/ +src/ +-- main/ + |-- java/ + |-- resources/ + |-- scala/ +|-- test/ + |-- java/ + |-- resources/ + |-- scala/ +target/ +``` + +You can also add a *lib* directory under the root directory if you want to add unmanaged dependencies — JAR files — to your project. + +If you’re going to create a project that has Scala source code files and tests, but won’t be using any Java source code files, and doesn’t need any “resources” — such as embedded images, configuration files, etc. — this is all you really need under the *src* directory: + +```bash +src/ +-- main/ + |-- scala/ +|-- test/ + |-- scala/ +``` + + +### “Hello, world” with an sbt directory structure + +{% comment %} +TODO: 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 %} + +Creating this directory structure is simple. +There are tools to do this for you, but assuming that you’re using a Unix/Linux system, you can use these commands to create your first sbt project directory structure: + +```bash +$ mkdir HelloWorld +$ cd HelloWorld +$ mkdir -p src/{main,test}/scala +$ mkdir project target +``` + +When you run a `find .` command after running those commands, you should see this result: + +```bash +$ find . +. +./project +./src +./src/main +./src/main/scala +./src/test +./src/test/scala +./target +``` + +If you see that, you’re in great shape for the next step. + +> There are other ways to create the files and directories for an sbt project. +> One way is to use the `sbt new` command, [which is documented here on scala-sbt.org](https://www.scala-sbt.org/1.x/docs/Hello.html). +> That approach isn’t shown here because some of the files it creates are more complicated than necessary for an introduction like this. + + +### Creating a first build.sbt file + +At this point you only need two more things to run a “Hello, world” project: + +- A *build.sbt* file +- A *Hello.scala* file + +For a little project like this, the *build.sbt* file only needs a `scalaVersion` entry, but we’ll add three lines that you commonly see: + +```scala +name := "HelloWorld" +version := "0.1" +scalaVersion := "{{ site.scala-3-version }}" +``` + +Because sbt projects use a standard directory structure, sbt can find everything else it needs. + +Now you just need to add a little “Hello, world” program. + + +### A “Hello, world” program + +In large projects, all of your Scala source code files will go under the *src/main/scala* and *src/test/scala* directories, but for a little sample project like this, you can put your source code file in the root directory of your project. +Therefore, create a file named *HelloWorld.scala* in the root directory with these contents: + +```scala +@main def helloWorld = println("Hello, world") +``` + +That code defines a Scala 3 “main” method that prints the `"Hello, world"` when it’s run. + +Now, use the `sbt run` command to compile and run your project: + +```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 +``` + +The first time you run `sbt` it downloads everything it needs, and that can take a few moments to run, but after that it gets much faster. + +Also, once you get this first step working, you’ll find that it’s much faster to run sbt interactively. +To do that, first run the `sbt` command by itself: + +```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> _ +``` + +Then inside this sbt shell, execute its `run` command: + +```` +sbt:hello-world> run + +[info] running helloWorld +Hello, world +[success] Total time: 0 s +```` + +There, that’s much faster. + +If you type `help` at the sbt command prompt you’ll see a list of other commands you can run. +But for now, just type `exit` (or press `CTRL-D`) to leave the sbt shell. + + +### Other build tools for Scala + +While sbt is widely used, there are other tools you can use to build Scala projects: + +- [Ant](https://ant.apache.org/) +- [Gradle](https://gradle.org/) +- [Maven](https://maven.apache.org/) +- [Mill](https://www.lihaoyi.com/mill/) + +#### Coursier + +In a related note, [Coursier](https://get-coursier.io/docs/overview) is a “dependency resolver,” similar to Maven and Ivy in function. +It’s written from scratch in Scala, “embraces functional programming principles,” and downloads artifacts in parallel for rapid downloads. +sbt uses it to handle most dependency resolutions, and as a command-line tool, it can be used to easily install tools like sbt, Java, and Scala on your system, as shown in our [Getting Started][getting_started] page. + +This example from the `launch` web page shows that the `cs launch` command can be used to launch applications from dependencies: + +```scala +$ cs launch org.scalameta::scalafmt-cli:2.4.2 -- --help +scalafmt 2.4.2 +Usage: scalafmt [options] [<file>...] + + -h, --help prints this usage text + -v, --version print version + more ... +``` + +See Coursier’s [launch page](https://get-coursier.io/docs/cli-launch) for more details. + + + +## Using sbt with ScalaTest + +[ScalaTest](https://www.scalatest.org) is one of the main testing libraries for Scala projects, and in this section you’ll see how to create a Scala/sbt project that uses ScalaTest. + + +### Creating the project directory structure + +As with the previous lesson, create an sbt project directory structure for a project named *HelloScalaTest* with the following commands: + +```bash +$ mkdir HelloScalaTest +$ cd HelloScalaTest +$ mkdir -p src/{main,test}/scala +$ mkdir project target +``` + + +### Creating the build.sbt file + +Next, create a *build.sbt* file in the root directory of your project with these contents: + +```scala +name := "HelloScalaTest" +version := "0.1" +scalaVersion := "{{site.scala-3-version}}" + +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.3.0-SNAP3" % Test +) +``` + +The first three lines of this file are essentially the same as the first example. +The `libraryDependencies` lines tell sbt to include the dependencies (JAR files) that are needed to include ScalaTest. + +> 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](https://www.scalatest.org/install) page. + + +### Create a Scala source code file + +Next, create a Scala program that you can use to demonstrate ScalaTest. +First, create a directory under *src/main/scala* named *math*: + +```bash +$ mkdir src/main/scala/math + ---- +``` + +Then, inside that directory, create a file named *MathUtils.scala* with these contents: + +```scala +package math + +object MathUtils: + def double(i: Int) = i * 2 +``` + +That method provides a simple way to demonstrate 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 %} + + +### Your first ScalaTest tests + +ScalaTest is very flexible, and offers several different ways to write tests. +A simple way to get started is to write tests using the ScalaTest `AnyFunSuite`. +To get started, create a directory named *math* under the *src/test/scala* directory: + +```bash +$ mkdir src/test/scala/math + ---- +``` + +Next, create a file named *MathUtilsTests.scala* in that directory with the following contents: + +```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 +``` + +This code demonstrates the ScalaTest `AnyFunSuite` approach. +A few important points: + +- Your test 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 +- When you know you want to write a test, but you don’t want to write it right now, create the test as “pending,” with the syntax shown + +Using ScalaTest like this is similar to JUnit, so if you’re coming to Scala from Java, hopefully this looks similar. + +Now you can run these tests with the `sbt test` command. +Skipping the first few lines of output, the result looks like this: + +```` +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 +```` + +If everything works well, you’ll see output that looks like that. +Welcome to the world of testing Scala applications with sbt and ScalaTest. + + +### Support for many types of tests + +This example demonstrates a style of testing that’s similar to xUnit *Test-Driven Development* (TDD) style testing, with a few benefits of the *Behavior-Driven Development* (BDD) style. + +As mentioned, ScalaTest is flexible and you can also write tests using other styles, such as a style similar to Ruby’s RSpec. +You can also use mock objects, property-based testing, and use ScalaTest to test Scala.js code. + +See the User Guide on the [ScalaTest website](https://www.scalatest.org) for more details on the different testing styles that are available. + + + +## Where to go from here + +For more information about sbt and ScalaTest, see the following resources: + +- [The sbt documentation](https://www.scala-sbt.org/1.x/docs/) +- [The ScalaTest website](https://www.scalatest.org/) + + + +[getting_started]: {{ site.baseurl }}/scala3/getting-started.html diff --git a/_overviews/scala3-book/scala4x.css b/_overviews/scala3-book/scala4x.css new file mode 100644 index 0000000000..c7d34705bb --- /dev/null +++ b/_overviews/scala3-book/scala4x.css @@ -0,0 +1,59 @@ +<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 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, +.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/_overviews/scala3-book/taste-collections.md b/_overviews/scala3-book/taste-collections.md new file mode 100644 index 0000000000..a14be41361 --- /dev/null +++ b/_overviews/scala3-book/taste-collections.md @@ -0,0 +1,120 @@ +--- +title: Collections +type: section +description: This page provides a high-level overview of the main features of the Scala 3 programming language. +num: 13 +previous-page: taste-objects +next-page: taste-contextual-abstractions +--- + + + +Scala has a rich set of collections 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`: + +```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) +``` + + + +## `List` methods + +Once you have a populated list, the following examples show some of the methods you can call on it. +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: + +```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') +``` + +These examples show how the “fold” and “reduce” methods are used to sum the values in a sequence of integers: + +```scala +val firstTen = (1 to 10).toList // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +firstTen.reduce(_ + _) // 55 +firstTen.reduceLeft(_ + _) // 55 +firstTen.fold(100)(_ + _) // 155 (100 is a “seed” value) +firstTen.foldLeft(100)(_ + _) // 155 +``` + +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: + +```scala +case class Person(name: String) +``` + +This is how you create a tuple that contains an `Int`, a `String`, and a custom `Person` value: + +```scala +val t = (11, "eleven", Person("Eleven")) +``` + +Once you have a tuple, you can access its values by binding them to variables, or access them by number: + +```scala +t._1 // 11 +t._2 // "eleven" +t._3 // Person("Eleven") +``` + +You can also use this *extractor* approach to assign the tuple fields to variable names: + +```scala +val (num, str, person) = t + +// result: +// val num: Int = 11 +// val str: String = eleven +// val person: Person = Person(Eleven) +``` + +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://dotty.epfl.ch/api/index.html +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/taste-contextual-abstractions.md b/_overviews/scala3-book/taste-contextual-abstractions.md new file mode 100644 index 0000000000..0187c8bb05 --- /dev/null +++ b/_overviews/scala3-book/taste-contextual-abstractions.md @@ -0,0 +1,32 @@ +--- +title: Contextual Abstractions +type: section +description: This section provides an introduction to Contextual Abstractions in Scala 3. +num: 14 +previous-page: taste-collections +next-page: taste-toplevel-definitions +--- + + +{% comment %} +TODO: Now that this is a separate section, it needs a little more content. +{% endcomment %} + + +Under certain circumstances, the Scala compiler can “write” some parts of your programs. +For instance, consider a program that sorts a list of addresses by two criteria, city name and then street name: + +```scala +val addresses: List[Address] = ... +addresses.sortBy(address => (address.city, address.street)) +``` + +The sorting algorithm needs to compare addresses by first comparing their city names, and then also their street names when the city names are the same. +However, with the use of contextual abstraction, you don’t need to manually define this ordering relation, because the compiler is able to summon it automatically based on an existing ordering relation for comparing string values. + +For more details, see the [Contextual Abstractions chapter][contextual] of this book, and also in 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 new file mode 100644 index 0000000000..23d4435cbd --- /dev/null +++ b/_overviews/scala3-book/taste-control-structures.md @@ -0,0 +1,268 @@ +--- +title: Control Structures +type: section +description: This section demonstrates Scala 3 control structures. +num: 8 +previous-page: taste-vars-data-types +next-page: taste-modeling +--- + + +Scala has the control structures you find in other programming languages, and also has powerful `for` expressions and `match` expressions: + +- `if`/`else` +- `for` loops and expressions +- `match` expressions +- `while` loops +- `try`/`catch` + +These structures are demonstrated in the following examples. + + + +## `if`/`else` + +Scala’s `if`/`else` control structure looks similar to other languages: + +```scala +if x < 0 then + println("negative") +else if x == 0 + println("zero") +else + println("positive") +``` + +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: + +```scala +val x = if a < b then a else b +``` + +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. + + + +## `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`: + +```scala +val ints = List(1, 2, 3, 4, 5) + +for i <- ints do 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 old syntax for this control structure was: + +```scala +for (i <- ints) println(i) +``` + +### Guards + +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`: + +```scala +for + i <- ints + if i > 2 +do + println(i) +``` + +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`: + +```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" +``` + + +### `for` expressions + +The `for` keyword has even more power: When you use the `yield` keyword instead of `do`, you create `for` _expressions_ which are used to calculate and yield results. + +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: + +```` +scala> val doubles = for i <- nums yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +Scala’s control structure syntax is flexible, and that `for` expression can be written in several other ways, depending on your preference: + +```scala +val doubles = for i <- nums yield i * 2 // style shown above +val doubles = for (i <- nums) yield i * 2 +val doubles = for (i <- nums) yield (i * 2) +val doubles = for { i <- nums } yield (i * 2) +``` + +This example shows how to capitalize the first character in each string in the list: + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for name <- names yield name.capitalize +``` + +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`: + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = for + f <- fruits + if f.length > 4 +yield + // you can use multiple lines + // of code here + f.length + +// result: 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). + + + +## `match` expressions + +Scala has a `match` expression, which in its most basic use is like a Java `switch` statement: + +```scala +val i = 1 + +// later in the code ... +i match + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +``` + +However, `match` really is an expression, meaning that it returns a result based on the pattern match, which you can bind to a variable: + +```scala +val result = i match + case 1 => "one" + case 2 => "two" + case _ => "other" +``` + +`match` isn’t limited to working with just integer values, it can be used with any data type: + +```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!") +``` +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: + +```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 +``` + +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: + +```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.") +``` + + + +## `while` loops + +Scala also has a `while` loop construct. +It’s one-line syntax looks like this: + +```scala +while x >= 0 do x = f(x) +``` + +Again, Scala’s control structure syntax is flexible, and you can write this code in different ways depending on your preferences: + +```scala +while (x >= 0) do x = f(x) +while (x >= 0) { x = f(x) } +``` + +The `while` loop multiline syntax looks like this: + +```scala +var x = 1 + +// without parentheses +while + x < 3 +do + println(x) + x += 1 + +// with parentheses +while (x < 3) + println(x) + x += 1 +``` + + + +## 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 new file mode 100644 index 0000000000..cae6249f67 --- /dev/null +++ b/_overviews/scala3-book/taste-functions.md @@ -0,0 +1,64 @@ +--- +title: First-Class Functions +type: section +description: This page provides an introduction to functions in Scala 3. +num: 11 +previous-page: taste-methods +next-page: taste-objects +--- + + + +Scala has most features you’d expect in a functional programming language, including: + +- Lambdas +- Higher-order functions (HOFs) +- Higher-order functions in the standard library + +Lambdas, also known as _anonymous functions_, are a big part of keeping your code concise but readable. +These two examples — which show how to call higher-order functions (HOFs) on a Scala `List` — are equivalent, and show how to multiply each number in a list by `2` by passing a lambda into the `map` method: + +```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) +``` + +Those examples are also equivalent to the following code, which uses a `double` method inside of `map` instead of a lambda: + +```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) +``` + +> 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. +As a result, it’s also common to chain them together in a “fluent” style to solve problems. + +For instance, this example shows how to filter a collection twice, and then multiply each element in the remaining collection: + +```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) +``` + +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 new file mode 100644 index 0000000000..f5c8e55519 --- /dev/null +++ b/_overviews/scala3-book/taste-hello-world.md @@ -0,0 +1,50 @@ +--- +title: Hello, World! +type: section +description: This section demonstrates a Scala 3 'Hello, World!' example. +num: 5 +previous-page: taste-intro +next-page: taste-repl +--- + + +A Scala 3 “Hello, world!” example goes as follows. +First, put this code in a file named _Hello.scala_: + +```scala +@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. + +Next, compile the code with `scalac`: + +```bash +$ scalac Hello.scala +``` + +If you’re coming to Scala from Java, `scalac` is just like `javac`, so that command creates several files: + +```bash +$ ls -1 +Hello$package$.class +Hello$package.class +Hello$package.tasty +Hello.scala +hello.class +hello.tasty +``` + +Like Java, the _.class_ files are bytecode files, and they’re ready to run in the JVM. + +Now you can run the `hello` method with the `scala` command: + +```bash +$ scala hello +Hello, world! +``` + +Assuming that worked, congratulations, you just compiled and ran your first Scala application. + diff --git a/_overviews/scala3-book/taste-intro.md b/_overviews/scala3-book/taste-intro.md new file mode 100644 index 0000000000..044bf7fa93 --- /dev/null +++ b/_overviews/scala3-book/taste-intro.md @@ -0,0 +1,17 @@ +--- +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. +num: 4 +previous-page: why-scala-3 +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. + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/taste-methods.md b/_overviews/scala3-book/taste-methods.md new file mode 100644 index 0000000000..719ec6bba2 --- /dev/null +++ b/_overviews/scala3-book/taste-methods.md @@ -0,0 +1,134 @@ +--- +title: Methods +type: section +description: This section provides an introduction to defining and using methods in Scala 3. +num: 10 +previous-page: taste-modeling +next-page: taste-functions +--- + + +## Scala methods + +Scala classes, case classes, traits, enums, and objects can all contain methods. +When a method isn’t declared to use a generic type or accept `using` parameters, its general syntax looks like this: + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +``` + +Here are a few examples of that syntax: + +```scala +def sum(a: Int, b: Int): Int = a + b +def concatenate(s1: String, s2: String): String = s1 + s2 +``` + +You don’t have to declare a method’s return type, so you can write those methods like this, if you prefer: + +```scala +def sum(a: Int, b: Int) = a + b +def concatenate(s1: String, s2: String) = s1 + s2 +``` + +This is how you call those methods: + +```scala +val x = sum(1, 2) +val y = concatenate("foo", "bar") +``` + +Here’s an example of a multiline method: + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +``` + +Method parameters can also have default values. +In this example, if the `timeout` parameter isn’t specified, it defaults to `5000`: + +```scala +def makeConnection(url: String, timeout: Int = 5000): Unit = + println(s"url=$url, timeout=$timeout") +``` + +Because a default `timeout` value is supplied in the method declaration, the method can be called in these two ways: + +```scala +makeConnection("https://localhost") // url=http://localhost, timeout=5000 +makeConnection("https://localhost", 2500) // url=http://localhost, timeout=2500 +``` + +Scala also supports the use of _named parameters_ when calling a method, so you can also call that method like this, if you prefer: + +```scala +makeConnection( + url = "https://localhost", + timeout = 2500 +) +``` + +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: + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = 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" +``` + +The `extension` keyword declares that you’re about to define one or more extension methods on the type that’s put in parentheses. +As shown with this `String` example, the parameter `s` can then be used in the body of your extension methods. + +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: + +```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 +``` + + + +## See also + +Methods are covered in detail in the [Data 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 new file mode 100644 index 0000000000..7f421c7ff3 --- /dev/null +++ b/_overviews/scala3-book/taste-modeling.md @@ -0,0 +1,229 @@ +--- +title: Domain Modeling +type: section +description: This section provides an introduction to data modeling in Scala 3. +num: 9 +previous-page: taste-control-structures +next-page: taste-methods +--- + + +{% comment %} +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_. + +{% 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 traits can be used as simple interfaces, but they can also contain abstract and concrete methods and fields, and they can have parameters, just like classes. +They provide a great way for you to organize behaviors into small, modular units. +Later, when you want to create concrete implementations of attributes and behaviors, classes and objects can extend traits, mixing in as many traits as needed to achieve the desired behavior. + +{% comment %} +TODO: Need a better example. This one shows behaviors, not data. +{% endcomment %} + +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: + +```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") +``` + +Given those traits, here’s a `Dog` class that extends all of those traits while providing a behavior for the abstract `speak` method: + +```scala +class Dog(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Woof!" +``` + +Notice how the class extends the traits with the `extends` and `with` keywords. + +Similarly, here’s a `Cat` class that implements those same traits while also overriding two of the concrete methods it inherits: + +```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") +``` + +These examples show how those classes are used: + +```scala +val d = Dog("Rover") +d.speak() // prints "Woof!" + +val c = Cat("Morris") +c.speak() // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +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 [Data 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: + +```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" +``` + +Notice that the class declaration creates a constructor: + +```scala +// this code uses that constructor +val p = Person("John", "Stephens") +``` + +Constructors and other class-related topics are covered in the [Data Modeling][data-1] chapter. + + +## FP Domain Modeling + +{% 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 %} + +When writing code in an FP style, you’ll use these constructs: + +- Enums to define ADTs +- Case classes +- Traits + + +### Enums + +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: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +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: + +```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") +``` + +Here’s another example of how to create and use an ADT with Scala: + +```scala +enum Nat: + case Zero + case Succ(pred: Nat) +``` + +Enums are covered in detail in the [Data Modeling][data-1] section of this book, and in the [Reference documentation]({{ site.scala3ref }}/enums/enums.html). + + +### Case classes + +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: + +- Case class constructor parameters are public `val` fields by default, so the fields are immutable, and accessor methods are generated for each parameter. +- An `unapply` method is generated, which lets you use case classes in more ways in `match` expressions. +- A `copy` method is generated in the class. + This provides a way to create updated copies of the object without changing the original object. +- `equals` and `hashCode` methods are generated. +- 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 %} + +You _can_ manually add all of those methods to a class yourself, but since those features are so commonly used in functional programming, using a `case` class is much more convenient. + +This code demonstrates several `case` class features: + +```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) +``` + +See the [Data 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 new file mode 100644 index 0000000000..ee07fc3b70 --- /dev/null +++ b/_overviews/scala3-book/taste-objects.md @@ -0,0 +1,96 @@ +--- +title: Objects +type: section +description: This section provides an introduction to the use of objects in Scala 3. +num: 12 +previous-page: taste-functions +next-page: taste-collections +--- + + +In Scala, the `object` keyword creates a Singleton object. +Put another way, an object defines a class that has exactly one instance. + +Objects have several uses: + +- They are used to create collections of utility methods. +- A _companion object_ is an object that has the same name as the class it shares a file with. + 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: + +```scala +object StringUtils: + def isNullOrEmpty(s: String): Boolean = + if (s==null || s.trim.equals("")) true else false + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +``` + +Because `StringUtils` is a singleton, its methods can be called directly on the object: + +```scala +val x = StringUtils.isNullOrEmpty("") // true +val x = StringUtils.isNullOrEmpty("a") // false +``` + + +## Companion objects + +A companion class or object can access the private members of its companion. +Use a companion object for methods and values which aren’t specific to instances of the companion class. + +This example demonstrates how the `area` method in the companion class can access the private `calculateArea` method in its companion object: + +```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 +``` + + +## 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`: + +```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 +``` + +{% 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/_overviews/scala3-book/taste-repl.md b/_overviews/scala3-book/taste-repl.md new file mode 100644 index 0000000000..130168c5cb --- /dev/null +++ b/_overviews/scala3-book/taste-repl.md @@ -0,0 +1,57 @@ +--- +title: The REPL +type: section +description: This section provides an introduction to the Scala REPL. +num: 6 +previous-page: taste-hello-world +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` command at your operating system command line, where you’ll see a “welcome” prompt like this: + +```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. + +scala> _ +``` + +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: + +```` +scala> 1 + 1 +val res0: Int = 2 + +scala> 2 + 2 +val res1: Int = 4 +```` + +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: + +```` +scala> val x = res0 * 10 +val x: Int = 20 +```` + +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: + +```` +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 +```` + +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 new file mode 100644 index 0000000000..a80297b2d9 --- /dev/null +++ b/_overviews/scala3-book/taste-summary.md @@ -0,0 +1,31 @@ +--- +title: Summary +type: section +description: This page provides a summary of the previous 'Taste of Scala' sections. +num: 16 +previous-page: taste-toplevel-definitions +next-page: first-look-at-types +--- + + +In the previous sections you saw: + +- How to use the Scala REPL +- How to create variables with `val` and `var` +- Some common data types +- Control structures +- How to model the real world using OOP and FP styles +- How to create and use methods +- How to use lambdas (anonymous functions) and higher-order functions +- 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). + +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. + + + +[reference]: {{ site.scala3ref }}/overview.html +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_overviews/scala3-book/taste-toplevel-definitions.md b/_overviews/scala3-book/taste-toplevel-definitions.md new file mode 100644 index 0000000000..9c5981ccda --- /dev/null +++ b/_overviews/scala3-book/taste-toplevel-definitions.md @@ -0,0 +1,65 @@ +--- +title: Toplevel Definitions +type: section +description: This page provides an introduction to top-level definitions in Scala 3 +num: 15 +previous-page: taste-contextual-abstractions +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: + +```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) +``` + +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: + +```scala +package foo { + def double(i: Int) = i * 2 +} + +package foo { + package bar { + @main def fooBarMain = + println(s"${double(1)}") // this works + } +} +``` + +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 new file mode 100644 index 0000000000..13bbd939a1 --- /dev/null +++ b/_overviews/scala3-book/taste-vars-data-types.md @@ -0,0 +1,207 @@ +--- +title: Variables and Data Types +type: section +description: This section demonstrates val and var variables, and some common Scala data types. +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: + +<table> + <thead> + <tr> + <th>Variable Type</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td valign="top"><code>val</code></td> + <td valign="top">Creates an <em>immutable</em> variable — like <code>final</code> in Java. You should always create a variable with <code>val</code>, unless there’s a reason you need a mutable variable.</td> + </tr> + <tr> + <td><code>var</code></td> + <td>Creates a <em>mutable</em> variable, and should only be used when a variable’s contents will change over time.</td> + </tr> + </tbody> +</table> + +These examples show how to create `val` and `var` variables: + +```scala +// immutable +val a = 0 + +// mutable +var b = 1 +``` + +In an application, a `val` can’t be reassigned. +You’ll cause a compiler error if you try to reassign one: + +```scala +val msg = "Hello, world" +msg = "Aloha" // "reassignment to val" error; this won’t compile +``` + +Conversely, a `var` can be reassigned: + +```scala +var msg = "Hello, world" +msg = "Aloha" // this compiles because a var can be reassigned +``` + + + +## Declaring variable types + +When you create a variable you can explicitly declare its type, or let the compiler infer the type: + +```scala +val x: Int = 1 // explicit +val x = 1 // implicit; the compiler infers the type +``` + +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: + +```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) +``` + +You can always explicitly declare a variable’s type if you prefer, but in simple assignments like these it isn’t necessary: + +```scala +val x: Int = 1 +val s: String = "a string" +val p: Person = Person("Richard") +``` + +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 %} + +## Built-in data types + +Scala comes with the standard numeric data types you’d expect, and they’re all full-blown instances of classes. +In Scala, everything is an object. + +These examples show how to declare variables of the numeric types: + +```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 +``` + +Because `Int` and `Double` are the default numeric types, you typically create them without explicitly declaring the data type: + +```scala +val i = 123 // defaults to Int +val j = 1.0 // defaults to Double +``` + +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: + +```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 +``` + +When you need really large numbers, use the `BigInt` and `BigDecimal` types: + +```scala +var a = BigInt(1_234_567_890_987_654_321L) +var b = BigDecimal(123_456.789) +``` + +Where `Double` and `Float` are approximate decimal numbers, `BigDecimal` is used for precise arithmetic. + +Scala also has `String` and `Char` data types: + +```scala +val name = "Bill" // String +val c = 'a' // Char +``` + + +### 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 +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +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 embed arbitrary expressions inside a string, enclose them in curly braces: + +``` scala +println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" + +val x = -1 +println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +``` + +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: + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +> 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/types-adts-gadts.md b/_overviews/scala3-book/types-adts-gadts.md new file mode 100644 index 0000000000..124870b36d --- /dev/null +++ b/_overviews/scala3-book/types-adts-gadts.md @@ -0,0 +1,192 @@ +--- +title: Algebraic Data Types +type: section +description: This section introduces and demonstrates algebraic data types (ADTs) in Scala 3. +num: 52 +previous-page: types-union +next-page: types-variance +--- + + +Algebraic Data Types (ADTs) can be created with the `enum` construct, so we’ll briefly review enumerations before looking at ADTs. + +## Enumerations + +An _enumeration_ is used to define a type consisting of a set of named values: + +```scala +enum Color: + case Red, Green, Blue +``` +which can be seen as a shorthand for: +```scala +enum Color: + case Red extends Color + case Green extends Color + case Blue extends Color +``` +#### Parameters +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) +``` +This way, each of the different variants has a value member `rgb` which is assigned the corresponding value: +```scala +println(Color.Green.rgb) // prints 65280 +``` + +#### Custom Definitions +Enums can also have custom definitions: + +```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 ... +``` + +Like classes and `case` classes, you can also define a 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) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") +``` + +## Algebraic Datatypes (ADTs) + +The `enum` concept is general enough to also support _algebraic data types_ (ADTs) and their generalized version (GADTs). +Here’s an example that shows how an `Option` type can be represented as an ADT: + +```scala +enum Option[+T]: + case Some(x: T) + case None +``` + +This example creates an `Option` enum with a covariant type parameter `T` consisting of two cases, `Some` and `None`. +`Some` is _parameterized_ with a value parameter `x`; this is a shorthand for writing a `case` class that extends `Option`. +Since `None` is not parameterized, it’s treated as a normal `enum` value. + +The `extends` clauses that were omitted in the previous example can also be given explicitly: + +```scala +enum Option[+T]: + case Some(x: T) extends Option[T] + case None extends Option[Nothing] +``` + +As with normal `enum` values, the cases of an `enum` are defined in the `enum`s companion object, so they’re referred to as `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 +``` + +As with other enumeration uses, ADTs can define additional methods. +For instance, here’s `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 Some(_) => true + +object Option: + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) +``` + +Enumerations and ADTs share the same syntactic construct, so they can +be seen simply as two ends of a spectrum, and it’s 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) +``` + +#### Recursive Enumerations +So far all the enumerations that we defined consisted of different variants of values or case classes. +Enumerations can also be recursive, as illustrated in the below example of encoding natural numbers: +```scala +enum Nat: + case Zero + case Succ(n: Nat) +``` +For example the value `Succ(Succ(Zero))` represents the number `2` in an unary encoding. +Lists can be defined in a very similar way: + +```scala +enum List[+A]: + case Nil + case Cons(head: A, tail: List[A]) +``` + +## Generalized Algebraic Datatypes (GADTs) +The above notation for enumerations is very concise and serves as the perfect starting point for modeling your data types. +Since we can always be more explicit, it is also possible to express types that are much more powerful: generalized algebraic datatypes (GADTs). + +Here is an example of a GADT where the type parameter (`T`) specifies the contents stored in the box: +```scala +enum Box[T](contents: T): + case IntBox(n: Int) extends Box[Int](n) + case BoolBox(b: Boolean) extends Box[Boolean](b) +``` +Pattern matching on the particular constructor (`IntBox` or `BoolBox`) recovers the type information: +```scala +def extract[T](b: Box[T]): T = b match + case IntBox(n) => n + 1 + case BoolBox(b) => !b +``` +It is only safe to return an `Int` in the first case, since we know from pattern matching that the input was an `IntBox`. + + +## Desugaring Enumerations +_Conceptually_, enums can be thought of as defining a sealed class together with an companion object. +Let’s look at the desugaring of our `Color` enum above: +```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) +``` +Note that the above desugaring is simplified and we purposefully leave out [some details][desugar-enums]. + +While enums could be manually encoded using other constructs, using enumerations is more concise and also comes with a few additional utilities (such as the `fromOrdinal` method). + + +[desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html diff --git a/_overviews/scala3-book/types-dependent-function.md b/_overviews/scala3-book/types-dependent-function.md new file mode 100644 index 0000000000..207fddbff5 --- /dev/null +++ b/_overviews/scala3-book/types-dependent-function.md @@ -0,0 +1,146 @@ +--- +title: Dependent Function Types +type: section +description: This section introduces and demonstrates dependent function types in Scala 3. +num: 56 +previous-page: types-structural +next-page: types-others +--- + +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. + +## Dependent Method Types +Let's consider the following example of a heterogenous database that can store values of different types. +The key contains the information about what's the type of the corresponding value: + +```scala +trait Key { type Value } + +trait DB { + def get(k: Key): Option[k.Value] // a dependent method +} +``` +Given a key, the method `get` let's us access the map and potentially returns the stored value of type `k.Value`. +We can read this _path-dependent type_ as: "depending on the concrete type of the argument `k`, we return a matching value". + +For example, we could have the following keys: +```scala +object Name extends Key { type Value = String } +object Age extends Key { type Value = Int } +``` +The following calls to method `get` would now type check: +```scala +val db: DB = ... +val res1: Option[String] = db.get(Name) +val res2: Option[String] = db.get(Age) +``` +Calling the method `db.get(Name)` returns a value of type `Option[String]`, while calling `db.get(Age)` returns a value of type `Option[Int]`. +The return type _depends_ on the concrete type of the argument passed to `get` -- hence the name _dependent type_. + +## Dependent Function Types +As seen above, Scala 2 already had support for dependent method types. +However, creating values of type `DB` is quite cumbersome: +```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 +}) +``` +We manually need to create an anonymous inner class of `DB`, implementing the `get` method. +For code that relies on creating many different instances of `DB` this is very tedious. + +The trait `DB` only has a single abstract method `get`. +Wouldn't it be nice, if we could use lambda syntax instead? +```scala +user { k => + ... // implementation of DB +} +``` +In fact, this is now possible in Scala 3! We can define `DB` as a _dependent function type_: +```scala +type DB = (k: Key) => Option[k.Value] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// A dependent function type +``` +Given this definition of `DB` the above call to `user` type checks, as is. + +You can read more about the interals of dependent function types in the [reference documentation][ref]. + +## Case Study: Numerical Expressions +Let us assume we want to define a module that abstracts over the internal represention of numbers. +This can be useful, for instance, to implement libraries for automatic derivation. + +We start by defining our module for numbers: +```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 +``` +> We omit the concrete implementation of `Nums`, but as an exercise you could implement `Nums` by assigning `type Num = Double` and implement methods accordingly. + +A program that uses our number abstraction now has the following type: + +```scala +type Prog = (n: Nums) => n.Num => n.Num + +val ex: Prog = nums => x => nums.add(nums.lit(0.8), x) +``` +The type of a function that computes the derivative of programs like `ex` is: +```scala +def derivative(input: Prog): Double +``` +Given the facility of dependent function types, calling this function with different programs is very convenient: +```scala +derivative { nums => x => x } +derivative { nums => x => nums.add(nums.lit(0.8), x) } +// ... +``` + +To recall, the same program in the encoding above would be: +```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) +}) +// ... +``` + +#### Combination with Context Functions +The combination of extension methods, [context functions][ctx-fun], and dependent functions provides a powerful tool for library designers. +For instance, we can refine our library from above as follows +```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? +derive { x => const(1.0) + x } +derive { 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/_overviews/scala3-book/types-generics.md b/_overviews/scala3-book/types-generics.md new file mode 100644 index 0000000000..410a71045c --- /dev/null +++ b/_overviews/scala3-book/types-generics.md @@ -0,0 +1,47 @@ +--- +title: Generics +type: section +description: This section introduces and demonstrates generics in Scala 3. +num: 49 +previous-page: types-inferred +next-page: types-intersection +--- + + +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: + +```scala +// here we delare 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 = x :: elements } + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` + +This implementation of a `Stack` class takes any type 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]`: + +``` +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 +``` + +> See the [Variance section][variance] for details on how to express variance with generic types. + + +[variance]: {% link _overviews/scala3-book/types-variance.md %} diff --git a/_overviews/scala3-book/types-inferred.md b/_overviews/scala3-book/types-inferred.md new file mode 100644 index 0000000000..fab485fecc --- /dev/null +++ b/_overviews/scala3-book/types-inferred.md @@ -0,0 +1,40 @@ +--- +title: Inferred Types +type: section +description: This section introduces and demonstrates inferred types in Scala 3 +num: 48 +previous-page: types-introduction +next-page: types-generics +--- + + +As with other statically typed programming languages, in Scala you can _declare_ a type when creating a new variable: + +```scala +val x: Int = 1 +val x: Double = 1 +``` + +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: + +```scala +val a = 1 +val b = List(1, 2, 3) +val m = Map(1 -> "one", 2 -> "two") +``` + +When you do this, Scala _infers_ the types, as shown in the following REPL interaction: + +```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) +``` + +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 new file mode 100644 index 0000000000..8b0c96bb5a --- /dev/null +++ b/_overviews/scala3-book/types-intersection.md @@ -0,0 +1,41 @@ +--- +title: Intersection Types +type: section +description: This section introduces and demonstrates intersection types in Scala 3. +num: 50 +previous-page: types-generics +next-page: types-union +--- + + +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]`: + +```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") +``` + +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`. +Therefore, as shown, `Resettable & Growable[String]` has member methods `reset` and `add`. + +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: +```scala +trait Both[A] extends Resettable, Growable[A] +def f(x: Both): Unit +``` +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`. + +> 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 new file mode 100644 index 0000000000..97c369fe47 --- /dev/null +++ b/_overviews/scala3-book/types-introduction.md @@ -0,0 +1,49 @@ +--- +title: Types and the Type System +type: chapter +description: This chapter provides an introduction to Scala 3 types and the type system. +num: 47 +previous-page: fp-summary +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: + +```scala +val a = 1 +val b = 2.0 +val c = "Hi!" +``` + +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: + +```scala +def isTruthy(a: Boolean | Int | String): Boolean = ??? +def dogCatOrWhatever(): Dog | Plant | Car | Sun = ??? +``` + +As the example suggests, when using union types, thetypes don’t have to share a common hierarchy, and you can still accept them as arguments or return them from a method. + +If you’re an application developer, you’ll use features like type inference every day and generics every week. +When you read the Scaladoc for classes and methods, you’ll also need to have some understanding of _variance_. +Hopefully you’ll see that using types can be relatively simple and also offers a lot of expressive power, flexibility, and control for library developers. + + +## Benefits of types + +Statically-typed programming languages offer a number of benefits, including: + +- Helping to provide strong IDE support +- Eliminating many classes of potential errors at compile time +- Assisting in refactoring +- Providing strong documentation that cannot be outdated since it is type checked + + +## Introducing features of Scala’s type system + +Given that brief introduction, the following sections provide an overview of the features of Scala’s type system. + +[union-types]: {% link _overviews/scala3-book/types-union.md %} diff --git a/_overviews/scala3-book/types-opaque-types.md b/_overviews/scala3-book/types-opaque-types.md new file mode 100644 index 0000000000..bb641caac7 --- /dev/null +++ b/_overviews/scala3-book/types-opaque-types.md @@ -0,0 +1,136 @@ +--- +title: Opaque Types +type: section +description: This section introduces and demonstrates opaque types in Scala 3. +num: 54 +previous-page: types-variance +next-page: types-structural +--- + +Scala 3 _Opaque type aliases_ provide type abstractions without any **overhead**. + +## Abstraction Overhead + +Let us assume we want to define a module that offers arithmetic on numbers, which are represented by their logarithm. +This can be useful to improve precision when the numerical values involved tend to be very large, or close to zero. + +Since it is important to distinguish “regular” double values from numbers stored as their logarithm, we introduce a class `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)) +``` +The apply method on the companion object lets us create values of type `Logarithm` which we can use as follows: +```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... +``` +While the class `Logarithm` offers a nice abstraction for `Double` values that are stored in this particular logarithmic form, it imposes severe performance overhead: For every single mathematical operation, we need to extract the underlying value and then wrap it again in a new instance of `Logarithm`. + + +## Preventing the Overhead +Let us consider another approach to implement the same library. +This time instead of defining `Logarithm` as a class, we define it using a _type alias_. +First, we define an abstract interface of our module: + +```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) +``` +Now, let us implement this abstract interface by saying type `Logarithm` is equal to `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) +``` +Within the implementation of `LogarithmsImpl`, the equation `Logarithm = Double` allows us to implement the various methods. +And all of this without any performance overhead of boxing the double values. + +#### Leaky Abstractions +However, this abstraction is slightly leaky. +We have to make sure to _only_ ever program against the abstract interface `Logarithms` and never directly use `LogarithmsImpl`. +Directly using `LogarithmsImpl` would make the equality `Logarithm = Double` visible for the user, who might accidentally use a `Double` where a logarithmic double is expected. +For example: + +```scala +import LogarithmsImpl._ +val l: Logarithm = make(1.0) +val d: Double = l // type checks AND leaks the equality! +``` + +Having to separate the module into an abstract interface and implementation can be useful, but is also a lot of effort, just to hide the implementation detail of `Logarithm`. +Programming against the abstract module `Logarithms` can be very tedious and often requires the use of advanced features like path-dependent types, as in the following example: + +```scala +def someComputation(L: Logarithms)(init: L.Logarithm): L.Logarithm = ... +``` + +## Opaque Types +Instead of manually splitting our `Logarithms` component into an abstract part and into a concrete implementation, we can simply use opaque types in Scala 3 to achieve the same effect: + +```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 +``` +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 `Logarithms`. +The type equality `Logarithm = Double` can be used to implement the methods (like `*` and `toDouble`). + +However, outside of the module the type `Logarithm` is completely encapsulated, or “opaque.” To users of `Logarithm` it is not possible to discover that `Logarithm` is actually implemented as a `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 +``` +### Summary of Opaque Types +Opaque types offer a sound abstraction over implementation details, without imposing performance overhead. +As illustrated above, opaque types are convenient to use, and integrate very well with the [Extension Methods][extension] feature. + + +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} diff --git a/_overviews/scala3-book/types-others.md b/_overviews/scala3-book/types-others.md new file mode 100644 index 0000000000..cb87cccf2e --- /dev/null +++ b/_overviews/scala3-book/types-others.md @@ -0,0 +1,26 @@ +--- +title: Other Types +type: section +description: This section mentions other advanced types in Scala 3. +num: 57 +previous-page: types-dependent-function +next-page: ca-contextual-abstractions-intro +--- + + +Scala has several other advanced types that are not shown in this book, including: + +- Type lambdas +- Match types +- Existential types +- Higher-kinded types +- Singleton types +- Refinement types +- Kind polymorphism + +For more details on these types, see the [Reference documentation][reference]. + + + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_overviews/scala3-book/types-structural.md b/_overviews/scala3-book/types-structural.md new file mode 100644 index 0000000000..cf89aa6c8c --- /dev/null +++ b/_overviews/scala3-book/types-structural.md @@ -0,0 +1,102 @@ +--- +title: Structural Types +type: section +description: This section introduces and demonstrates structural types in Scala 3. +num: 55 +previous-page: types-opaque-types +next-page: types-dependent-function +--- + +{% comment %} +NOTE: It would be nice to simplify this more. +{% endcomment %} + +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`. + +Achieving the same experience in a 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 column 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 you’d 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 `Person` type adds a _refinement_ to its parent type `Record` that defines `name` and `age` fields. +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 you create a `Person` as a `Record` you 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] +``` + +## A second example + +To reinforce what you just saw, here’s another structural type named `Book` that represents a book that you might read from a database: + +```scala +type Book = Record { + val title: String + val author: String + val year: Int + val rating: Double +} +``` + +As with `Person`, this is how you create a `Book` instance: + +```scala +val book = Record( + "title" -> "The Catcher in the Rye", + "author" -> "J. D. Salinger", + "year" -> 1951, + "rating" -> 4.5 +).asInstanceOf[Book] +``` + +## Selectable class + +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)` translates to: + +```scala +a.applyDynamic("f")(b, c) +``` + diff --git a/_overviews/scala3-book/types-type-classes.md b/_overviews/scala3-book/types-type-classes.md new file mode 100644 index 0000000000..f09222d59d --- /dev/null +++ b/_overviews/scala3-book/types-type-classes.md @@ -0,0 +1,52 @@ +--- +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 `A` we can show 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://ropas.snu.ac.kr/~bruno/papers/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 new file mode 100644 index 0000000000..5f6a918e16 --- /dev/null +++ b/_overviews/scala3-book/types-union.md @@ -0,0 +1,91 @@ +--- +title: Union Types +type: section +description: This section introduces and demonstrates union types in Scala 3. +num: 51 +previous-page: types-intersection +next-page: types-adts-gadts +--- + +Used on types, the `|` operator creates a so-called _union type_. +The type `A | B` represents values that are **either** of the type `A` **or** of the type `B`. + +In the following example, the `help` method accepts a parameter named `id` of the union type `Username | Password`, that can be either a `Username` or a `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 ... +``` +We implement the method `help` by distinguishing between the two alternatives using pattern matching. + +This code is a flexible and type-safe solution. +If you attempt to pass in a type other than a `Username` or `Password`, the compiler flags it as an error: + +```scala +help("hi") // error: Found: ("hi" : String) + // Required: Username | Password +``` + +You’ll also get an error if you attempt to add a `case` to the `match` expression that doesn’t match the `Username` or `Password` types: + +```scala +case 1.0 = > ??? // ERROR: this line won’t compile +``` + +### Alternative to Union Types +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: + +```scala +trait UsernameOrPassword +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. + +#### Tagged Unions +Another alternative is to define a separate enumeration type like: + +```scala +enum UsernameOrPassword: + case IsUsername(u: Username) + case IsPassword(p: Password) +``` +The enumeration `UsernameOrPassword` represents a _tagged_ union of `Username` and `Password`. +However, this way of modeling the union requires _explicit wrapping and unwrapping_ and, for instance, `Username` is **not** a subtype of `UsernameOrPassword`. + +### Inference of Union Types +The compiler assigns a union type to an expression _only if_ such a type is explicitly given. +For instance, given these values: + +```scala +val name = Username("Eve") // name: Username = Username(Eve) +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 +val a: Object = Username(Eve) + +scala> val b: Password | Username = if (true) name else password +val b: Password | Username = Username(Eve) +```` + +The type of `a` is `Object`, which is a supertype of `Username` and `Password`, but not the *least* supertype, `Password | Username`. +If you want the least supertype you have to give it explicitly, as is done for `b`. + +> Union types are duals of intersection types. +> And like `&` with intersection types, `|` is also commutative: `A | B` is the same type as `B | A`. + diff --git a/_overviews/scala3-book/types-variance.md b/_overviews/scala3-book/types-variance.md new file mode 100644 index 0000000000..e4c23a24dc --- /dev/null +++ b/_overviews/scala3-book/types-variance.md @@ -0,0 +1,147 @@ +--- +title: Variance +type: section +description: This section introduces and demonstrates variance in Scala 3. +num: 53 +previous-page: types-adts-gadts +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: +```scala +trait Item { def productNumber: String } +trait Buyable extends Item { def price: Int } +trait Book extends CartItem { def isbn: String } +``` + +Let us also assume the following parameterized types: +```scala +// an example of an invariant type +class Pipeline[T]: + def process(t: T): T + +// an example of an covariant type +class Producer[+T]: + def make: T + +// an example of an contravariant type +class Consumer[-T]: + def take(t: T): Unit +``` +In general there are three modes of variance: + +- **invariant** -- the default, written like `Pipeline[T]` +- **covariant** -- annotated with a `+`, such as `Producer[+T]` +- **contravariant** -- annotated with a `-`, like in `Consumer[-T]` + +We will now go into detail on what this annotation means and why we use it. + +### Invariant Types +By default, types like `Pipeline` are invariant in their type argument (`T` in this case). +This means that types like `Pipeline[Item]`, `Pipeline[Buyable]`, and `Pipeline[Item]` are in _no subtyping relationship_ to each other. + +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: + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = + if p1.price < p2.price then p1.process(b) else p2.process(b) +``` +Now, recall that we have the following _subtyping relationship_ between our types: +```scala +Book <: Buyable <: Item +``` +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. + +We cannot pass a `Pipeline[Item]` because calling `process` on it only promises to return an `Item`; however, we are supposed to return a `Buyable`. + +#### Why Invariant? +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_. + +Marking it as covariant means that we can pass (or return) a `Producer[Book]` where a `Producer[Buyable]` is expected. +And in fact, this is sound: The type of `Producer[Buyable].make` only promises to _return_ a `Buyable`. +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]`: +```scala +def makeTwo(p: Producer[Buyable]): Double = + p.make.price + p.make.price +``` +It is perfectly fine to pass a producer for books: +``` +val bookProducer: Producer[Book] = ??? +makeTwo(bookProducer) +``` +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: + +```scala +class List[+A] ... +class Vector[+A] ... +``` + +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_. + +Marking it as contravariant means that we can pass (or return) a `Producer[Item]` where a `Producer[Buyable]` is expected. +That is, we have the subtyping relationship `Producer[Item] <: Producer[Buyable]`. +Remember, for type `Consumer`, it was the other way around, and we had `Consumer[Buyable] <: Consumer[Item]`. + +And in fact, this is sound: The type of `Producer[Buyable].make` only promises us to _return_ a `Buyable`. +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`. + + +#### 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: + +```scala +trait Function[-A, +B]: + def apply(a: A): B +``` +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: + +```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] = g +``` + +## Summary +In this section, we have encountered three different kinds of variance: + +- **Producers** are typically covariant, and mark their type parameter with `+`. + This also holds for immutable collections. +- **Consumers** are typically contravariant, and mark their type parameter with `-`. +- Types that are **both** producers and consumers have to be invariant, and do not require any marking on their type parameter. + Immutable collections like `Array` fall into this category. diff --git a/_overviews/scala3-book/why-scala-3.md b/_overviews/scala3-book/why-scala-3.md new file mode 100644 index 0000000000..7092d0c6f9 --- /dev/null +++ b/_overviews/scala3-book/why-scala-3.md @@ -0,0 +1,446 @@ +--- +title: Why Scala 3? +type: chapter +description: This page describes the benefits of the Scala 3 programming language. +num: 3 +previous-page: scala-features +next-page: taste-intro +--- + +{% 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 %} + + +There are many benefits to using Scala, and Scala 3 in particular. +It might be hard to list every benefit of Scala, but a Top Ten list looks like this: + +1. Scala embraces a fusion of functional programming (FP) and object-oriented programming (OOP) +2. Scala is statically typed, but often feels like a dynamically typed language +3. Scala’s syntax is concise, but still readable; it’s often referred to as *expressive* +4. *Implicits* in Scala 2 were a defining feature, and they have been improved and simplified in Scala 3 +5. Scala integrates seamlessly with Java, so you can create projects with mixed Scala and Java code, and Scala code easily uses the thousands of existing Java libraries +6. Scala can be used on the server, and also in the browser with [Scala.js](https://www.scala-js.org) +7. The Scala standard library has dozens of pre-built, functional methods to save you time, and greatly reduce the need to write custom `for` loops and algorithms +8. “Best practices” are built into Scala, which favors immutability, anonymous functions, higher-order functions, pattern matching, classes that cannot be extended by default, and more +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. +As Martin Odersky has stated, 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 + +Possibly some of the best examples of this are the classes in the standard library. +For instance, a `List` is defined as a `class`, and a new instance is created like this: + +```scala +val x = List(1,2,3) +``` + +However, what appears to the programmer to be a simple `List` is actually built from a combination of many specialized traits. + +{% comment %} +TODO: Update this. It shows the Scala 2.13 hierarchy with Scala 3 syntax. + +```scala +sealed abstract class List[+A] + extends AbstractSeq[A], + LinearSeq[A], + LinearSeqOps[A, List, List[A]], + StrictOptimizedLinearSeqOps[A, List, List[A]], + StrictOptimizedSeqOps[A, List, List[A]], + IterableFactoryDefaults[A, List], + DefaultSerializable +``` + +Those traits are also composed of other smaller, modular traits. +{% endcomment %} + +In addition to building a class like `List` from a series of small, modular traits, the `List` API also consists of a series of dozens of functional methods, many of which are higher-order functions: + +```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) +``` + +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 assignment: + +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3,4,5) +val stuff = ("fish", 42, 1_234.5) +``` + +When passing anonymous functions to higher-order functions: + +```scala +list.filter(_ < 4) +list.map(_ * 2) +list.filter(_ < 4) + .map(_ * 2) +``` + +When defining methods: + +```scala +def add(a: Int, b: Int) = a + b +``` + +And in Scala 3, when using [union types][union-types]: + +```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 +``` + + +## 3) Concise syntax + +Variable type assignment is concise: + +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3) +``` + +Creating types like traits, classes, and enumerations are concise: + +```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 +) +``` + +Higher-order functions are concise: + +```scala +list.filter(_ < 4) +list.map(_ * 2) +``` + +All of these expressions, and many more, are concise, and still very readable: *expressive*. + + + +## 4) Implicits, simplified + +Implicits in Scala 2 were a major distinguishing design feature. +They represented *the* fundamental way to abstract over context, with a unified paradigm that served a great variety of use cases, among them: + +- Implementing type classes +- Establishing context +- Dependency injection +- Expressing capabilities + +Since then other languages have adopted similar concepts, all of which are variants of the core idea of *term inference*: Given a type, the compiler synthesizes a “canonical” term that has that type. + +While implicits were a defining feature in Scala 2, their design has been greatly improved in Scala 3: + +- There’s a single way to define “given” values +- There’s a single way to introduce implicit parameters and arguments +- There’s a separate way to import givens that does not allow them to hide in a sea of normal imports +- There’s a single way to define an implicit conversion, which is clearly marked as such, and does not require special syntax + +Benefits of these changes include: + +- The new design avoids feature interactions and makes the language more consistent +- It makes implicits easier to learn and harder to abuse +- It greatly improves the clarity of the 95% of Scala programs that use implicits +- It has the potential to enable term inference in a principled way that is also accessible and friendly + +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. +For instance, you can use all of the thousands of Java libraries that are available in your Scala projects. +A Scala `String` is essentially a Java `String`, with additional capabilities added to it. +Scala seamlessly uses the date/time classes in the Java *import java.time._* package. +You can also use Java collections classes in Scala, and to give them more functionality, Scala includes methods so you can transform them into Scala collections. + +While almost every interaction is seamless, the [“Interacting with Java” chapter][java] demonstrates how to use some features together better, including how to use: + +- Java collections in Scala +- Java `Optional` in Scala +- Java interfaces in Scala +- Scala collections in Java +- Scala `Option` in Java +- Scala traits in Java +- Scala methods that throw exceptions in Java code +- Scala varargs parameters in Java + +See that chapter for more details on these features. + + + +## 6) Client & server + +Scala can be used on the server side with terrific frameworks: + +- The [Play Framework](https://www.playframework.com) lets you build highly scalable server-side applications and microservices +- [Akka Actors](https://akka.io) let you use the actor model to greatly simplify parallel and concurrent software applications + +Scala can also be used in the browser with the [Scala.js project](https://www.scala-js.org), which is a type-safe replacement for JavaScript. +The Scala.js ecosystem [has dozens of libraries](https://www.scala-js.org/libraries) to let you use React, Angular, jQuery, and many other JavaScript and Scala libraries in the browser. + +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 + +Because you’ll rarely ever need to write a custom `for` loop again, 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. +The following examples show some of the built-in collections methods, and there are many in addition to these. +Also, while these all use the `List` class, the same methods work with other collections classes like `Seq`, `Vector`, `LazyList`, `Set`, `Map`, `Array`, and `ArrayBuffer`. + +Here are some examples: + +```scala +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) + +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) + +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) +``` + + + +## 8) Built-in practices + +Scala idioms encourage best practices in many ways. +For immutability, you’re encouraged to create immutable `val` fields: + +```scala +val a = 1 // immutable variable +``` + +You’re encouraged to use immutable collections classes like `List` and `Map`: + +```scala +val b = List(1,2,3) // List is immutable +val c = Map(1 -> "one") // Map is immutable +``` + +Case class parameters are immutable: + +```scala +case class Person(name: String) +val p = Person("Michael Scott") +p.name // Michael Scott +p.name = "Joe" // compiler error (reassignment to val name) +``` + +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: + +```scala +a.dropWhile(_ < 25) +a.filter(_ < 25) +a.takeWhile(_ < 30) +a.filter(_ < 30).map(_ * 10) +nums.sortWith(_ < _) +nums.sortWith(_ > _) +``` + +`match` expressions let you use pattern matching, and they truly are *expressions* that return values: + +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` + +Because they can return values, they’re often used as the body of a method: + +```scala +def isTrue(a: Any) = a match + case 0 | "" => false + case _ => true +``` + + + +## 9) Ecosystem libraries + +Scala libraries for functional programming like [Cats](https://typelevel.org/cats) and [Zio](https://zio.dev) are leading-edge libraries in the FP community. +All of the buzzwords like high-performance, type safe, concurrent, asynchronous, resource-safe, testable, functional, modular, binary-compatible, efficient, effects/effectful, and more, can be said about these libraries. + +Many other libraries can be found in 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. +Scala 3’s goals were defined early on, and those related to the type system include: + +- Simplification +- Eliminate inconsistencies +- Safety +- Ergonomics +- Performance + +*Simplification* comes about through dozens of changed and dropped features. +For instance, the changes from the overloaded `implicit` keyword in Scala 2 to the terms `given` and `using` in Scala 3 make the language more clear, especially for beginning developers. + +*Eliminating inconsistencies* is related to the dozens of [dropped features][dropped], [changed features][changed], and [added features][added] in Scala 3. +Some of the most important features in this category are: + +- Intersection types +- Union types +- Implicit function types +- Dependent function types +- Trait parameters +- Generic tuples + +{% 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 %} + + +*Safety* is related to several new and changed features: + +- Multiversal equality +- Restricting implicit conversions +- Null safety + +Good examples of *ergonomics* are enumerations and extension methods, which have been added to Scala 3 in a very readable manner: + +```scala +// enumeration +enum Color: + case Red, Green, Blue + +// extension methods +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 +``` + +*Performance* relates to several areas. +One of those is [opaque types][opaque-types]. +In Scala 2 there were several attempts to create solutions to keep with the Domain-driven design (DDD) practice of giving values more meaningful types. +These attempts included: + +- Type aliases +- Value classes +- Case classes + +Unfortunately all of these approaches had weaknesses, as described in the [*Opaque Types* SIP](https://docs.scala-lang.org/sips/opaque-types.html). +Conversely, the goal of opaque types, as described in that SIP, is that “operations on these wrapper types must not create any extra overhead at runtime while still providing a type safe use at compile time.” + +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. +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 %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }}/overview.html +[dropped]: https://dotty.epfl.ch/docs/Dropped%20Features/index.html +[changed]: https://dotty.epfl.ch/docs/Other%20Changed%20Features/index.html +[added]: https://dotty.epfl.ch/docs/Other%20New%20Features/index.html + +[union-types]: {% link _overviews/scala3-book/types-union.md %} +[opaque-types]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_overviews/scala3-macros/best-practices.md b/_overviews/scala3-macros/best-practices.md new file mode 100644 index 0000000000..261ddd9e0d --- /dev/null +++ b/_overviews/scala3-macros/best-practices.md @@ -0,0 +1,61 @@ +--- +type: chapter +title: Best Practices +num: 8 +--- +## Inline + +### Be careful when inlining for performance +To take the most advantage of the JVM JIT optimisations you want to avoid generating large methods. + + +## Macros +**Coming soon** + + +## Quoted code + +### Keep quotes readable +* Try to avoid `${..}` with arbitrary expressions inside + * Use `$someExpr` + * Use `${ someExprFrom('localExpr) }` + +To illustrate, consider the following example: +```scala +val x: StringContext = ... +'{ StringContext(${Varargs(stringContext.parts.map(Expr(_)))}: _*) } +``` +Instead we can write the following: + +```scala +val x: StringContext = ... +val partExprs = stringContext.parts.map(Expr(_)) +val partsExpr = Varargs(partExprs) +'{ StringContext($partsExpr: _*) } +``` +The contents of the quote are cleared this way. + +### Avoid nested contexts + +Consider the following code: + +```scala +val y: Expr[Int] = ... +def body(x: Expr[Int])(using quotes.Nested) = '{ $x + $y } +'{ (x: Int) => ${ body('x) } } +``` + +Instead, use a normal context and pass all needed expressions. +This has also the advantage of allowing the function to not be defined locally. +```scala +def body(x: Expr[Int], y: Expr[Int])(using Quotes) = + '{ $x + $y } + +val y: Expr[Int] = ... +'{ (x: Int) => ${ body('x, y) } } +``` + + + +## TASTy reflection +**Coming soon** diff --git a/_overviews/scala3-macros/faq.md b/_overviews/scala3-macros/faq.md new file mode 100644 index 0000000000..c3f616e333 --- /dev/null +++ b/_overviews/scala3-macros/faq.md @@ -0,0 +1,25 @@ +--- +type: chapter +title: FAQ +num: 7 +--- + +## Which should I use `Expr(...)` or `'{...}`? +If you can write your code using `Expr(...)`, you will evaluate more at compile time. +Only use `'{...}` if you really need to evaluate the code later at runtime, usually because it depends on runtime values. + +## Which is better between `Expr(true)` or `'{true}`? +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` + +## 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: +```scala +val x: Expr[X] = ... +x match + case '{ $x: t } => + // `x: Expr[X & t]` where `t` is the precise type of `x` +``` diff --git a/_overviews/scala3-macros/index.md b/_overviews/scala3-macros/index.md new file mode 100644 index 0000000000..0b5a62b4a6 --- /dev/null +++ b/_overviews/scala3-macros/index.md @@ -0,0 +1,16 @@ +--- +type: book +title: Introduction +description: A tutorial to cover all the features involved in writing macros in Scala 3. +num: 0 +--- + +## Scala 3 Macro Tutorial +A [tutorial][tutorial] to cover all the features involved in writing macros in Scala 3. + +## Migrating Macros +Scala 3 provides a toolbox full of metaprogramming features, which are safer, more robust, and much more stable than their counterparts in Scala 2. Implementing macro libraries in Scala 3 is simpler and the resulting libraries are easier to maintain across future versions of Scala. The improvements come at a price: the metaprogramming facilities have been re-designed from the ground up. In consequence, existing macro libraries need to be ported to the new interfaces. + +In the [Migrating Macros](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) section, you will find helpful content to port your macros code to Scala 3. + +[tutorial]: {% link _overviews/scala3-macros/tutorial/index.md %} diff --git a/_overviews/scala3-macros/other-resources.md b/_overviews/scala3-macros/other-resources.md new file mode 100644 index 0000000000..571cc89c5a --- /dev/null +++ b/_overviews/scala3-macros/other-resources.md @@ -0,0 +1,34 @@ +--- +type: chapter +title: Other Resources +num: 9 +--- + +## Scala 2 migration + * [Scala 2 migration and cross-compilation][migration] + * [Migration status][migration-status] + +## Dotty documentation +- [Dotty Documentation](https://dotty.epfl.ch/docs/reference/metaprogramming/toc.html) +- [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. + +## Talks +* [Scala Days - Metaprogramming in Dotty](https://www.youtube.com/watch?v=ZfDS_gJyPTc) + +## Projects and examples +* [dotty-macro-examples](https://github.com/lampepfl/dotty-macro-examples) +* [XML Interpolator](https://github.com/dotty-staging/xml-interpolator/tree/master) +* [Shapeless 3](https://github.com/dotty-staging/shapeless/tree/shapeless-3) +* *More Coming soon* + +[contributing]: {% link scala3/contribute-to-docs.md %} +[best-practices]: {% link _overviews/scala3-macros/best-practices.md %} +[compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[migration]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html +[faq]: {% link _overviews/scala3-macros/faq.md %} +[inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[migration-status]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html#macro-libraries +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[tasty]: {% link _overviews/scala3-macros/tutorial/reflection.md %} diff --git a/_overviews/scala3-macros/tutorial/compiletime.md b/_overviews/scala3-macros/tutorial/compiletime.md new file mode 100644 index 0000000000..79d90bd65c --- /dev/null +++ b/_overviews/scala3-macros/tutorial/compiletime.md @@ -0,0 +1,75 @@ +--- +type: section +title: Scala Compile-time Operations +num: 3 + +previous-page: inline +next-page: macros +--- + +Operations in [scala.compiletime][compiletime-api] are metaprogramming operations that can be used within an `inline` method. +These operation do cover some common use cases of macros without you needing to define a macro. + +## Reporting + +It is possible to emmit error messages when inlining code. + +```scala +inline def doSomething(inline mode: Boolean): Unit = + if mode then ... + else if !mode then ... + else error("Mode must be a know value") + +doSomething(true) +doSomething(false) +val bool: Boolean = ... +doSomething(bool) // error: Mode must be a known value +``` + +If `error` is called outside an inline method the error will be emitted when compiling that call. +If the `error` is written inside an inline method, the error will be emitted only if after inlining the call in not removed as part of a dead branch. +In the previous example we used the value of `mode` is know we would only keep one of the first two branches. + +If we want to include part the source code of the arguments in the error message we can use the `codeOf` method. + +```scala +inline def doSomething(inline mode: Boolean): Unit = + if mode then ... + else if !mode then ... + else error("Mode must be a known value but got: " + codeOf(mode)) + +val bool: Boolean = ... +doSomething(bool) // error: Mode must be a known value but got: bool +``` + +## Summoning + +There are two ways to summon values in inline methods, the first is with a `using` parameter and the second is with one of `summonInline`, `summonAll` or `summonFrom`. +`using` will summon the value at call site before inlining as if the method was not `inline`. +On the other hand, `summonInline` will summon after inlining if the call is not eliminated form a dead branch. +Summon all provides a way to summon multiple values at the same time from a tuple type. +`summonFrom` provides a way to try several implicit searches. + +## Values +* `constValue`, `constValueOpt` and `constValueTuple` +* `S` +*Coming soon* + +## Testing +* `testing.typeChecks` and `testing.typeCheckErrors` + +## Assertions +* `byName` + +*Coming soon* + +## Inline Matching +* `erasedValue` + +*Coming soon* + +## Ops (scala.compiletime.ops) +*Coming soon* + + +[compiletime-api]: https://dotty.epfl.ch/api/scala/compiletime.html diff --git a/_overviews/scala3-macros/tutorial/index.md b/_overviews/scala3-macros/tutorial/index.md new file mode 100644 index 0000000000..d9b0858392 --- /dev/null +++ b/_overviews/scala3-macros/tutorial/index.md @@ -0,0 +1,38 @@ +--- +type: chapter +title: Tutorial +description: A tutorial to cover all the features involved in writing macros in Scala 3. +num: 1 + +next-page: inline +--- + +## Introduction + +This tutorial covers all the features involved in writing macros in Scala 3. + +The metaprogramming API of Scala 3 is designed in layers to gradually +support different levels of use-cases. Each successive layer exposes additional +abstractions and offers more fine-grained control. + +- As a starting point, the new [`inline` feature][inline] allows some abstractions (values and methods) to be marked as statically reducible. + It provides the entry point for macros and other metaprogramming utilities. + +- [Compile-time operations][compiletime] offer additional metaprogramming utilities that can be used within `inline` methods (for example to improve error reporting), without having to define a macro. + +- Starting from `inline`-methods, [macros][macros] are programs that explicitly operate on programs. + + - Macros can be defined in terms of a _high-level_ API of [quoted expressions][quotes], that admits simple construction and deconstruction of programs expressions. + + - Macros can also be defined in terms of a more _low-level_ API of [TASTy Reflection][tasty], that allows detailed inspection of programs. + +> The tutorial uses the API of Scala 3.0.0-M3. The API had many small changes in this revision. + +> 🚧 We are still in the process of writing the tutorial. You can [help us improve it][contributing] 🚧 + +[contributing]: {% link scala3/contribute-to-docs.md %} +[compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[tasty]: {% link _overviews/scala3-macros/tutorial/reflection.md %} diff --git a/_overviews/scala3-macros/tutorial/inline.md b/_overviews/scala3-macros/tutorial/inline.md new file mode 100644 index 0000000000..31619bb2ec --- /dev/null +++ b/_overviews/scala3-macros/tutorial/inline.md @@ -0,0 +1,505 @@ +--- +type: section +title: Inline +num: 2 + +previous-page: index +next-page: compiletime +--- + +Inlining is a common compile-time metaprogramming technique, typically used to achieve performance optimizations. As we will see, in Scala 3, the concept of inlining provides us with an entrypoint to programming with macros. + +1. It introduces inline as a [soft keyword][soft-modifier]. +2. It guarantees that inlining actually happens instead of being best-effort. +3. It introduces operations that are guaranteed to evaluate at compile-time. + +## Inline Constants + +The simplest form of inlining is to inline constants in programs: + + +```scala +inline val pi = 3.141592653589793 +inline val pie = "🥧" +``` + +The usage of the keyword `inline` in the _inline value definitions_ above *guarantees* that all references to `pi` and `pie` are inlined: + +```scala +val pi2 = pi + pi // val pi2 = 6.283185307179586 +val pie2 = pie + pie // val pie2 = "🥧🥧" +``` + +In the code above, the references `pi` and `pie` are inlined. +Then an optimization called "constant folding" is applied by the compiler, which computes the resulting value `pi2` and `pie2` at _compile-time_. + +> ##### Inline (Scala 3) vs. final (Scala 2) +> In Scala 2, we would have used the modifier `final` in the definition that is without a return type: +> +> ```scala +> final val pi = 3.141592653589793 +> final val pie = "🥧" +> ``` +> +> The `final` modifier will ensure that `pi` and `pie` will take a _literal type_. +> Then the constant propagation optimization in the compiler can perform inlining for such definitions. +> However, this form of constant propagation is _best-effort_ and not guaranteed. +> Scala 3.0 also supports `final val`-inlining as _best-effort_ inlining for migration purposes. + +Currently, only constant expression may appear on the right-hand side of an inline value definition. +Therefore, the following code is invalid, though the compiler knows that the right-hand side is a compile-time constant value: + +```Scala +val pi = 3.141592653589793 +inline val pi2 = pi + pi // error +``` +Note that by defining `inline val pi`, the addition can be computed at compile time. +This resolves the above error and `pi2` will receive the literal type `6.283185307179586d`. + +## Inline Methods + +We can also use the modifier `inline` to define a method that should be inlined at the call-site: + +```scala +inline def logged[T](level: Int, message: => String)(inline op: T): T = + println(s"[$level]Computing $message") + val res = op + println(s"[$level]Result of $message: $res") + res +``` + +When an inline method like `logged` is called, its body will be expanded at the call-site at compile time! +That is, the call to `logged` will be replaced by the body of the method. +The provided arguments are statically substituted for the parameters of `logged`, correspondingly. +Therefore, the compiler inlines the following call + +```scala +logged(logLevel, getMessage()) { + computeSomthing() +} +``` + +and rewrites it to: + +```Scala +val level = logLevel +def message = getMessage() + +println(s"[$level]Computing $message") +val res = computeSomthing() +println(s"[$level]Result of $tag: $res") +res +``` + +### Semantics of Inline Methods +Our example method `logged` uses three different kinds of parameters, illustrating +that inlining handles those parameters differently: + +1. __By-value parameters__. The compiler generates a `val` binding for *by-value* parameters. This way, the argument expression is evaluated only once before the method body is reduced. + + This can be seen in the parameter `level` from the example. + In some cases, when the arguments are pure constant values, the binding is omitted and the value is inlined directly. + +2. __By-Name parameters__. The compiler generates a `def` binding for *by-name* parameters. This way, the argument expression is evaluated every time it is used, but the code is shared. + + This can be seen in the parameter `message` from the example. + +3. __Inline parameters__. Inline parameters do not create bindings and are simply inlined. This way, their code is duplicated everywhere they are used. + + This can be seen in the parameter `op` from the example. + +The way the different parameters are translated guarantees that inlining a call **will not change** its semantics. +This implies that the initial elaboration (overloading resolution, implicit search, ...), performed while typing the body of the inline method, will not change when inlined. + +For example, consider the following code: + +```scala +class Logger: + def log(x: Any): Unit = println(x) + +class RefinedLogger extends Logger: + override def log(x: Any): Unit = println("Any: " + x) + def log(x: String): Unit = println("String: " + x) + +inline def logged[T](logger: Logger, x: T): Unit = + logger.log(x) +``` + +The separate type checking of `logger.log(x)` will resolve the call to the method `Log.log` which takes an argument of the type `Any`. +Now, given the following code: + +```scala +logged(new RefinedLogger, "✔️") +``` + +It expands to: + +``` +val loggeer = new RefinedLogger +val x = "✔️" +logger.log(x) +``` +Even though now we know that `x` is a `String`, the call `logger.log(x)` still resolves to the method `Log.log` which takes an argument of the type `Any`. + +> ##### Inlining preserves semantics +> Regardless of whether `logged` is defined as a `def` or `inline def`, it performs the same operations with only some differences in performance. + +### Inline Parameters + +One important application of inlining is to enable constant folding optimisation across method boundaries. +Inline parameters do not create bindings and their code is duplicated everywhere they are used. + +```scala +inline def perimeter(inline radius: Double): Double = + 2.0 * pi * radius +``` +In the above example, we expect that if the `radius` is statically known then the whole computation can be performed at compile-time. +The following call + +```scala +perimeter(5.0) +``` + +is rewritten to: + +```Scala +2.0 * pi * 5.0 +``` + +Then `pi` is inlined (we assume the `inline val` definition from the start): + +```Scala +2.0 * 3.141592653589793 * 5.0 +``` + +Finally, it is constant folded to + +``` +31.4159265359 +``` + +> ##### Inline parameters should be used only once +> We need to be careful when using an inline parameter **more than once**. +> Consider the following code: +> +> ```scala +> inline def printPerimeter(inline radius: Double): Double = +> println(s"Perimeter (r = $radius) = ${perimeter(radius)}") +> ``` +> It works perfectly fine when a constant or reference to a val is passed to it. +> ```scala +> printPerimeter(5.0) +> // inlined as +> println(s"Perimeter (r = ${5.0}) = ${31.4159265359}") +> ``` +> +> But if a larger expression (possibly with side-effects) is passed, we might accidentally duplicate work. +> +> ```scala +> printPerimeter(longComputation()) +> // inlined as +> println(s"Perimeter (r = ${longComputation()}) = ${6.283185307179586 * longComputation()}") +> ``` + +A useful application of inline parameters is to avoid the creation of _closures_, incurred by the use of by-name parameters. + +```scala +def assert1(cond: Boolean, msg: => String) = + if !cond then + throw new Exception(msg) + +assert1(x, "error1") +// is inlined as +val cond = x +def msg = "error1" +if !cond then + throw new Exception("error1") +``` +In the above example, we can see that the use of a by-name parameter leads to a local definition `msg`, which allocates a closure before the condition is checked. + +If we use an inline parameter instead, we can guarantee that the condition is checked before any of the code that handles the exception is reached. +In the case of an assertion, this code should never be reached. +```scala +inline def assert2(cond: Boolean, inline msg: String) = + if !cond then + throw new Exception(msg) + +assert2(x, "error2") +// is inlined as +val cond = x +if !cond then + throw new Exception("error2") +``` + +### Inline Conditionals +If the condition of an `if` is a known constant (`true` or `false`), possibly after inlining and constant folding, then the conditional is partially evaluated and only one branch will be kept. + +For example, the following power method contains some `if` that will potentially unroll the recursion and remove all method calls. + +```scala +inline def power(x: Double, inline n: Int): Double = + if (n == 0) 1.0 + else if (n % 2 == 1) x * power(x, n - 1) + else power(x * x, n / 2) +``` +Calling `power` with statically known constants results in the following code: + ```scala + power(2, 2) + // first inlines as + val x = 2 + if (2 == 0) 1.0 // dead branch + else if (2 % 2 == 1) x * power(x, 2 - 1) // dead branch + else power(x * x, 2 / 2) + // partially evaluated to + val x = 2 + power(x * x, 1) + ``` +<details> + <summary> See rest of inlining steps</summary> + +```scala +// then inlined as +val x = 2 +val x2 = x * x +if (1 == 0) 1.0 // dead branch +else if (1 % 2 == 1) x2 * power(x2, 1 - 1) +else power(x2 * x2, 1 / 2) // dead branch +// partially evaluated to +val x = 2 +val x2 = x * x +x2 * power(x2, 0) +// then inlined as +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 +} +// partially evaluated to +val x = 2 +val x2 = x * x +x2 * 1.0 +``` +</details> + +In contrast, let us imagine we do not know the value of `n`: + +```scala +power(2, unkownNumber) +``` +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. + +<details> + <summary>See inlining steps</summary> + +```scala +// first inlines as +val x = 2 +if (unkownNumber == 0) 1.0 +else if (unkownNumber % 2 == 1) x * power(x, unkownNumber - 1) +else power(x * x, unkownNumber / 2) +// then inlined as +val x = 2 +if (unkownNumber == 0) 1.0 +else if (unkownNumber % 2 == 1) x * { + if (unkownNumber - 1 == 0) 1.0 + else if ((unkownNumber - 1) % 2 == 1) x2 * power(x2, unkownNumber - 1 - 1) + else power(x2 * x2, (unkownNumber - 1) / 2) +} +else { + val x2 = x * x + if (unkownNumber / 2 == 0) 1.0 + else if ((unkownNumber / 2) % 2 == 1) x2 * power(x2, unkownNumber / 2 - 1) + else power(x2 * x2, unkownNumber / 2 / 2) +} +// Oops this will never finish compiling +... +``` +</details> + +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. + +```scala +inline def power(x: Double, inline n: Int): Double = + inline if (n == 0) 1.0 + else inline if (n % 2 == 1) x * power(x, n - 1) + else power(x * x, n / 2) +``` + +```scala +power(2, 2) // Ok +power(2, unkownNumber) // error +``` + +We will come back to this example later and see how we can get more control on how code is generated. + + +### Inline Method Overriding + +To ensure the correct behavior of combining the static feature of `inline def` with the dynamic feature of interfaces and overriding, some restrictions have to be imposed. + +#### Effectively final +Firstly, all inline methods are _effectively final_. +This ensures that the overload resolution at compile-time behaves the same as the one at runtime. + +#### Signature preservation +Secondly, overrides must have the _exact same signature_ as the overridden method including the inline parameters. +This ensures that the call semantics are the same for both methods. + +#### Retained inline methods +It is possible to implement or override a normal method with an inline method. + +Consider the following example: + +```scala +trait Logger: + def log(x: Any): Unit + +class PrintLogger extends Logger: + inline def log(x: Any): Unit = println(x) +``` +However, calling the `log` method directly on `PrintLogger` will inline the code, while calling it on `Logger` will not. +To also admit the latter, the code of `log` must exist at runtime. +We call this a _retained inline_ method. + +For any non-retained inline `def` or `val` the code can always be fully inlined at all call sites. +Hence, those methods will not be needed at runtime and can be erased from the bytecode. +However, retained inline methods must be compatible with the case that they are not inlined. +In particular, retained inline methods cannot take any inline parameters. +Furthermore, an `inline if` (as in the `power` example) will not work, since the `if` cannot be constant folded in the retained case. +Other examples involve metaprogramming constructs that only have meaning when inlined. + +#### Abstract inline methods +It is also possible to create _abstract inline definitions_. + +```scala +trait InlineLogger: + inline def log(inline x: Any): Unit + +class PrintLogger inline InlineLogger: + inline def log(inline x: Any): Unit = println(x) +``` + +This forces the implementation of `log` to be an inline method and also allows `inline` parameters. +Counterintuitively, the `log` on the interface `InlineLogger` cannot be directly called. The method implementation is not statically known and we thus do not know what to inline. +Calling an abstract inline methods thus results in an error. +The usefuleness of abstract inline methods becomes apparent when used in another inline method: + +```scala +inline def logged(logger: Logger, x: Any) = + logger.log(x) +``` +Let us assume a call to `logged` on a concrete instance of `PrintLogger`: +```scala +logged(new PrintLogger, "🥧") +// inlined as +val logger: PrintLogger = new PrintLogger +logger.log(x) +``` +After inlining, the call to `log` is de-virtualized and known to be on `PrintLogger`. +Therefore also the code of `log` can be inlined. + +#### Summary of inline methods +* All `inline` methods are final. +* Abstract `inline` methods can only be implemented by inline methods. +* If an inline method overrides/implements a normal method then it must be retained and retained methods cannot have inline parameters. +* Abstract `inline` methods cannot be called directly (except in inline code). + +## Transparent Inline Methods +Transparent inlines are a simple, yet powerful, extension to `inline` methods and unlock many metaprogramming usecases. +Calls to transparents allow for an inline piece of code to refine the return type based on the precise type of the inlined expression. +In Scala 2 parlance, transparents capture the essence of _whitebox macros_. + + +```scala +transparent inline def default(inline name: String): Any = + inline if name == "Int" then 0 + else inline if name == "String" then "" + else ... +``` + +```scala +val n0: Int = default("Int") +val s0: String = default("String") +``` + +Note that even if the return type of `default` is `Any`, the first call is typed as an `Int` and the second as a `String`. +The return type represents the upper bound of the type within the inlined term. +We could also have been more precise and have written instead +```scala +transparent inline def default(inline name: String): 0 | "" = ... +``` +While in this example it seems the return type is not necessary, it is important when the inline method is recursive. +There it should be precise enough for the recursion to type but will get more precise after inlining. + +> ##### Transparents affect binary compatibility +> It is important to note that changing the body of a `transparent inline def` will change how the call site is typed. +> This implies that the body plays a part in the binary and source compatibility of this interface. + + +## Compiletime Operations + +We also provide some operations that evaluate at compiletime. + +### Inline Matches +Like inline `if`, inline matches guarantee that the pattern matching can be statically reduced at compile time and only one branch is kept. + +In the following example, the scrutinee, `x`, is an inline parameter that we can pattern match on at compile time. + +```scala +inline def half(x: Any): Any = + inline x match + case x: Int => x / 2 + case x: String => x.substring(0, x.length / 2) + +half(6) +// expands to: +// val x = 6 +// x / 2 + +half("hello world") +// expands to: +// val x = "hello world" +// x.substring(0, x.length / 2) +``` +This illustrates that inline matches provide a way to match on the static type of some expression. +As we match on the _static_ type of an expression, the following code would fail to compile. + +```scala +val n: Any = 3 +half(n) // error: n is not statically known to be an Int or a Double +``` +Notably, The value `n` is not marked as `inline` and in consequence at compile time +there is not enough information about the scrutinee to decide which branch to take. + +### scala.compiletime +The package `scala.compiletime` provides useful metaprogramming abstractions that can be used within `inline` methods to provide custom semantics. + +## Macros +Inlining is also the core mechanism used to write macros. +Macros provide a way to control the code generation and analysis after the call is inlined. + + +```scala +inline def power(x: Double, inline n: Int) = + ${ powerCode('x, 'n) } + +def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = ... +``` + + +[soft-modifier]: https://dotty.epfl.ch/docs/reference/soft-modifier.html + +[contributing]: {% link scala3/contribute-to-docs.md %} +[best-practices]: {% link _overviews/scala3-macros/best-practices.md %} +[compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[migration]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html +[faq]: {% link _overviews/scala3-macros/faq.md %} +[inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[migration-status]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/migration-status.html +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[tasty]: {% link _overviews/scala3-macros/tutorial/reflection.md %} diff --git a/_overviews/scala3-macros/tutorial/macros.md b/_overviews/scala3-macros/tutorial/macros.md new file mode 100644 index 0000000000..c421e31159 --- /dev/null +++ b/_overviews/scala3-macros/tutorial/macros.md @@ -0,0 +1,282 @@ +--- +type: section +title: Scala 3 Macros +num: 4 + +previous-page: compiletime +next-page: quotes +--- + +[Inline methods][inline] provide us with a elegant technique for metaprogramming by performing some operations at compile time. +However, sometimes inlining is not enough and we need more powerful ways to analyze and synthesize programs at compile time. +Macros enable us to do exactly this: treat **programs as data** and manipulate them. + + +## 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: +```scala +def inspectCode(x: Expr[Any])(using Quotes): Expr[Any] = + println(x.show) + x +``` +After printing the argument expression, we return the original argument as a Scala expression of type `Expr[Any]`. + +As foreshadowed in the section on [Inline][inline], inline methods provide the entry point for macro definitions: + +```scala +inline def inspect(inline x: Any): Any = ${ inspectCode('x) } +``` +All macros are defined with an `inline def`. +The implementation of this entry point always has the same shape: + +- they only contain a single [splice][quotes] `${ ... }` +- the splice contains a single call to the method that implements the macro (for example `inspectCode`). +- the call to the macro implementation receives the _quoted_ parameters (that is `'x` instead of `x`) and a contextual `Quotes`. + +We will dig deeper into these concepts later in this and the following sections. + +Calling our `inspect` macro `inspect(sys error "abort")` prints a string representation of the argument expression at compile time: +``` +scala.sys.error("abort") +``` + + +### Macros and Type Parameters + +If the macro has type parameters, the implementation will also need to know about them. +Just like `scala.quoted.Expr[T]` represents a Scala expression of type `T`, we use `scala.quoted.Type[T]` to represent the Scala type `T`. + +```scala +inline def logged[T](inline x: T): T = ${ loggedCode('x) } + +def loggedCode[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = ... +``` +Both the instance of `Type[T]` and the contextual `Quotes` are automatically provided by the splice in the corresponding inline method (that is, `logged`) and can be used by the macro implementation. + + +### Defining and Using Macros + +A key difference between inlining and macros is the way they are evaluated. +Inlining works by rewriting code and performing optimisations based on rules the compiler knows. +On the other hand, a macro executes user-written code that generates the code that the macro expands to. + +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**. +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 +> To allow defining and using macros in the same project, only those calls to macros are expanded, where the macro has already been compiled. +> For all other (unknown) macro calls, the compilation of the file is _suspended_. +> Suspended files are only compiled after all non suspended files have been successfully compiled. +> In some cases, you will have _cyclic dependencies_ that will block the completion of the compilation. +> To get more information on which files are suspended you can use the `-Xprint-suspension` compiler flag. + + +### Example: Statically Evaluating `power` with Macros + +Let us recall our definition of `power` from the section on [Inline][inline] that specialized the computation of `xⁿ` for statically known values of `n`. +```scala +inline def power(x: Double, inline n: Int): Double = + inline if n == 0 then 1.0 + else inline if n % 2 == 1 then x * power(x, n - 1) + else power(x * x, n / 2) +``` +In the remainder of this section, we will define a macro that computes `xⁿ` for a statically known values `x` and `n`. +While this is also possible purely with `inline`, implementing it with macros will illustrate a few things. + +```scala +inline def power(inline x: Double, inline n: Int) = + ${ evalPower('x, 'n) } + +def powerCode( + x: Expr[Double], + n: Expr[Int] +)(using Quotes): Expr[Double] = ... +``` + +## Simple Expressions + +We could implement `powerCode` as follows: +```scala +def pow(x: Double, n: Int): Double = + if n == 0 then 1 else x * pow(x, n - 1) + +def powerCode( + x: Expr[Double], + n: Expr[Int] +)(using Quotes): Expr[Double] = + val value: Double = pow(x.valueOrError, n.valueOrError) + Expr(value) +``` +Here, the `pow` operation is a simple Scala function that computes the value of `xⁿ`. +The interesting part is how we create and look into the `Expr`s. + + +### Creating Expression From Values + +Let's first look at `Expr.apply(value)`. Given a value of type `T`, this call will return an expression containing the code representing the given value (that is, of type `Expr[T]`). +The argument value to `Expr` is computed at compile-time, at runtime we only need to instantiate this value. + +Creating expressions from values works for all _primitive types_, _tuples_ of any arity, `Class`, `Array`, `Seq`, `Set`, `List`, `Map`, `Option`, `Either`, `BigInt`, `BigDecimal`, `StringContext`. +Other types can also work if a `ToExpr` is implemented for it, we will [see this later][quotes]. + + +### 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`. +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`. +This way we can report the error with a custom error message. + +```scala + ... + (x.value, n.value) match + case (Some(base), Some(exponent)) => + pow(base, exponent) + case (Some(_), _) => + report.error("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) +``` + +Alternatively, we can also use the `Expr.unapply` extractor + +```scala + ... + (x, n) match + case (Expr(base), Expr(exponent)) => + pow(base, exponent) + 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`. +Other types can also work if an `FromExpr` is implemented for it, we will [see this later][quotes]. + + +### Showing Expressions + +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: +```scala +def debugPowerCode( + x: Expr[Double], + n: Expr[Int] +)(using Quotes): Expr[Double] = + println( + s"""powerCode + | x := ${x.show} + | n := ${n.show}""".stripMargin) + val code = powerCode(x, n) + println(s" code := ${code.show}") + code +``` + + +### Working with Varargs + +Varargs in Scala are represented with `Seq`, hence when we write a macro with a _vararg_, it will be passed as an `Expr[Seq[T]]`. +It is possible to recover each individual argument (of type `Expr[T]`) using the `scala.quoted.Varargs` extractor. + +```scala +inline def sumNow(inline nums: Int*): Int = + ${ sumCode('nums) } + +def sumCode(nums: Expr[Seq[Int]])(using Quotes): Expr[Int] = + nums match + case Varargs(numberExprs) => // numberExprs: Seq[Expr[Int]] + val numbers: Seq[Int] = numberExprs.map(_.valueOrError) + Expr(numbers.sum) + case _ => report.error( + "Expected explicit argument" + + "Notation `args: _*` is not supported.", numbersExpr) +``` + +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. + +`Varargs` can also be used as a constructor, `Varargs(Expr(1), Expr(2), Expr(3))` will return a `Expr[Seq[Int]]`. +We will see how this can be useful later. + + +## Complex Expressions +So far, we have only seen how to construct and destruct expressions that correspond to simple values. +In order to work with more complex expressions, Scala 3 offers different metaprogramming facilities ranging from + +- additional constructors like `Expr.apply`, +- over [quoted pattern matching][quotes], +- to a full [reflection API][tasty]; + +each increasing in complexity and potentially losing safety guarantees. +It is generally recommended to prefer simple APIs over more advanced ones. +In the remainder of this section, we introduce some more additional constructors and destructors, +while subsequent chapters introduce the more advanced APIs. + +### Collections + +We have seen how to convert a `List[Int]` into an `Expr[List[Int]]` using `Expr.apply`. +How about converting a `List[Expr[Int]]` into `Expr[List[Int]]`? +We mentioned that `Varargs.apply` can do this for sequences -- likewise for other collection types, corresponding methods are available: + +* `Expr.ofList`: Transform a `List[Expr[T]]` into `Expr[List[T]]` +* `Expr.ofSeq`: Transform a `Seq[Expr[T]]` into `Expr[Seq[T]]` (just like `Varargs`) +* `Expr.ofTupleFromSeq`: Transform a `Seq[Expr[T]]` into `Expr[Tuple]` +* `Expr.ofTuple`: Transform a `(Expr[T1], ..., Expr[Tn])` into `Expr[(T1, ..., Tn)]` + +### Simple Blocks + +The constructor `Expr.block` provides a simple way to create a block of code `{ stat1; ...; statn; expr }`. +Its first arguments is a list with all the statements and the second argument is the expression at the end of the block. + +```scala +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) + else Expr.block(List(computation), Expr(true)) +``` + +The `Expr.block` constructor is useful when we want to generate code contanining several side effects. +The macro call `test(false, EXPRESSION)` will generate `{ EXPRESSION; true}`, while the call `test(true, EXPRESSION)` will result in `false`. + +### Simple Matching + +The method `Expr.matches` can be used to check if one expression is equal to another. +With this method we could implement an `value` operation for `Expr[Boolean]` as follows. + +```scala +def value(boolExpr: Expr[Boolean]): Option[Boolean] = + if boolExpr.matches(Expr(true)) then Some(true) + else if boolExpr.matches(Expr(false)) then Some(false) + else None +``` + +It may also be used to compare two user written expression. +Note, that `matches` only performs a limited amount of normalization and while for instance the Scala expression `2` matches the expression `{ 2 }`, this is _not the case_ for the expression `{ val x: Int = 2; x }`. + +### 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))`. +The subsequent section on [Quoted Code][quotes] presents quotes in more detail. + +[contributing]: {% link scala3/contribute-to-docs.md %} +[best-practices]: {% link _overviews/scala3-macros/best-practices.md %} +[compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[migration]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html +[faq]: {% link _overviews/scala3-macros/faq.md %} +[inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[migration-status]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/migration-status.html +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[tasty]: {% link _overviews/scala3-macros/tutorial/reflection.md %} diff --git a/_overviews/scala3-macros/tutorial/quotes.md b/_overviews/scala3-macros/tutorial/quotes.md new file mode 100644 index 0000000000..51ec424265 --- /dev/null +++ b/_overviews/scala3-macros/tutorial/quotes.md @@ -0,0 +1,392 @@ +--- +type: section +title: Quoted Code +num: 5 + +previous-page: macros +next-page: reflection +--- + +## Code blocks +A quoted code block `'{ ... }` is syntactically similar to a string quote `" ... "` with the difference that the first contains typed code. +To insert a code into other code we use the `$expr` or `${ expr }` where `expr` is of type `Expr[T]`. +Intuitively, the code directly within the quote is not executed now, while the code within the splices is evaluated and their results are then spliced into the surrounding expression. + +```scala +val msg = Expr("Hello") +val printHello = '{ print($hello) } +println(printHello.show) // print("Hello") +``` + +In general, the quote delays the execution while the splice makes it happen before the surrounding code. +This generalisation allows us to also give meaning to a `${ .. }` that is not within a quote, this evaluate the code within the splice at compile-time and place the result in the generated code. +Due to some technical considerations we only allow it directly within `inline` definitions that we call a [macro][macros]. + +It is possible to write a quote within a quote, but usually when we write macros we do not encounter such code. + +## Level consistency +One cannot simple write any arbitrary code within quotes and within splices. +A part of the program will live at compile-time and the other will live at runtime. +Consider the following ill-constructed code. + +```scala +def myBadCounter1(using Quotes): Expr[Int] = { + var x = 0 + '{ x += 1; x } +} +``` +The problem with this code is that `x` exists during compilation, but then we try to use it after the compiler has finished (maybe even in another machine). +Clearly it would be impossible to access its value and update it. + +Now consider the dual version, where we define the variable at runtime and try to access it at compile-time. +```scala +def myBadCounter2(using Quotes): Expr[Int] = '{ + var x = 0 + ${ x += 1; 'x } +} +``` +Clearly, this should work as the variable does not exist yet. +To make sure you can only write programs that do not contain these kinds of problems we restrict the set of references to variable and other definitions. + +We introduce _levels_ as a count of the number of quotes minus the number of splices surrounding an expression or definition. + +```scala +// level 0 +'{ // level 1 + var x = 0 + ${ // level 0 + x += 1 + 'x // level 1 + } +} +``` + +The system will allow at any level references to global definitions such as `println`, but will restrict references to local definitions. +A local definition can only be accessed if it is defined at the same level as its reference. +This will catch the errors in `myBadCounter1` and `myBadCounter2`. + +Even though we cannot refer to variable inside of a quote, we can still pass its current value to it by lifting the value to an expression using `Expr.apply`. + + +## Generics + +When using type parameters or other kinds of abstract types with quoted code we will need to keep track of some of these types explicitly. +Scala uses erased-types semantics for its generics. +This implies that types are removed from the program when compiling and the runtime does not have to track all types at runtime. + +Consider the following code +```scala +def evalAndUse[T](x: Expr[T]) = '{ + val x2: T = $x // error + ... // use x2 +} +``` + +Here we will get an error telling us that we are missing a contextual `Type[T]`. +Therefore we can easily fix it by writing +```scala +def evalAndUse[X](x: Expr[X])(using Type[X])(using Quotes) = '{ + val x2: X = $x + ... // use x2 +} +``` +This code will be equivalent to the more verbose +```scala +def evalAndUse[X](x: Expr[X])(using t: Type[X])(using Quotes) = '{ + val x2: t.T = $x + ... // use x2 +} +``` +Note that `Type` has a type member called `T` that refers to the type held within the `Type`, in this case `t.T` is `X`. +Note that even if we used it implicitly is better to keep it contextual as some changes inside the quote may require it. +The less verbose version is usually the best way to write the types as it is much simpler to read. +In some cases, we will not know statically the type within the `Type` and will need to use the `.T` to refer to it. + +When do we need this extra `Type` parameter? +* When a type is abstract and it is used in a level that is larger than the current level. + +When you add a `Type` contextual parameter to a method you will either get it from another context parameter or implicitly with a call to `Type.of`. +```scala +evalAndUse(Expr(3)) +// is equivalent to +evalAndUse[Int](Expr(3))(using Type.of[Int]) +``` +As you may have guessed, not every type is can be used in this `Type.of[..]` out of the box. +We cannot recover abstract types that have already been erased. +```scala +def evalAndUse[T](x: Expr[T])(using Quotes) = + given Type[T] = Type.of[T] // error + '{ + val x2: T = $x + ... // use x2 + } +``` + +But we can write more complex types that depend on these abstract types. +For example, if we look for or construct explicitly a `Type[List[T]]`, then the system will require a `Type[T]` in the current context to compile. + +Good code should only add `Type` to the context parameters and never use them explicitly. +Explicit use is useful while debugging at the cost of conciseness and clarity. + + +## ToExpr +The `Expr.apply` method uses intances of `ToExpr` to generate an expression that will create a copy of the value. +```scala +object Expr: + def apply[T](x: T)(using Quotes, ToExpr[T]): Expr[T] = + summon[ToExpr[T]].apply(x) +``` + +`ToExpr` is defined as follows: +```scala +trait ToExpr[T]: + def apply(x: T)(using Quotes): Expr[T] +``` + +The `ToExpr.apply` method will take a value `T` and generate code that will construct a copy of this value at runtime. + +We can define our own `ToExpr`s like: +```scala +given ToExpr[Boolean] with { + def apply(x: Boolean)(using Quotes) = + if x then '{true} + else '{false} +} + +given ToExpr[StringContext] with { + def apply(x: StringContext)(using Quotes) = + val parts = Varargs(stringContext.parts.map(Expr(_))) + '{ 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 a varargs. + +## Quoted patterns +Quotes can also be used to check if an expression is equivalent to another or deconstruct an expression into it parts. + + +### Matching exact expression + +The simples thing we can do is to check if an expression matches another know expression. +Bellow we show how we can match some expressions using `case '{...} =>` + +```scala +def valueOfBoolean(x: Expr[Boolean])(using Quotes): Option[Boolean] = + x match + case '{ true } => Some(true) + case '{ false } => Some(false) + case _ => None + +def valueOfBooleanOption(x: Expr[Option[Boolean]])(using Quotes): Option[Option[Boolean]] = + x match + case '{ Some(true) } => Some(Some(true)) + case '{ Some(false) } => Some(Some(false)) + case '{ None } => Some(None) + case _ => None +``` + +### Matching partial expression + +To make thing more compact, we can also match patially the expression using a `$` to match arbitrarry code and extract it. + +```scala +def valueOfBooleanOption(x: Expr[Option[Boolean]])(using Quotes): Option[Option[Boolean]] = + x match + case '{ Some($boolExpr) } => Some(valueOfBoolean(boolExpr)) + case '{ None } => Some(None) + case _ => None +``` + +### Matching types of expression + +We can also match agains code of an arbitrary type `T`. +Bellow we match agains `$x` of type `T` and we get out an `x` of type `Expr[T]`. + +```scala +def exprOfOption[T: Type](x: Expr[Option[T]])(using Quotes): Option[Expr[T]] = + x match + case '{ Some($x) } => Some(x) // x: Expr[T] + case '{ None } => Some(None) + case _ => None +``` + +We can also check for the type of an expression + +```scala +def valueOf(x: Expr[Any])(using Quotes): Option[Any] = + x match + case '{ $x: Boolean } => valueOfBoolean(x) // x: Expr[Boolean] + case '{ $x: Option[Boolean] } => valueOfBooleanOption(x) // x: Expr[Option[Boolean]] + case _ => None +``` +Or similarly for an some subexpression + +```scala +case '{ Some($x: Boolean) } => // x: Expr[Boolean] +``` + +### Matching reciver of methods + +When we want to match the receiver of a method we need to explicitly state its type + +```scala +case '{ ($ls: List[Int]).sum } => +``` + +If we would have written `$ls.sum` we would not have been able to know the type of `ls` and which `sum` method we are calling. + +Another common case where we need type annotations is for infix operations. +```scala +case '{ ($x: Int) + ($y: Int) } => +case '{ ($x: Double) + ($y: Double) } => +case ... +``` + +### Matching function expressions + +*Coming soon* + +### Matching types + +*Coming soon* + +## FromExpr + +The `Expr.value`, `Expr.valueOrError` `Expr.unapply` method uses intances of `FromExpr` to to extract the value if possible. +```scala +extension [T](expr: Expr[T]): + def value(using Quotes)(using fromExpr: FromExpr[T]): Option[T] = + fromExpr.unapply(expr) + + def valueOrError(using Quotes)(using fromExpr: FromExpr[T]): T = + fromExpr.unapply(expr).getOrElse(eport.throwError("...", expr)) +end extension + +object Expr: + def unapply[T](expr: Expr[T])(using Quotes)(using fromExpr: FromExpr[T]): Option[T] = + fromExpr.unapply(expr) +``` + +`FromExpr` is defined as follows: +```scala +trait FromExpr[T]: + def unapply(x: Expr[T])(using Quotes): Option[T] +``` + +The `FromExpr.unapply` method will take a value `T` and generate code that will construct a copy of this value at runtime. + +We can define our own `FromExpr`s like: +```scala +given FromExpr[Boolean] with { + def unapply(x: Expr[Boolean])(using Quotes): Option[Boolean] = + x match + case '{ true } => Some(true) + case '{ false } => Some(false) + case _ => None +} + +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 _ => None + } +} +``` +Note that we handled two cases for the `StringContext`. +As it is a `case class` it can be created with the `new StringContext` or with the `StringContext.apply` in the companion object. +We also used the `Varargs` extractor to match the arguments of type `Expr[Seq[String]]` into a `Seq[Expr[String]]`. +Then we used the `Exprs` to match known constants in the `Seq[Expr[String]]` to get a `Seq[String]`. + + +## The Quotes +The `Quotes` is the main entry point for the creation of all quotes. +This context is usually just passed around through contextual abstractions (`using` and `?=>`). +Each quote scope will provide have its own `Quotes`. +New scopes are introduced each time a splice is introduced `${...}`. +Though it looks like a splice takes an expression as argument, it actually takes a `Quotes ?=> Expr[T]`. +Therefore we could actually write it explicitly as `${ (using q) => ... }`, this might be useful when debugging to avoid generated names for these scopes. + +The method `scala.quoted.quotes` provides a simple way to use the current `Quotes` without naming it. +It is usually imported along with the `Quotes` using `import scala.quoted._`. + +```scala +${ (using q1) => body(using q1) } +// equivalent to +${ body(using quotes) } +``` +If you explicitly name a `Quotes` `quotes` you will shadow this definition. + +When we write a top level splice in a macro we are calling something similar to the following definition. +This splice will provide the initial `Quotes` associated with the macro expansion. +```scala +def $[T](x: Quotes ?=> Expr[T]): T = ... +``` + +When we have a splice within a quote, the inner quote context will depend on the outer one. +This link is represented using the `Quotes.Nested` type. +Users of quotes will almost never need to use `Quotes.Nested`. +These details are only useful for advanced macros that will inspect code and may encounter details of quotes and splices. + +```scala +def f(using q1: Quotes) = '{ + ${ (using q2: q1.Nested) ?=> + ... + } +} +``` + +We can imagine that a nested splice is like the following method, where `ctx` is the context received by the surrounding quote. +```scala +def $[T](using q: Quotes)(x: q.Nested ?=> Expr[T]): T = ... +``` + +## β-reduction +When we have a lambda applied to an argument in a quote `'{ ((x: Int) => x + x)(y) }` we do not reduce it within the quote, the code is kept as is. +There is an optimisation that β-reduce all lambdas directly applied to parameters to avoid the creation of the closure. +This will not be visible from the quotes perspective. + +Sometime it is useful to perform this β-reduction on the quotes directly. +We provide the function `Expr.betaReduce[T]` that receives an `Expr[T]` and β-reduce if it contains a directly applied lambda. + +```scala +Expr.betaReduce('{ ((x: Int) => x + x)(y) }) // returns '{ val x = y; x + x } +``` + + +## Summon values +There are two ways to summon values in a macro. +The first is to have a `using` parameter in the inline method that is passed explicitly to the macro implementation. + +```scala +inline def setFor[T](using ord: Ordering[T]): Set[T] = + ${ setForCode[T]('ord) } + +def setForCode[T: Type](ord: Expr[Ordering[T]])(using Quotes): Expr[Set[T]] = + '{ TreeSet.empty[T](using $ord) } +``` + +In this scenario, the context parameter is found before the macro is expanded. +If not found, the macro will not expand. + +The second way is using `Expr.summon`. +This allows to programatically search for distinct given expressions. +The following example is similar to the previous example. + +```scala +inline def setFor[T]: Set[T] = + ${ setForCode[T] } + +def setForCode[T: Type](using Quotes): Expr[Set[T]] = + import scala.collection.immutable._ + Expr.summon[Ordering[T]] match + case Some(ord) => '{ TreeSet.empty[T](using $ord) } + case _ => '{ HashSet.empty[T] } +``` + +The difference is that in this scenario we do start expanding the macro before the implicit search failure and we can write arbitrary code to handle the case where it is not found. +Here we used `HashSet` and another valid implementation that does not need the `Ordering`. + +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} diff --git a/_overviews/scala3-macros/tutorial/reflection.md b/_overviews/scala3-macros/tutorial/reflection.md new file mode 100644 index 0000000000..aeef82f189 --- /dev/null +++ b/_overviews/scala3-macros/tutorial/reflection.md @@ -0,0 +1,41 @@ +--- +type: section +title: Tasty Reflection +num: 6 + +previous-page: quotes +--- + +The reflection API provides a more complex and comprehensive view on the structure of the code. +It provides a view on the *Typed Abstract Syntax Trees* **TASTy** and their properties such as types, symbols, positions and comments. + +## How to use the API + +Accessing this API need and import that depends the current `Quotes`. +We can use `scala.quoted.quotes` to import it. + +```scala +def pow(x: Expr[Int])(using Quotes): Expr[Int] = { + import quotes.tasty._ // Import Tree, Type, Symbol, Position, ..... + ... +} +``` + +This will import all the types and modules (with extension methods) of the API. + +The full imported API can be found here: [Reflection](https://dotty.epfl.ch/api/scala/tasty/Reflection.html) + +For example to find what is a `Term`, we can see in the hierarchy that it is a subtype of `Statement` which is a subtype of `Tree`. +If we look into the [`TermMethods`](https://dotty.epfl.ch/api/scala/tasty/Reflection/TermMethods.html) we will find all the extension methods that are defined for `Term` such as `Term.tpe` which returns a `Type`. +As it is a subtype of `Tree` we can also look into the [`TreeMethods`](http://dotty.epfl.ch/api/scala/tasty/Reflection/TreeMethods.html) to find more methods such as `Tree.pos`. +Each type also a module with some _static-ish_ methods, for example in the [TypeModule](http://dotty.epfl.ch/api/scala/tasty/Reflection/TypeModule.html) we can find the method `Type.of[T]` with will create an instance of `Type` containing `T`. + + +## Relation with expressions +<!-- Term vs Expr --> +<!-- Safty --> +*Coming soon* + + +## Examples +*Coming soon* diff --git a/_sass/components/wip-notice.scss b/_sass/components/wip-notice.scss new file mode 100644 index 0000000000..b4a011e9fa --- /dev/null +++ b/_sass/components/wip-notice.scss @@ -0,0 +1,11 @@ +.wip-notice { + position: relative; + top: 1em; + padding: 1em; + background: rgba(255,255,255,0.7); + + a { + color: $brand-primary; + font-weight: 700; + } +} diff --git a/_sass/layout/doc-navigation.scss b/_sass/layout/doc-navigation.scss index 1d11f17010..7fa75f5d6a 100644 --- a/_sass/layout/doc-navigation.scss +++ b/_sass/layout/doc-navigation.scss @@ -4,6 +4,7 @@ $nav-height: 46px; + .doc-navigation { padding: 10px 20px; @include display(flex); @@ -18,6 +19,23 @@ $nav-height: 46px; display: none; } } + .doc-language-version { + font-size: 1.6em; + font-family: $heading-font-family; + font-weight: bold; + color: rgb(134,161,166); + @include bp(large) { + display: none; + } + } + a, + a:hover, + a:active, + a:focus, + a:hover, + a.active { + text-decoration: none; + } } .navigation-ellipsis { display: none; diff --git a/_sass/layout/documentation.scss b/_sass/layout/documentation.scss index f1a16f69d5..6954645f23 100644 --- a/_sass/layout/documentation.scss +++ b/_sass/layout/documentation.scss @@ -17,15 +17,17 @@ max-height: 160px; margin-top: 30px; margin-bottom: 100px; - @include bp(medium) { - margin-bottom: 0; - } + // this causes issues on mobile with the header being hidden behind + // the contents: + // @include bp(medium) { + // margin-bottom: 0; + //} .supertitle { text-transform: uppercase; font-weight: $font-black; font-size: 20px; - color: darken(#53b6d3, 15%); + color: rgba(0, 0, 0, 0.2); } h1 { @@ -64,3 +66,67 @@ a.new-version-notice { 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); + } +} + +.landing-page .table-of-content .wrap { + + position: relative; + + h5 { + margin: 0; + } + + .language-header { + padding: 30px; + background: $brand-tertiary; + + h1 { + font-size: 1.875rem; + font-family: $base-font-family; + text-transform: uppercase; + text-shadow: $text-shadow; + color: #fff; + } + } + + .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; + + &:hover { + cursor: pointer; + background: #E5EAEA; + color: rgba(0, 0, 0, 0.4); + } + } + } + + &:first-of-type { + margin-bottom: 20px; + } +} + +.landing-page .table-of-content .wrap.scala3 { + .language-header { + background: $brand-tertiary-dotty; + } +} diff --git a/_sass/layout/title-page.scss b/_sass/layout/title-page.scss index 44a33f8621..e0a5353faf 100755 --- a/_sass/layout/title-page.scss +++ b/_sass/layout/title-page.scss @@ -2,6 +2,10 @@ //------------------------------------------------ //------------------------------------------------ +.scala3 .title-page { + background: $brand-tertiary-dotty; +} + .title-page { background: $brand-tertiary; // height: 200px; diff --git a/_sass/layout/toc.scss b/_sass/layout/toc.scss index a5ab5c19b3..0e5e7f71d1 100644 --- a/_sass/layout/toc.scss +++ b/_sass/layout/toc.scss @@ -83,11 +83,16 @@ } } +// disable floating for the book, since the TOC can become very large +.scala3 .sidebar-toc-wrapper { + position: relative; +} + .book #toc { ul { margin-top: 5px; margin-bottom: 10px; - margin-left: 20px; + margin-left: 0px; a { color: $base-link-color; @@ -104,7 +109,7 @@ margin-bottom: 0px; a { - line-height: 1; + line-height: 1.3; font-weight: normal; } diff --git a/_sass/utils/_variables.scss b/_sass/utils/_variables.scss index b40b379981..33277a1b03 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-dotty: #E45C77; //------------------------------------------------- $gray-darker: #002B36; $gray-dark: #073642; @@ -15,6 +16,7 @@ $gray-li: #244E58; $gray-light: #E5EAEA; $gray-lighter: #F0F3F3; $apple-blue: #6dccf5; + //------------------------------------------------- $headings-font-color: $gray-dark; $base-font-color: #4A5659; diff --git a/index.md b/index.md index 570a7959e5..f358009d2c 100644 --- a/index.md +++ b/index.md @@ -1,14 +1,45 @@ --- -layout: inner-page-documentation +layout: documentation title: Documentation -languages: [ja, zh-cn, ru] namespace: root partof: documentation -discourse: true -# Content masthead links more-resources-label: More Resources -sections: +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: "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 migrate from Scala 2 to Scala 3." + icon: "fa fa-suitcase" + link: https://scalacenter.github.io/scala-3-migration-guide/ + - 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-text" + link: https://dotty.epfl.ch/api/index.html + - title: "Language Reference" + description: "The Scala 3 language reference." + icon: "fa fa-book" + link: https://dotty.epfl.ch/docs/reference/overview.html + +scala2-sections: - title: "First Steps..." links: - title: "Getting Started" @@ -66,5 +97,4 @@ sections: description: "The Scala Platform Process. Community-driven library evolution." icon: "fa fa-users" link: https://platform.scala-lang.org - --- diff --git a/resources/css/style.scss b/resources/css/style.scss index 7d297f71ea..57b121deba 100755 --- a/resources/css/style.scss +++ b/resources/css/style.scss @@ -71,3 +71,4 @@ @import 'components/tag'; @import 'components/search'; @import 'components/dropdown'; +@import 'components/wip-notice'; diff --git a/resources/images/scala3-book/hierarchy.dot b/resources/images/scala3-book/hierarchy.dot new file mode 100644 index 0000000000..24e6c2a896 --- /dev/null +++ b/resources/images/scala3-book/hierarchy.dot @@ -0,0 +1,37 @@ +digraph unix { + rankdir = BT; + size="6,6"; + node [color=lightblue2, style=filled, fontname="Consolas"]; + + + {rank=same; "AnyVal"; "AnyRef / Object"} + + {rank=same; + "Unit"; "Boolean"; "Int"; "... (value types)"; + "String"; "List[Int]"; "... (reference types)" + } + + "Matchable" -> "Any"; + "AnyVal" -> "Matchable"; + "AnyRef / Object" -> "Matchable"; + + "Unit" -> "AnyVal"; + "Boolean" -> "AnyVal"; + "Int" -> "AnyVal"; + "... (value types)" -> "AnyVal"; + + "String" -> "AnyRef / Object"; + "List[Int]" -> "AnyRef / Object"; + "... (reference types)" -> "AnyRef / Object"; + + "Null" -> "String"; + "Null" -> "List[Int]"; + "Null" -> "... (reference types)"; + + "Nothing" -> "Null"; + "Nothing" -> "Unit"; + "Nothing" -> "Boolean"; + "Nothing" -> "Int"; + "Nothing" -> "... (value types)"; + +} diff --git a/resources/images/scala3-book/hierarchy.svg b/resources/images/scala3-book/hierarchy.svg new file mode 100644 index 0000000000..013848749e --- /dev/null +++ b/resources/images/scala3-book/hierarchy.svg @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.44.1 (20200629.0846) + --> +<!-- Title: unix Pages: 1 --> +<svg width="432pt" height="226pt" + viewBox="0.00 0.00 432.00 226.05" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(0.56 0.56) rotate(0) translate(4 400)"> +<title>unix + + + +AnyVal + +AnyVal + + + +Matchable + +Matchable + + + +AnyVal->Matchable + + + + + +AnyRef / Object + +AnyRef / Object + + + +AnyRef / Object->Matchable + + + + + +Unit + +Unit + + + +Unit->AnyVal + + + + + +Boolean + +Boolean + + + +Boolean->AnyVal + + + + + +Int + +Int + + + +Int->AnyVal + + + + + +... (value types) + +... (value types) + + + +... (value types)->AnyVal + + + + + +String + +String + + + +String->AnyRef / Object + + + + + +List[Int] + +List[Int] + + + +List[Int]->AnyRef / Object + + + + + +... (reference types) + +... (reference types) + + + +... (reference types)->AnyRef / Object + + + + + +Any + +Any + + + +Matchable->Any + + + + + +Null + +Null + + + +Null->String + + + + + +Null->List[Int] + + + + + +Null->... (reference types) + + + + + +Nothing + +Nothing + + + +Nothing->Unit + + + + + +Nothing->Boolean + + + + + +Nothing->Int + + + + + +Nothing->... (value types) + + + + + +Nothing->Null + + + + + diff --git a/resources/js/functions.js b/resources/js/functions.js index 046ec1a7f5..ed5670f578 100644 --- a/resources/js/functions.js +++ b/resources/js/functions.js @@ -275,7 +275,7 @@ $(document).ready(function() { $(document).ready(function() { if ($("#sidebar-toc").length) { $('#toc').toc({ - exclude: 'h1, h5, h6', + exclude: 'h1, h4, h5, h6', context: '.toc-context', autoId: true, numerate: false diff --git a/scala3/contribute-to-docs.md b/scala3/contribute-to-docs.md new file mode 100644 index 0000000000..5675ca2662 --- /dev/null +++ b/scala3/contribute-to-docs.md @@ -0,0 +1,87 @@ +--- +layout: singlepage-overview +overview-name: "Scala 3 Documentation" +title: Contributing to the Docs +--- +## Overview +There are several ongoing efforts to produce high quality documentation for +Scala 3. In particular there are the following documents: + +- Scala 3 book +- Macros tutorial +- Migration guide +- Scala 3 language reference + +We welcome contributions from the community to every aspect of the documentation. + + +### How can I contribute? +In general, there is many different ways you could help us: + +- **Confused about something in any of the docs?** Open an issue. +- **Found something not up-to-date?** Open an issue or create a PR. +- **Typos and other small text enhancements?** Create a PR. +- **Want to add something new or make larger changes?** Great! Please open an issue and let us discuss this. + +Typically, each of the different documentation projects contain links (and so does this document, in the table-of-contents pane) to edit and improve them. Additionally, below we provide you with the necessary information to get started. + +## Scala 3 Book +The [Scala 3 Book][scala3-book] is being written by Alvin Alexander and provides an overview over all the important features of Scala 3. It targets readers, which are new to Scala. + +- [Sources](https://github.com/scala/docs.scala-lang/tree/master/_overviews/scala3-book) +- [Issues](https://github.com/scala/docs.scala-lang/issues) + +## Macros Tutorial +The [Macros Tutorial](/scala3/guides/macros) is being written by Nicolas Stucki and contains detailed information about macros in Scala 3 and best-practices. + +- [Sources](https://github.com/scala/docs.scala-lang/tree/master/_overviews/scala3-macros) +- [Issues](https://github.com/scala/docs.scala-lang/issues) + +### Status + +This tutorial will cover all that is needed to start writing macros. +The first part (M-1) will cover `inline`, the second part (M-2) will cover macros and quoted expressions and the third part (M-3) will cover how to use the TASTy reflection API. +Additionally, we will include a cross-compilation/migration guide (M-A). + +- **M-A Cross-compilation** (in progress) + * Update/complete [migration status][migration-status] (help needed) + * Write [cross-compilation][cross-compilation] + * Create simple example projects in SBT and Mill +- **M-1 Inline tutorial** + * Write [inline tutorial][inline] (in progress) + * Write [scala.compiletime tutorial][compiletime]: Each feature or group of featurs will need a section +- **M-2 Macro tutorial** (not started) + * Write [macros tutorial][macros]: Understanding relation between inline and quoted expressions + * Write [quoted expressions tutorial][quotes]: Dive deep into all quoted expression features +- **M-3 TASTy reflect API** (not started) + * Write [TASTy reflection tutorial][tasty] (not started): How to access it and the relation with quoted expressions + * Complete [TASTy reflect API][reflection-api] docs (help needed) + +## Migration Guide +The [Scala 3 Migration Guide](https://scalacenter.github.io/scala-3-migration-guide/) +contains an comprehensive overview over compatibility between Scala 2 and Scala 3, +a tour presenting the migration tools, and detailed migration guides. + +- [Contribution Overview](https://scalacenter.github.io/scala-3-migration-guide/docs/contributing.html) +- [Source](https://github.com/scalacenter/scala-3-migration-guide) +- [Issues](https://github.com/scalacenter/scala-3-migration-guide/issues) + + +## Scala 3 Language Reference +The [Dotty reference](https://dotty.epfl.ch/docs/reference/overview.html) will evolve into the Scala 3 language, containing a formal presentation and detailed technical information about the various features of the language. + +- [Sources](https://github.com/lampepfl/dotty/tree/master/docs/docs/reference) +- [Issues](https://github.com/lampepfl/dotty/issues) + + +[scala3-book]: {% link _overviews/scala3-book/introduction.md %} + +[best-practices]: {% link _overviews/scala3-macros/best-practices.md %} +[compiletime]: {% link _overviews/scala3-macros/tutorial/compiletime.md %} +[cross-compilation]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/migration-tutorial.html#cross-building +[inline]: {% link _overviews/scala3-macros/tutorial/inline.md %} +[macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} +[migration-status]: https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html#macro-libraries +[quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} +[tasty]: {% link _overviews/scala3-macros/tutorial/reflection.md %} +[reflection-api]: https://dotty.epfl.ch/api/scala/quoted.html diff --git a/scala3/getting-started.md b/scala3/getting-started.md new file mode 100644 index 0000000000..ebaaf7ad20 --- /dev/null +++ b/scala3/getting-started.md @@ -0,0 +1,190 @@ +--- +layout: singlepage-overview +title: Getting Started with Scala 3 +--- + + + +## Try Scala without installing anything + +To start experimenting with Scala 3 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. + + + +## 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 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 +- The [sbt](https://www.scala-sbt.org) and [mill](https://www.lihaoyi.com/mill) build tools +- [Ammonite](https://ammonite.io), an enhanced REPL +- [scalafmt](https://scalameta.org/scalafmt), the Scala formatter +- The [Coursier CLI](https://get-coursier.io/docs/cli-overview), to install further Scala-based applications +- The `scala` and `scalac` command-line tools + +For more information, read the [coursier-cli documentation](https://get-coursier.io/docs/cli-overview). + + +### ... or manually + +You only need two tools to compile, run, test, and package a Scala project: Java 8 or 11, and sbt. +To install these manually: + +1. 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 + +To create a project, you can either use a command-line tool 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 this command `sbt new scala/hello-world.g8`. +This pulls the ['hello-world' template][template-url] from GitHub. +It also creates 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 + src/main/scala/ (all of your Scala code goes here) + Main.scala (entry point of program) + build.sbt (sbt’s build definition file) +``` +The scala file `Main.scala` in `src/main/scala` is all we need for now. + +More documentation about sbt can be found in the [Scala Book](/scala3/book/scala-tools.html) and in the official sbt [documentation](https://www.scala-sbt.org/1.x/docs/index.html) + + +{% comment %} +### With IntelliJ IDEA + +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) +{% endcomment %} + + +## Open the “Hello, world” project + +Let’s use an IDE to open the project. +The most popular ones are IntelliJ IDEA and VS Code. +They both offer rich IDE features, but you can still use [many other editors.](https://scalameta.org/metals/docs/editors/overview.html) + +### Using IntelliJ IDEA + +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 VS Code with Metals + +1. Download [VS Code](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 your *build.sbt* file. + 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. +(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).) + + + +### View 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 the “Hello, world” project + +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 also generates a `target` directory for its own use, 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 + +Now that you’ve created a first “Hello, world” example with Scala 3, you’re ready for some next steps. +Consider checking out: + +- [The Scala 3 Book](/scala3/book/introduction.html), which provides a set of short lessons introducing Scala’s main features +- [The migration guide](https://scalacenter.github.io/scala-3-migration-guide/) helps you to migrate your existing Scala 2 code base to Scala 3. + +When you want to connect with other Scala users, there are several mailing lists and real-time chat rooms available. +Check out our [Scala community page](https://scala-lang.org/community/) for a list of these resources, and for where to reach out for help. + + + + + + + + + + + +[template-url]: https://github.com/scala/hello-world.g8 diff --git a/scala3/guides.md b/scala3/guides.md new file mode 100644 index 0000000000..b3aad16645 --- /dev/null +++ b/scala3/guides.md @@ -0,0 +1,61 @@ +--- +layout: inner-page-parent +title: Guides on Scala 3 + +guides: + - title: "Migration from Scala 2 to Scala 3" + icon: suitcase + url: "https://scalacenter.github.io/scala-3-migration-guide" + 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 +--- + +
+
+
+
+

Overviews and Guides

+

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

+
+ {% for overview in page.guides %} +
+
+
+ {% if overview.icon %} +
+
+
+ {% endif %} +

{{ overview.title }}

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

{{ overview.description }}

{% endif %} + {% if overview.subdocs %} + Contents + + {% endif %} +
+
+ +
+ {% endfor %} +
+
+
+
+
diff --git a/scala3/index.md b/scala3/index.md new file mode 100644 index 0000000000..c6e7eafbdb --- /dev/null +++ b/scala3/index.md @@ -0,0 +1,44 @@ +--- +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 migrate from Scala 2 to Scala 3." + icon: "fa fa-suitcase" + link: https://scalacenter.github.io/scala-3-migration-guide/ + - 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-text" + link: https://dotty.epfl.ch/api/index.html + - title: "Language Reference" + description: "The Scala 3 language reference." + icon: "fa fa-book" + link: https://dotty.epfl.ch/docs/reference/overview.html + +--- diff --git a/scala3/new-in-scala3.md b/scala3/new-in-scala3.md new file mode 100644 index 0000000000..42ffc2a06e --- /dev/null +++ b/scala3/new-in-scala3.md @@ -0,0 +1,136 @@ +--- +layout: singlepage-overview +title: New in Scala 3 +--- +The upcoming, exciting new version of Scala 3 brings many improvements and +new features. Here we provide you with a quick overview of the most important +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. + +## That's new in Scala 3 +Scala 3 is a complete overhaul of the Scala language. At its core, many aspects +of the type-system have been change to be more principled. While this also +brings exciting new features along (like union types), first and foremost, it +means that the type-system gets (even) less in your way and for instance +[type-inference][type-inference] and overload resolution are much improved. + +### New & Shiny: The Syntax +Besides many (minor) cleanups, the Scala 3 syntax offers the following improvements: + +- A new "quiet" syntax for control structures like `if`, `while`, and `for` ([new control syntax][syntax-control]) +- The `new` keyword is optional (_aka_ [creator applications][creator]) +- [Optional braces][syntax-indentation] that supports a distraction-free, indentation sensitive style of programming +- Change of [type-level wildcards][syntax-wildcard] from `_` to `?`. +- Implicits (and their syntax) have been [heavily revised][implicits]. + +### Opinionated: Contextual Abstractions +One underlying core concept of Scala was (and still is to some degree) to +provide users with a small set of powerful features that can be combined to +great (and sometimes even unforeseen) expressivity. For example, the feature of _implicits_ +has been used to model contextual abstraction, to express type-level +computation, model type-classes, perform implicit coercions, encode +extension methods, and many more. +Learning from these use cases, Scala 3 takes a slightly different approach +and focuses on **intent** rather than **mechanism**. +Instead of offering one very powerful feature, Scala 3 offers multiple +tailored language features, allowing programmers to directly express their intent: + +- **Abtracting over contextual information**. [Using clauses][contextual-using] 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][contextual-givens] 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][contextual-extension] are now directly built into the language, leading to better error messages and improved type inference. + +- **Viewing one type as another**. Implicit conversion have been [redesigned][contextual-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. They are an important tool for library authors and allow to express concise domain specific languages. + +- **Actionable feedback from the compiler**. In case an implicit parameter can not be resolved by the compiler, it now provides you [import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) that may fix the problem. + +### Say What You Mean: Type System Improvements +Besides greatly improved type inference, the Scala 3 type system also offers many new features, giving you powerful tools to statically express invariants in the types: + +- **Enumerations**. [Enums][enums] have been redesigned to blend well with case classes and form the new standard to express [algebraic data types][enums-adts]. + +- **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 a [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. + +- **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! + +- **Polymorphic function types**. Like with dependent function types, Scala 2 supported methods that allow type parameters, but does not allow programmers to abstract over those methods. In Scala 3, [polymorphic function types][types-polymorphic] like `[A] => List[A] => List[A]` can abstract over functions that take _type arguments_ in addition to their value arguments. + +- **Type lambdas**. What needed to be expressed using a [compiler plugin](https://github.com/typelevel/kind-projector) in Scala 2 is now a first-class feature in Scala 3: Type lambdas are type level functions that can be pass as (higher-kinded) type arguments without requiring an auxiliary type definition. + +- **Match types**. Instead of encoding type-level computation using implicit resolution, Scala 3 offers direct support for [matching on types][types-match]. Integrating type-level computation into the type checker enables improved error messages and removes the need for complicated encodings. + + +### Re-envisioned: Object-Oriented Programming +Scala has always been at the frontier between functional programming and object-oriented programming -- +and Scala 3 pushes boundaries in both directions! The above mentioned type system changes and the redesign of contextual abstractions make _functional programming_ easier than before. +At the same time, the following novel features enable well-structured _object-oriented designs_ and support best practices. + +- **Pass it on**. Traits move closer to classes and now can also take [parameters][oo-trait-parameters], making them even more powerful as a tool for modular software decomposition. +- **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 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. + + +### Batteries Included: Metaprogramming +While macros in Scala 2 were an experimental feature only, Scala 3 comes with a powerful arsenal of tools for metaprogramming. +The [macro tutorial]({% link _overviews/scala3-macros/index.md %}) contains detailed information on the different facilities. In particular, Scala 3 offers the following features for metaprogramming. + +- **Inline**. As the basic starting point, the [inline feature][meta-inline] allows values and methods to be reduced at compile time. This simple feature already covers many use-cases and at the same time provides the entry point for more advanced features. +- **Compile-time operations**. The package [`scala.compiletime`][meta-compiletime] contains additional functionality that can be used to implement inline methods. +- **Quoted code blocks**. Scala 3 adds the new feature of [quasi-quotation][meta-quotes] for code, providing a convenient high-level interface to construct and analyse code. Constructing code for adding one and one is as easy as `'{ 1 + 1 }`. +- **Reflection API**. For more advanced use cases [TASTy reflect][meta-reflection] provides more detailed control to inspect and generate program trees. + +If you want to learn more about meta programming in Scala 3, we invite you to take our [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]: https://scalacenter.github.io/scala-3-migration-guide + +[implicits]: {{ site.scala3ref }}/contextual/motivation.html +[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/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 }}/other-new-features/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