Skip to content

Commit

Permalink
Add more guidelines for labelling code examples for Scala 2/3 (#2768)
Browse files Browse the repository at this point in the history
* Clarify how to label entire pages of documentation that are specific to a Scala version

* Add a note that all the code examples assume a specific version of Scala in the “version-specific-notice”

* Use the new way of labelling pages in Multiversal Equality

* Use the new way of labelling pages in Given Instances and Using Clauses, and adjust the “version-specific-notice”.

* Label “types-union”

* Also label the chinese version of types-union.md

* Label the page types-adts-gadts.md as “New in Scala 3”
  • Loading branch information
julienrf authored Apr 13, 2023
1 parent 86fe255 commit 0cb1c34
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 51 deletions.
8 changes: 6 additions & 2 deletions _includes/version-specific-notice.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
<i class="fa fa-info"></i><span style="margin-left: 0.5rem">
{% if include.language == 'scala3' %}
This doc page is specific to Scala 3,
and may cover new concepts not available in Scala 2.
and may cover new concepts not available in Scala 2. Unless
otherwise stated, all the code examples in this page assume
you are using Scala 3.
{% else if include.language == 'scala2' %}
This doc page is specific to features shipped in Scala 2,
which have either been removed in Scala 3 or replaced by an alternative.
which have either been removed in Scala 3 or replaced by an
alternative. Unless otherwise stated, all the code examples
in this page assume you are using Scala 2.
{% endif %}
</span>
</blockquote>
Expand Down
57 changes: 53 additions & 4 deletions _overviews/contribute/add-guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ clarifications, etc.
## Code blocks

It's common for various kinds of documents to require code examples.
You can contribute code in a markdown document by either
You can contribute code in a Markdown document by either
- in-line by putting backticks around it,
- surrounding by triple backticks,
- or indenting it by 4 spaces, e.g.:
Expand All @@ -92,6 +92,7 @@ You can contribute code in a markdown document by either
inline example: `val x = 23`
block example:
```scala
println("hello")
```
Expand All @@ -103,9 +104,31 @@ indented example:

### Scala 2 vs Scala 3

Sometimes you would like to compare between Scala 2 and Scala 3 in a document, for example in
our [Hello World][hello-world] chapter of the Scala Book. Here is an example of how you
can generate the same tabs in markdown with the `tabs` directive and class `tabs-scala-version`:
Our goal is to have a unified documentation that covers both Scala 2 and Scala 3. In many cases, the
code examples are the same in both Scala 2 and Scala 3, but sometimes there are some syntactic
differences. In some less common cases, a page may explain a concept that is new in Scala 3 and has
no equivalent in Scala 2, or a concept that has been removed in Scala 3. In all the cases, the
documentation should clearly "label" the code examples so that the readers know in which versions
of Scala they are valid.

The following sections explain how to properly "label" the code examples.

#### Labelling the code snippets of a page documenting a concept available in both Scala 2 and Scala 3

When the content of a page not specific to Scala 2 or Scala 3, like for example our
[Hello World][hello-world] chapter of the Scala Book, the code snippets should show both the
Scala 2 and Scala 3 syntax. We achieve this by labelling the code snippets in tabs according
to the following rules:

- if the idiomatic syntax is different in Scala 2 and Scala 3, we create two tabs,
“Scala 2” and “Scala 3”, showing the corresponding syntax
- if the code snippet is idiomatic in both Scala 2 and Scala 3, we create a single tab,
“Scala 2 and 3”
- if the code snippet is valid only in Scala 2 or Scala 3, we create a single tab,
“Scala 2 Only” or “Scala 3 Only”

Here is an example of how you
can generate such tabs in Markdown with the `tabs` directive and class `tabs-scala-version`:

<!-- {% raw %} -->
~~~liquid
Expand Down Expand Up @@ -161,6 +184,32 @@ a parameter `for=tab-group` as in this example:
~~~
<!-- {% endraw %} -->

#### Labelling an entire page documenting a concept that is specific to a Scala version

When the content of a page explains a concept that is new in Scala 3 and has no
equivalent in Scala 2 (e.g. [TASTy]({% link scala3/guides/tasty-overview.md %})),
or a concept that has been removed in Scala 3, we label the entire page instead
of labelling each code example.

We achieve this by setting a couple of a attributes in the [YAML front
matter](https://jekyllrb.com/docs/front-matter/) of the Markdown file. For
instance, for a page that is specific to Scala 3:

~~~ yaml
scala3: true
versionSpecific: true
~~~

Or, for a page that is specific to Scala 2:

~~~ yaml
scala2: true
versionSpecific: true
~~~

Please note that when the entire page is labelled, its code examples do not
need to have tabs.

### Typechecked Examples

The site build process uses [mdoc](https://scalameta.org/mdoc/) to typecheck
Expand Down
41 changes: 2 additions & 39 deletions _overviews/scala3-book/ca-given-using-clauses.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ languages: [zh-cn]
num: 60
previous-page: ca-extension-methods
next-page: ca-context-bounds
scala3: true
versionSpecific: true
---


<h5>Use contextual abstraction <span class="tag tag-inline">Scala 3 Only</span></h5>

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.
Expand All @@ -23,9 +22,6 @@ One common way to achieve this is by passing the configuration as additional arg

In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods.

{% tabs nonusing %}
{% tab 'Scala 2 and 3' %}

```scala
case class Config(port: Int, baseUrl: String)

Expand All @@ -38,19 +34,13 @@ val config = Config(8080, "docs.scala-lang.org")
renderWebsite("/home", config)
```

{% endtab %}
{% endtabs %}

Let us assume that the configuration does not change throughout most of our code base.
Passing `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 parameters of our methods as _contextual_.

{% tabs using1 %}
{% tab 'Scala 3 Only' %}

```scala
def renderWebsite(path: String)(using c: Config): String =
"<html>" + renderWidget(List("cart")) + "</html>"
Expand All @@ -60,9 +50,6 @@ def renderWebsite(path: String)(using c: Config): String =
def renderWidget(items: List[String])(using c: Config): String = ???
```

{% endtab %}
{% endtabs %}

By starting a parameter section with the keyword `using`, we tell the Scala compiler that at the call-site it should automatically find an argument with the correct type.
The Scala compiler thus performs **term inference**.

Expand All @@ -71,36 +58,24 @@ So the program is equivalent to the one above.

In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature:

{% tabs using2 %}
{% tab 'Scala 3 Only' %}

```scala
// no need to come up with a parameter name
// vvvvvvvvvvvvv
def renderWebsite(path: String)(using Config): String =
"<html>" + renderWidget(List("cart")) + "</html>"
```

{% endtab %}
{% endtabs %}

#### 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:`

{% tabs using3 %}
{% tab 'Scala 3 Only' %}

```scala
renderWebsite("/home")(using config)
```

{% endtab %}
{% endtabs %}

Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense, and we want to make sure that the correct one is passed to the function.

For all other cases, as we will see in the next Section, there is also another way to bring contextual values into scope.
Expand All @@ -110,9 +85,6 @@ For all other cases, as we will see in the next Section, there is also another w
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`.

{% tabs given1 %}
{% tab 'Scala 3 Only' %}

```scala
val config = Config(8080, "docs.scala-lang.org")
// this is the type that we want to provide the
Expand All @@ -124,24 +96,15 @@ given Config = config
// as argument to contextual parameters of type Config
```

{% endtab %}
{% endtabs %}

In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument.

Having defined a given for `Config`, we can simply call `renderWebsite`:

{% tabs given2 %}
{% tab 'Scala 3 Only' %}

```scala
renderWebsite("/home")
// ^^^^^
// again no argument
```

{% endtab %}
{% endtabs %}

[reference]: {{ site.scala3ref }}/overview.html
[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html
7 changes: 2 additions & 5 deletions _overviews/scala3-book/ca-multiversal-equality.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ languages: [zh-cn]
num: 64
previous-page: ca-type-classes
next-page: ca-implicit-conversions
scala3: true
versionSpecific: true
---
<span class="tag tag-inline">New In Scala 3</span>

> Multiversal Equality is a new language feature that was introduced in Scala 3.
> Because it has no equivalent in Scala 2, all code examples
> in this lesson assume you are using Scala 3.

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.
Expand Down
2 changes: 2 additions & 0 deletions _overviews/scala3-book/types-adts-gadts.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ languages: [zh-cn]
num: 52
previous-page: types-union
next-page: types-variance
scala3: true
versionSpecific: true
---


Expand Down
5 changes: 4 additions & 1 deletion _overviews/scala3-book/types-union.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ languages: [zh-cn]
num: 51
previous-page: types-intersection
next-page: types-adts-gadts
scala3: true
versionSpecific: true
---

Used on types, the `|` operator creates a so-called _union type_.
Expand Down Expand Up @@ -43,14 +45,15 @@ case 1.0 => ??? // ERROR: this line won’t compile
As shown, union types can be used to represent alternatives of several different types, without requiring those types to be part of a custom-crafted class hierarchy, or requiring explicit wrapping.

#### Pre-planning the Class Hierarchy
Other languages would require pre-planning of the class hierarchy, like the following example illustrates:
Without union types, it would require pre-planning of the class hierarchy, like the following example illustrates:

```scala
trait UsernameOrPassword
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.

Expand Down
6 changes: 6 additions & 0 deletions _zh-cn/overviews/scala3-book/types-union.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ partof: scala3-book
overview-name: "Scala 3 — Book"
layout: multipage-overview
permalink: "/zh-cn/scala3/book/:title.html"
scala3: true
versionSpecific: true
---


Expand Down Expand Up @@ -54,12 +56,16 @@ case 1.0 => ??? // ERROR: this line won’t compile

其他语言需要预先规划类层次结构,如下例所示:

{% tabs pre-planning %}
{% tab 'Scala 2 and 3' %}
```scala
trait UsernameOrPassword
case class Username(name: String) extends UsernameOrPassword
case class Password(hash: Hash) extends UsernameOrPassword
def help(id: UsernameOrPassword) = ...
```
{% endtab %}
{% endtabs %}

预先计划不能很好地扩展,例如,API 用户的需求可能无法预见。
此外,使用诸如 `UsernameOrPassword` 之类的标记 trait 使类型层次结构混乱也会使代码更难阅读。
Expand Down

0 comments on commit 0cb1c34

Please sign in to comment.