Skip to content

Style Guide update #3122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 108 additions & 57 deletions _style/control-structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,41 @@ next-page: method-invocation
---

All control structures should be written with a space following the
defining keyword:

// right!
if (foo) bar else baz
for (i <- 0 to 10) { ... }
while (true) { println("Hello, World!") }

// wrong!
if(foo) bar else baz
for(i <- 0 to 10) { ... }
while(true) { println("Hello, World!") }

defining keyword. In Scala 3 parentheses around the condition should be omitted:

{% tabs control_structures_1 class=tabs-scala-version%}
{% tab 'Scala 2' for=control_structures_1 %}
```scala
// right!
if (foo) bar else baz
for (i <- 0 to 10) { ... }
while (true) { println("Hello, World!") }

// wrong!
if(foo) bar else baz
for(i <- 0 to 10) { ... }
while(true) { println("Hello, World!") }
```
{% endtab %}
{% tab 'Scala 3' for=control_structures_1 %}
```scala
// right!
if foo then bar else baz
for i <- 0 to 10 do ...
while true do println("Hello, World!")

// wrong!
if(foo) bar else baz
for(i <- 0 to 10) do ...
while(true) do println("Hello, World!")
```
{% endtab %}
{% endtabs %}

## Curly-Braces

Curly-braces should be omitted in cases where the control structure
In Scala 3 using curly-braces is discouraged and the quiet syntax with significant indentation is favoured.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this claim might be too strong. How about something like:

We have chosen to use significant indentation through this style guide, but our preference is not universally shared.
Some teams and individuals have enthusiastically embraced Scala 3's significant indentation; others still favor curly braces.

In Scala 2, curly-braces should be omitted in cases where the control structure
represents a pure-functional operation and all branches of the control
structure (relevant to `if`/`else`) are single-line expressions.
Remember the following guidelines:
Expand All @@ -41,63 +60,84 @@ Remember the following guidelines:

<!-- necessary to separate the following example from the above bullet list -->

val news = if (foo)
goodNews()
else
badNews()

if (foo) {
println("foo was true")
}

news match {
case "good" => println("Good news!")
case "bad" => println("Bad news!")
}
{% tabs control_structures_2 class=tabs-scala-version%}
{% tab 'Scala 2' for=control_structures_2 %}
```scala
val news = if (foo)
goodNews()
else
badNews()

if (foo) {
println("foo was true")
}

news match {
case "good" => println("Good news!")
case "bad" => println("Bad news!")
}
```
{% endtab %}
{% tab 'Scala 3' for=control_structures_2 %}
```scala
val news = if foo then
goodNews()
else
badNews()

if foo then
println("foo was true")

news match
case "good" => println("Good news!")
case "bad" => println("Bad news!")
```
{% endtab %}
{% endtabs %}

## Comprehensions

Scala has the ability to represent `for`-comprehensions with more than
one generator (usually, more than one `<-` symbol). In such cases, there
are two alternative syntaxes which may be used:

// wrong!
for (x <- board.rows; y <- board.files)
yield (x, y)

// right!
for {
x <- board.rows
y <- board.files
} yield (x, y)
{% tabs control_structures_3 class=tabs-scala-version%}
{% tab 'Scala 2' for=control_structures_3 %}
```scala
// wrong!
for (x <- board.rows; y <- board.files)
yield (x, y)

// right!
for {
x <- board.rows
y <- board.files
} yield (x, y)
```
{% endtab %}
{% tab 'Scala 3' for=control_structures_3 %}
```scala
// wrong!
for x <- board.rows; y <- board.files
yield (x, y)

// right!
for
x <- board.rows
y <- board.files
yield (x, y)
```
{% endtab %}
{% endtabs %}

While the latter style is more verbose, it is generally considered
easier to read and more "scalable" (meaning that it does not become
obfuscated as the complexity of the comprehension increases). You should
prefer this form for all `for`-comprehensions of more than one
generator. Comprehensions with only a single generator (e.g.
`for (i <- 0 to 10) yield i`) should use the first form (parentheses
`for i <- 0 to 10 yield i`) should use the first form (parentheses
rather than curly braces).

The exceptions to this rule are `for`-comprehensions which lack a
`yield` clause. In such cases, the construct is actually a loop rather
than a functional comprehension and it is usually more readable to
string the generators together between parentheses rather than using the
syntactically-confusing `} {` construct:
Comment on lines -85 to -89
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to drop this since the arguments for using one-generator-per-line syntax hold no matter whether the next keyword is do or yield


// wrong!
for {
x <- board.rows
y <- board.files
} {
printf("(%d, %d)", x, y)
}

// right!
for (x <- board.rows; y <- board.files) {
printf("(%d, %d)", x, y)
}

Finally, `for` comprehensions are preferred to chained calls to `map`,
`flatMap`, and `filter`, as this can get difficult to read (this is one
of the purposes of the enhanced `for` comprehension).
Expand All @@ -108,11 +148,22 @@ There are certain situations where it is useful to create a short
`if`/`else` expression for nested use within a larger expression. In
Java, this sort of case would traditionally be handled by the ternary
operator (`?`/`:`), a syntactic device which Scala lacks. In these
situations (and really any time you have a extremely brief `if`/`else`
situations (and really any time you have an extremely brief `if`/`else`
expression) it is permissible to place the "then" and "else" branches on
the same line as the `if` and `else` keywords:

val res = if (foo) bar else baz
{% tabs control_structures_4 class=tabs-scala-version%}
{% tab 'Scala 2' for=control_structures_4 %}
```scala
val res = if (foo) bar else baz
```
{% endtab %}
{% tab 'Scala 3' for=control_structures_4 %}
```scala
val res = if foo then bar else baz
```
{% endtab %}
{% endtabs %}

The key here is that readability is not hindered by moving both branches
inline with the `if`/`else`. Note that this style should never be used
Expand Down
39 changes: 11 additions & 28 deletions _style/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ If a class/object/trait extends anything, the same general rule applies,
put it on one line unless it goes over about 100 characters, and then
put each item on its own line with
[trailing commas](https://docs.scala-lang.org/sips/trailing-commas.html#motivation);
closing parenthesis provides visual separation between constructor arguments and extensions;
closing parenthesis and indentation provide visual separation between constructor arguments and extensions;
empty line should be added to further separate extensions from class implementation:

{% tabs declarations_2 class=tabs-scala-version%}
Expand All @@ -70,9 +70,9 @@ class Person(
shoeSize: Int,
favoriteColor: java.awt.Color,
) extends Entity
with Logging
with Identifiable
with Serializable {
with Logging
with Identifiable
with Serializable {

def firstMethod: Foo = …
}
Expand All @@ -88,9 +88,9 @@ class Person(
shoeSize: Int,
favoriteColor: java.awt.Color,
) extends Entity
with Logging
with Identifiable
with Serializable:
with Logging
with Identifiable
with Serializable:

def firstMethod: Foo = …
```
Expand Down Expand Up @@ -177,26 +177,6 @@ private def foo(x: Int = 6, y: Int = 7) = x + y
{% endtab %}
{% endtabs %}

#### Procedure Syntax

Avoid the (now deprecated) procedure syntax, as it tends to be confusing for very little gain in brevity.

{% tabs declarations_7 %}
{% tab 'Scala 2 Only' for=declarations_7 %}
```scala
// don't do this
def printBar(bar: Baz) {
println(bar)
}

// write this instead
def printBar(bar: Bar): Unit = {
println(bar)
}
```
{% endtab %}
{% endtabs %}

#### Modifiers

Method modifiers should be given in the following order (when each is
Expand Down Expand Up @@ -354,6 +334,7 @@ There are three main reasons you should do this:
def unless(exp: Boolean)(code: => Unit): Unit = {
if (!exp) code
}

unless(x < 5) {
println("x was not less than five")
}
Expand All @@ -363,12 +344,14 @@ unless(x < 5) {
```scala
def unless(exp: Boolean)(code: => Unit): Unit =
if (!exp) code

unless(x < 5):
println("x was not less than five")
```
{% endtab %}
{% endtabs %}

{:start="2"}
2. Implicit Parameters

When using implicit parameters, and you use the `implicit` keyword,
Expand Down Expand Up @@ -435,7 +418,7 @@ protected def forResource(
{% endtab %}
{% endtabs %}


{:start="2"}
2. Or align the open-paren of the parameter lists, one list per line:

{% tabs declarations_17 class=tabs-scala-version%}
Expand Down
41 changes: 32 additions & 9 deletions _style/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,36 @@ previous-page: method-invocation
next-page: scaladoc
---

As a rule, files should contain a *single* logical compilation unit. By
"logical" I mean a class, trait or object. One exception to this
Comment on lines -14 to -15
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resolves #1621 by removing the confusing concept entirely.

As a rule, files should contain a *single* class, trait or object. One exception to this
guideline is for classes or traits which have companion objects.
Companion objects should be grouped with their corresponding class or
trait in the same file. These files should be named according to the
class, trait or object they contain:

package com.novell.coolness
{% tabs files_1 class=tabs-scala-version%}
{% tab 'Scala 2' for=files_1 %}
```scala
package com.novell.coolness

class Inbox { ... }
class Inbox { ... }

// companion object
object Inbox { ... }
// companion object
object Inbox { ... }
```
{% endtab %}
{% tab 'Scala 3' for=files_1 %}
```scala
package com.novell.coolness

class Inbox:
...

// companion object
object Inbox:
...
```
{% endtab %}
{% endtabs %}

These compilation units should be placed within a file named
`Inbox.scala` within the `com/novell/coolness` directory. In short, the
Expand All @@ -36,11 +53,17 @@ file. One common example is that of a sealed trait and several
sub-classes (often emulating the ADT language feature available in
functional languages):

sealed trait Option[+A]
{% tabs files_2 %}
{% tab 'Scala 2 and 3' for=files_2 %}
```scala
sealed trait Option[+A]

case class Some[A](a: A) extends Option[A]
case class Some[A](a: A) extends Option[A]

case object None extends Option[Nothing]
case object None extends Option[Nothing]
```
{% endtab %}
{% endtabs %}

Because of the nature of sealed superclasses (and traits), all subtypes
*must* be included in the same file. Thus, such a situation definitely
Expand Down
Loading
Loading