diff --git a/_layouts/style-guide.html b/_layouts/style-guide.html index 41e39d8709..62534401ee 100644 --- a/_layouts/style-guide.html +++ b/_layouts/style-guide.html @@ -15,6 +15,16 @@ {{content}} +
+ {% if page.previous-page %} + previous + {% else %} +
+ {% endif %} + {% if page.next-page %} + next + {% endif %} +
{% include contributors-list.html %} diff --git a/_style/control-structures.md b/_style/control-structures.md index 7a746c46bd..1277aebb40 100644 --- a/_style/control-structures.md +++ b/_style/control-structures.md @@ -1,33 +1,49 @@ --- layout: style-guide title: Control Structures - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 7 - previous-page: declarations 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. +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: @@ -44,19 +60,40 @@ Remember the following guidelines: - 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 @@ -64,43 +101,43 @@ 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: - - // 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). @@ -111,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 diff --git a/_style/declarations.md b/_style/declarations.md index 06fbfadcb7..ef274676f1 100644 --- a/_style/declarations.md +++ b/_style/declarations.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Declarations - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 6 - previous-page: nested-blocks next-page: control-structures --- @@ -18,42 +15,87 @@ unless the line becomes "too long" (about 100 characters). In that case, put each constructor argument on its own line with [trailing commas](https://docs.scala-lang.org/sips/trailing-commas.html#motivation): - class Person(name: String, age: Int) { - … - } - - class Person( - name: String, - age: Int, - birthdate: Date, - astrologicalSign: String, - shoeSize: Int, - favoriteColor: java.awt.Color, - ) { - def firstMethod: Foo = … - } +{% tabs declarations_1 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_1 %} +```scala +class Person(name: String, age: Int) { + … +} + +class Person( + name: String, + age: Int, + birthdate: Date, + astrologicalSign: String, + shoeSize: Int, + favoriteColor: java.awt.Color, +) { + def firstMethod: Foo = … +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_1 %} +```scala +class Person(name: String, age: Int): + … + +class Person( + name: String, + age: Int, + birthdate: Date, + astrologicalSign: String, + shoeSize: Int, + favoriteColor: java.awt.Color, +): + def firstMethod: Foo = … +``` +{% endtab %} +{% endtabs %} 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: - class Person( - name: String, - age: Int, - birthdate: Date, - astrologicalSign: String, - shoeSize: Int, - favoriteColor: java.awt.Color, - ) extends Entity - with Logging - with Identifiable - with Serializable { - - def firstMethod: Foo = … - } +{% tabs declarations_2 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_2 %} +```scala +class Person( + name: String, + age: Int, + birthdate: Date, + astrologicalSign: String, + shoeSize: Int, + favoriteColor: java.awt.Color, +) extends Entity + with Logging + with Identifiable + with Serializable { + + def firstMethod: Foo = … +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_2 %} +```scala +class Person( + name: String, + age: Int, + birthdate: Date, + astrologicalSign: String, + shoeSize: Int, + favoriteColor: java.awt.Color, +) extends Entity + with Logging + with Identifiable + with Serializable: + + def firstMethod: Foo = … +``` +{% endtab %} +{% endtabs %} ### Ordering Of Class Elements @@ -63,14 +105,31 @@ may be declared without the intervening newline, but only if none of the fields have Scaladoc and if all of the fields have simple (max of 20-ish chars, one line) definitions: - class Foo { - val bar = 42 - val baz = "Daniel" +{% tabs declarations_3 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_3 %} +```scala +class Foo { + val bar = 42 + val baz = "Daniel" - def doSomething(): Unit = { ... } + def doSomething(): Unit = { ... } - def add(x: Int, y: Int): Int = x + y - } + def add(x: Int, y: Int): Int = x + y +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_3 %} +```scala +class Foo: + val bar = 42 + val baz = "Daniel" + + def doSomething(): Unit = ... + + def add(x: Int, y: Int): Int = x + y +``` +{% endtab %} +{% endtabs %} Fields should *precede* methods in a scope. The only exception is if the `val` has a block definition (more than one expression) and performs @@ -85,12 +144,24 @@ class file. Methods should be declared according to the following pattern: - def foo(bar: Baz): Bin = expr +{% tabs declarations_4 %} +{% tab 'Scala 2 and 3' for=declarations_4 %} +```scala +def foo(bar: Baz): Bin = expr +``` +{% endtab %} +{% endtabs %} Methods with default parameter values should be declared in an analogous fashion, with a space on either side of the equals sign: - def foo(x: Int = 6, y: Int = 7): Int = x + y +{% tabs declarations_5 %} +{% tab 'Scala 2 and 3' for=declarations_5 %} +```scala +def foo(x: Int = 6, y: Int = 7): Int = x + y +``` +{% endtab %} +{% endtabs %} You should specify a return type for all public members. Consider it documentation checked by the compiler. @@ -98,21 +169,13 @@ It also helps in preserving binary compatibility in the face of changing type in Local methods or private methods may omit their return type: - private def foo(x: Int = 6, y: Int = 7) = x + y - -#### Procedure Syntax - -Avoid the (now deprecated) procedure syntax, as it tends to be confusing for very little gain in brevity. - - // don't do this - def printBar(bar: Baz) { - println(bar) - } - - // write this instead - def printBar(bar: Bar): Unit = { - println(bar) - } +{% tabs declarations_6 %} +{% tab 'Scala 2 and 3' for=declarations_6 %} +```scala +private def foo(x: Int = 6, y: Int = 7) = x + y +``` +{% endtab %} +{% endtabs %} #### Modifiers @@ -128,25 +191,51 @@ applicable): - @Transaction - @throws(classOf[IOException]) - override protected final def foo(): Unit = { - ... - } +{% tabs declarations_8 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_8 %} +```scala +@Transaction +@throws(classOf[IOException]) +override protected final def foo(): Unit = { + ... +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_8 %} +```scala +@Transaction +@throws(classOf[IOException]) +override protected final def foo(): Unit = + ... +``` +{% endtab %} +{% endtabs %} #### Body When a method body comprises a single expression which is less than 30 (or so) characters, it should be given on a single line with the method: - def add(a: Int, b: Int): Int = a + b +{% tabs declarations_9 %} +{% tab 'Scala 2 and 3' for=declarations_9 %} +```scala +def add(a: Int, b: Int): Int = a + b +``` +{% endtab %} +{% endtabs %} When the method body is a single expression *longer* than 30 (or so) characters but still shorter than 70 (or so) characters, it should be given on the following line, indented two spaces: - def sum(ls: List[String]): Int = - ls.map(_.toInt).foldLeft(0)(_ + _) +{% tabs declarations_10 %} +{% tab 'Scala 2 and 3' for=declarations_10 %} +```scala +def sum(ls: List[String]): Int = + ls.map(_.toInt).foldLeft(0)(_ + _) +``` +{% endtab %} +{% endtabs %} The distinction between these two cases is somewhat artificial. Generally speaking, you should choose whichever style is more readable @@ -159,29 +248,71 @@ When the body of a method cannot be concisely expressed in a single line or is of a non-functional nature (some mutable state, local or otherwise), the body must be enclosed in braces: +{% tabs declarations_11 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_11 %} +```scala def sum(ls: List[String]): Int = { val ints = ls.map(_.toInt) ints.foldLeft(0)(_ + _) } +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_11 %} +```scala +def sum(ls: List[String]): Int = + val ints = ls.map(_.toInt) + ints.foldLeft(0)(_ + _) +``` +{% endtab %} +{% endtabs %} Methods which contain a single `match` expression should be declared in the following way: - // right! - def sum(ls: List[Int]): Int = ls match { - case hd :: tail => hd + sum(tail) - case Nil => 0 - } +{% tabs declarations_12 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_12 %} +```scala +// right! +def sum(ls: List[Int]): Int = ls match { + case hd :: tail => hd + sum(tail) + case Nil => 0 +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_12 %} +```scala +// right! +def sum(ls: List[Int]): Int = ls match + case hd :: tail => hd + sum(tail) + case Nil => 0 +``` +{% endtab %} +{% endtabs %} *Not* like this: - // wrong! - def sum(ls: List[Int]): Int = { - ls match { - case hd :: tail => hd + sum(tail) - case Nil => 0 - } - } +{% tabs declarations_13 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_13 %} +```scala +// wrong! +def sum(ls: List[Int]): Int = { + ls match { + case hd :: tail => hd + sum(tail) + case Nil => 0 + } +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_13 %} +```scala +// wrong! +def sum(ls: List[Int]): Int = + ls match + case hd :: tail => hd + sum(tail) + case Nil => 0 +``` +{% endtab %} +{% endtabs %} #### Multiple Parameter Lists @@ -197,12 +328,30 @@ There are three main reasons you should do this: Multiple parameter lists allow you to create your own "control structures": - def unless(exp: Boolean)(code: => Unit): Unit = - if (!exp) code - unless(x < 5) { - println("x was not less than five") - } - +{% tabs declarations_14 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_14 %} +```scala +def unless(exp: Boolean)(code: => Unit): Unit = { + if (!exp) code +} + +unless(x < 5) { + println("x was not less than five") +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_14 %} +```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, @@ -215,14 +364,20 @@ There are three main reasons you should do this: type inferencer can allow a simpler syntax when invoking the remaining parameter lists. Consider fold: - def foldLeft[B](z: B)(op: (B, A) => B): B - List("").foldLeft(0)(_ + _.length) - - // If, instead: - def foldLeft[B](z: B, op: (B, A) => B): B - // above won't work, you must specify types - List("").foldLeft(0, (b: Int, a: String) => b + a.length) - List("").foldLeft[Int](0, _ + _.length) +{% tabs declarations_15 %} +{% tab 'Scala 2 and 3' for=declarations_15 %} +```scala +def foldLeft[B](z: B)(op: (B, A) => B): B +List("").foldLeft(0)(_ + _.length) + +// If, instead: +def foldLeft[B](z: B, op: (B, A) => B): B +// above won't work, you must specify types +List("").foldLeft(0, (b: Int, a: String) => b + a.length) +List("").foldLeft[Int](0, _ + _.length) +``` +{% endtab %} +{% endtabs %} For complex DSLs, or with type names that are long, it can be difficult to fit the entire signature on one line. For those cases there are several @@ -233,24 +388,58 @@ different styles in use: and parentheses being on separate lines adding to visual separation between the lists: - protected def forResource( - resourceInfo: Any, - )( - f: (JsonNode) => Any, - )(implicit - urlCreator: URLCreator, - configurer: OAuthConfiguration, - ): Any = { - ... - } - +{% tabs declarations_16 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_16 %} +```scala +protected def forResource( + resourceInfo: Any, +)( + f: (JsonNode) => Any, +)(implicit + urlCreator: URLCreator, + configurer: OAuthConfiguration, +): Any = { + ... +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_16 %} +```scala +protected def forResource( + resourceInfo: Any, +)( + f: (JsonNode) => Any, +)(using + urlCreator: URLCreator, + configurer: OAuthConfiguration, +): Any = + ... +``` +{% endtab %} +{% endtabs %} + +{:start="2"} 2. Or align the open-paren of the parameter lists, one list per line: - protected def forResource(resourceInfo: Any) - (f: (JsonNode) => Any) - (implicit urlCreator: URLCreator, configurer: OAuthConfiguration): Any = { - ... - } +{% tabs declarations_17 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_17 %} +```scala +protected def forResource(resourceInfo: Any) + (f: (JsonNode) => Any) + (implicit urlCreator: URLCreator, configurer: OAuthConfiguration): Any = { + ... +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_17 %} +```scala +protected def forResource(resourceInfo: Any) + (f: (JsonNode) => Any) + (using urlCreator: URLCreator, configurer: OAuthConfiguration): Any = + ... +``` +{% endtab %} +{% endtabs %} #### Higher-Order Functions @@ -259,16 +448,34 @@ fact that Scala allows a somewhat nicer syntax for such functions at call-site when the function parameter is curried as the last argument. For example, this is the `foldl` function in SML: - fun foldl (f: ('b * 'a) -> 'b) (init: 'b) (ls: 'a list) = ... +{% tabs declarations_18 %} +{% tab 'SML' for=declarations_18 %} +``` +fun foldl (f: ('b * 'a) -> 'b) (init: 'b) (ls: 'a list) = ... +``` +{% endtab %} +{% endtabs %} In Scala, the preferred style is the exact inverse: - def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B = ... +{% tabs declarations_19 %} +{% tab 'Scala 2 and 3' for=declarations_19 %} +```scala +def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B = ... +``` +{% endtab %} +{% endtabs %} By placing the function parameter *last*, we have enabled invocation syntax like the following: - foldLeft(List(1, 2, 3, 4))(0)(_ + _) +{% tabs declarations_20 %} +{% tab 'Scala 2 and 3' for=declarations_20 %} +```scala +foldLeft(List(1, 2, 3, 4))(0)(_ + _) +``` +{% endtab %} +{% endtabs %} The function value in this invocation is not wrapped in parentheses; it is syntactically quite disconnected from the function itself @@ -281,8 +488,13 @@ note of access modifier ordering and annotation conventions. Lazy vals should use the `lazy` keyword directly before the `val`: - private lazy val foo = bar() - +{% tabs declarations_21 %} +{% tab 'Scala 2 and 3' for=declarations_21 %} +```scala +private lazy val foo = bar() +``` +{% endtab %} +{% endtabs %} ## Function Values @@ -325,10 +537,23 @@ invocation, while the closing brace is on its own line immediately following the last line of the function. Parameters should be on the same line as the opening brace, as should the "arrow" (`=>`): - val f1 = { (a: Int, b: Int) => - val sum = a + b - sum - } +{% tabs declarations_22 class=tabs-scala-version%} +{% tab 'Scala 2' for=declarations_22 %} +```scala +val f1 = { (a: Int, b: Int) => + val sum = a + b + sum +} +``` +{% endtab %} +{% tab 'Scala 3' for=declarations_22 %} +```scala +val f1 = (a: Int, b: Int) => + val sum = a + b + sum +``` +{% endtab %} +{% endtabs %} As noted earlier, function values should leverage type inference whenever possible. diff --git a/_style/files.md b/_style/files.md index 54ddf4e2db..b311a75fd4 100644 --- a/_style/files.md +++ b/_style/files.md @@ -1,29 +1,43 @@ --- layout: style-guide title: Files - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 9 - 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 +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 { ... } + +// companion object +object Inbox { ... } +``` +{% endtab %} +{% tab 'Scala 3' for=files_1 %} +```scala +package com.novell.coolness - class Inbox { ... } +class Inbox: + ... - // companion object - object 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 @@ -39,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 diff --git a/_style/indentation.md b/_style/indentation.md index 247c07bb05..2d5caa417e 100644 --- a/_style/indentation.md +++ b/_style/indentation.md @@ -1,36 +1,63 @@ --- layout: style-guide title: Indentation - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 2 - -previous-page: overview +previous-page: index next-page: naming-conventions --- Each level of indentation is 2 spaces. Tabs are not used. Thus, instead of indenting like this: - // wrong! - class Foo { - def fourspaces = { - val x = 4 - .. - } +{% tabs indentation_wrong class=tabs-scala-version %} +{% tab 'Scala 2' for=indentation_wrong %} +```scala +// wrong! +class Foo { + def fourspaces = { + val x = 4 + ... } +} +``` +{% endtab %} +{% tab 'Scala 3' for=indentation_wrong %} +```scala +// wrong! +class Foo: + def fourspaces = + val x = 4 + ... +``` +{% endtab %} +{% endtabs %} You should indent like this: - // right! - class Foo { - def twospaces = { - val x = 2 - .. - } - } +{% tabs indentation_right class=tabs-scala-version %} +{% tab 'Scala 2' for=indentation_right %} +```scala +// right! +class Foo { + def twospaces = { + val x = 2 + ... + } +} +``` +{% endtab %} +{% tab 'Scala 3' for=indentation_right %} +```scala +// right! +class Foo: + def twospaces = + val x = 2 + ... +``` +{% endtab %} +{% endtabs %} The Scala language encourages a startling amount of nested scopes and logical blocks (function values and such). Do yourself a favor and don't @@ -53,9 +80,15 @@ one line, each successive line should be indented two spaces from the have an unclosed parenthetical or to end with an infix method in which the right parameter is not given: - val result = 1 + 2 + 3 + 4 + 5 + 6 + - 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + - 15 + 16 + 17 + 18 + 19 + 20 +{% tabs line_wrapping %} +{% tab 'Scala 2 and 3' for=line_wrapping %} +```scala +val result = 1 + 2 + 3 + 4 + 5 + 6 + + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 15 + 16 + 17 + 18 + 19 + 20 +``` +{% endtab %} +{% endtabs %} Without this trailing method, Scala will infer a semi-colon at the end of a line which was intended to wrap, throwing off the compilation @@ -68,11 +101,18 @@ five or more), it is often necessary to wrap the method invocation onto multiple lines. In such cases, put each argument on a line by itself, indented two spaces from the current indent level: - foo( - someVeryLongFieldName, - andAnotherVeryLongFieldName, - "this is a string", - 3.1415) +{% tabs method_invocation %} +{% tab 'Scala 2 and 3' for=method_invocation %} +```scala +foo( + someVeryLongFieldName, + andAnotherVeryLongFieldName, + "this is a string", + 3.1415 +) +``` +{% endtab %} +{% endtabs %} This way, all parameters line up, but you don't need to re-align them if you change the name of the method later on. @@ -83,19 +123,26 @@ avoided when each parameter would have to be indented more than 50 spaces to achieve alignment. In such cases, the invocation itself should be moved to the next line and indented two spaces: - // right! - val myLongFieldNameWithNoRealPoint = - foo( - someVeryLongFieldName, - andAnotherVeryLongFieldName, - "this is a string", - 3.1415) - - // wrong! - val myLongFieldNameWithNoRealPoint = foo(someVeryLongFieldName, - andAnotherVeryLongFieldName, - "this is a string", - 3.1415) +{% tabs method_invocation_2 %} +{% tab 'Scala 2 and 3' for=method_invocation_2 %} +```scala +// right! +val myLongFieldNameWithNoRealPoint = + foo( + someVeryLongFieldName, + andAnotherVeryLongFieldName, + "this is a string", + 3.1415 + ) + +// wrong! +val myLongFieldNameWithNoRealPoint = foo(someVeryLongFieldName, + andAnotherVeryLongFieldName, + "this is a string", + 3.1415) + ``` +{% endtab %} +{% endtabs %} Better yet, just try to avoid any method which takes more than two or three parameters! diff --git a/_style/index.md b/_style/index.md index 7663a32054..1b2d0b73ec 100644 --- a/_style/index.md +++ b/_style/index.md @@ -1,13 +1,14 @@ --- layout: style-guide -title: Scala Style Guide +title: Introduction partof: style -overview-name: " " +overview-name: Style Guide +num: 1 +next-page: indentation --- This document is intended to outline some basic Scala stylistic guidelines which should be followed with more or less fervency. Wherever possible, this guide attempts to detail why a particular style is encouraged and how it relates to other alternatives. As with all style guides, treat this document as a list of rules to be broken. There are certainly times when alternative styles should be preferred over the ones given here. - - [Indentation](indentation.html) - [Line Wrapping](indentation.html#line-wrapping) - [Methods with Numerous Arguments](indentation.html#methods-with-numerous-arguments) @@ -40,7 +41,6 @@ This document is intended to outline some basic Scala stylistic guidelines which - [Classes](declarations.html#classes) - [Ordering Of Class Elements](declarations.html#ordering-of-class-elements) - [Methods](declarations.html#methods) - - [Procedure Syntax](declarations.html#procedure-syntax) - [Modifiers](declarations.html#modifiers) - [Body](declarations.html#body) - [Multiple Parameter Lists](declarations.html#multiple-parameter-lists) @@ -69,6 +69,4 @@ This document is intended to outline some basic Scala stylistic guidelines which - [Traits](scaladoc.html#traits) - [Methods and Other Members](scaladoc.html#methods-and-other-members) -### Thanks to ### - -[Daniel Spiewak](https://www.codecommit.com/) and [David Copeland](https://www.naildrivin5.com/) for putting this style guide together, and Simon Ochsenreither for converting it to Markdown. +**Thanks to** [Daniel Spiewak](https://www.codecommit.com/) and [David Copeland](https://www.naildrivin5.com/) for putting this style guide together, and Simon Ochsenreither for converting it to Markdown. diff --git a/_style/method-invocation.md b/_style/method-invocation.md index eb1e548785..813815d055 100644 --- a/_style/method-invocation.md +++ b/_style/method-invocation.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Method Invocation - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 8 - previous-page: control-structures next-page: files --- @@ -18,16 +15,28 @@ name, nor should there be any space between the method name and the argument-delimiters (parentheses). Each argument should be separated by a single space *following* the comma (`,`): - foo(42, bar) - target.foo(42, bar) - target.foo() +{% tabs method_invocation_1 %} +{% tab 'Scala 2 and 3' for=method_invocation_1 %} +```scala +foo(42, bar) +target.foo(42, bar) +target.foo() +``` +{% endtab %} +{% endtabs %} As of version 2.8, Scala now has support for named parameters. Named parameters in a method invocation should be treated as regular parameters (spaced accordingly following the comma) with a space on either side of the equals sign: - foo(x = 6, y = 7) +{% tabs method_invocation_2 %} +{% tab 'Scala 2 and 3' for=method_invocation_2 %} +```scala +foo(x = 6, y = 7) +``` +{% endtab %} +{% endtabs %} While this style does create visual ambiguity with named parameters and variable assignment, the alternative (no spacing around the equals sign) @@ -39,11 +48,17 @@ non-trivial expressions for the actuals. Scala allows the omission of parentheses on methods of arity-0 (no arguments): - reply() +{% tabs method_invocation_3 %} +{% tab 'Scala 2 and 3' for=method_invocation_3 %} +```scala +reply() - // is the same as +// is the same as - reply +reply +``` +{% endtab %} +{% endtabs %} However, this syntax should *only* be used when the method in question has no side-effects (purely-functional). In other words, it would be @@ -63,39 +78,63 @@ Scala has a special punctuation-free syntax for invoking methods of arity-1 exceptions for operators and higher-order functions. In these cases it should only be used for purely-functional methods (methods with no side-effects). - // recommended - names.mkString(",") +{% tabs method_invocation_4 %} +{% tab 'Scala 2 and 3' for=method_invocation_4 %} +```scala +// recommended +names.mkString(",") - // also sometimes seen; controversial - names mkString "," +// also sometimes seen; controversial +names mkString "," - // wrong - has side-effects - javaList add item +// wrong - has side-effects +javaList add item +``` +{% endtab %} +{% endtabs %} ### Symbolic Methods/Operators Symbolic methods (operators) should always be invoked using infix notation with spaces separating the target, the operator, and the parameter: - // right! - "daniel" + " " + "spiewak" - a + b - - // wrong! - "daniel"+" "+"spiewak" - a+b - a.+(b) +{% tabs method_invocation_5 %} +{% tab 'Scala 2 and 3' for=method_invocation_5 %} +```scala +// right! +"daniel" + " " + "spiewak" +a + b + +// wrong! +"daniel"+" "+"spiewak" +a+b +a.+(b) +``` +{% endtab %} +{% endtabs %} For the most part, this idiom follows Java and Haskell syntactic conventions. A gray area is short, operator-like methods like `max`, especially if commutative: - // fairly common - a max b +{% tabs method_invocation_6 %} +{% tab 'Scala 2 and 3' for=method_invocation_6 %} +```scala +// fairly common +a max b +``` +{% endtab %} +{% endtabs %} Symbolic methods which take more than one parameter are discouraged. When they exist, they may still be invoked using infix notation, delimited by spaces: - foo ** (bar, baz) +{% tabs method_invocation_7 %} +{% tab 'Scala 2 and 3' for=method_invocation_7 %} +```scala +foo ** (bar, baz) +``` +{% endtab %} +{% endtabs %} Such methods are fairly rare, however, and should normally be avoided during API design. For example, the use of the (now deprecated) `/:` and `:\` methods should be avoided in @@ -106,14 +145,26 @@ preference to their better-known names, `foldLeft` and `foldRight`. Invoking higher-order functions may use parens or braces, but in either case, use dot notation and omit any space after the method name: - names.map(_.toUpperCase) +{% tabs method_invocation_8 %} +{% tab 'Scala 2 and 3' for=method_invocation_8 %} +```scala +names.map(_.toUpperCase) +``` +{% endtab %} +{% endtabs %} These are not recommended: - // wrong! missing dot - names map (_.toUpperCase) - // wrong! extra space - names.map (_.toUpperCase) +{% tabs method_invocation_9 %} +{% tab 'Scala 2 and 3' for=method_invocation_9 %} +```scala +// wrong! missing dot +names map (_.toUpperCase) +// wrong! extra space +names.map (_.toUpperCase) +``` +{% endtab %} +{% endtabs %} Experience has shown that these styles make code harder to read, especially when multiple such method calls are chained. diff --git a/_style/naming-conventions.md b/_style/naming-conventions.md index 7cdbc493fb..da386f890e 100644 --- a/_style/naming-conventions.md +++ b/_style/naming-conventions.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Naming Conventions - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 3 - previous-page: indentation next-page: types --- @@ -14,18 +11,36 @@ next-page: types Generally speaking, Scala uses "camel case" naming. That is, each word is capitalized, except possibly the first word: - UpperCamelCase - lowerCamelCase +{% tabs camel_case %} +{% tab 'Scala 2 and 3' for=camel_case %} +```scala +UpperCamelCase +lowerCamelCase +``` +{% endtab %} +{% endtabs %} Acronyms should be treated as normal words: - xHtml - maxId +{% tabs acronyms_1 %} +{% tab 'Scala 2 and 3' for=acronyms_1 %} +```scala +xHtml +maxId +``` +{% endtab %} +{% endtabs %} instead of: - XHTML - maxID +{% tabs acronyms_2 %} +{% tab 'Scala 2 and 3' for=acronyms_2 %} +```scala +XHTML +maxID +``` +{% endtab %} +{% endtabs %} Underscores in names (`_`) are not actually forbidden by the compiler, but are strongly discouraged as they have @@ -36,7 +51,13 @@ for exceptions.) Classes should be named in upper camel case: - class MyFairLady +{% tabs class_names %} +{% tab 'Scala 2 and 3' for=class_names %} +```scala +class MyFairLady +``` +{% endtab %} +{% endtabs %} This mimics the Java naming convention for classes. @@ -53,38 +74,75 @@ Object names are like class names (upper camel case). An exception is when mimicking a package or function. This isn't common. Example: - object ast { - sealed trait Expr - - case class Plus(e1: Expr, e2: Expr) extends Expr - ... - } - - object inc { - def apply(x: Int): Int = x + 1 - } +{% tabs object_names class=tabs-scala-version%} +{% tab 'Scala 2' for=object_names %} +```scala +object ast { + sealed trait Expr + + case class Plus(e1: Expr, e2: Expr) extends Expr + ... +} + + object inc { + def apply(x: Int): Int = x + 1 + } + ``` +{% endtab %} +{% tab 'Scala 3' for=object_names %} +```scala +object ast: + sealed trait Expr + + case class Plus(e1: Expr, e2: Expr) extends Expr + ... + +object inc: + def apply(x: Int): Int = x + 1 + ``` +{% endtab %} +{% endtabs %} ## Packages Scala packages should follow the Java package naming conventions: - // wrong! - package coolness - - // right! puts only coolness._ in scope - package com.novell.coolness - - // right! puts both novell._ and coolness._ in scope - package com.novell - package coolness - - // right, for package object com.novell.coolness - package com.novell - /** - * Provides classes related to coolness - */ - package object coolness { - } +{% tabs packages class=tabs-scala-version%} +{% tab 'Scala 2' for=packages %} +```scala +// wrong! +package coolness + +// right! puts only coolness._ in scope +package com.novell.coolness + +// right! puts both novell._ and coolness._ in scope +package com.novell +package coolness + +// right, for package object com.novell.coolness +package com.novell +/** + * Provides classes related to coolness + */ +package object coolness { +} +``` +{% endtab %} +{% tab 'Scala 3' for=packages %} +```scala +// wrong! +package coolness + +// right! puts only coolness.* in scope +package com.novell.coolness + +// right! puts both novell.* and coolness.* in scope +package com.novell +package coolness +``` +{% endtab %} +{% endtabs %} ### _root_ @@ -92,7 +150,18 @@ It is occasionally necessary to fully-qualify imports using `_root_`. For example if another `net` is in scope, then to access `net.liftweb` we must write e.g.: - import _root_.net.liftweb._ +{% tabs packages_root class=tabs-scala-version%} +{% tab 'Scala 2' for=packages_root %} +```scala +import _root_.net.liftweb._ +``` +{% endtab %} +{% tab 'Scala 3' for=packages_root %} +```scala +import _root_.net.liftweb.* +``` +{% endtab %} +{% endtabs %} Do not overuse `_root_`. In general, nested package resolves are a good thing and very helpful in reducing import clutter. Using `_root_` @@ -103,7 +172,13 @@ of itself. Textual (alphabetic) names for methods should be in lower camel case: - def myFairMethod = ... +{% tabs method_names %} +{% tab 'Scala 2 and 3' for=method_names %} +```scala +def myFairMethod = ... +``` +{% endtab %} +{% endtabs %} This section is not a comprehensive guide to idiomatic method naming in Scala. Further information may be found in the method invocation section. @@ -128,54 +203,98 @@ conventions are used: this convention will enable a call-site mutation syntax which mirrors assignment. Note that this is not just a convention but a requirement of the language. +{% tabs accessors class=tabs-scala-version%} +{% tab 'Scala 2' for=accessors %} +```scala +class Foo { + + def bar = ... - class Foo { + def bar_=(bar: Bar) { + ... + } - def bar = ... + def isBaz = ... +} - def bar_=(bar: Bar) { - ... - } +val foo = new Foo +foo.bar // accessor +foo.bar = bar2 // mutator +foo.isBaz // boolean property +``` +{% endtab %} +{% tab 'Scala 3' for=accessors %} +```scala +class Foo: - def isBaz = ... - } + def bar = ... - val foo = new Foo - foo.bar // accessor - foo.bar = bar2 // mutator - foo.isBaz // boolean property + def bar_=(bar: Bar) = + ... + def isBaz = ... + +val foo = new Foo +foo.bar // accessor +foo.bar = bar2 // mutator +foo.isBaz // boolean property +``` +{% endtab %} +{% endtabs %} Unfortunately, these conventions fall afoul of the Java convention to name the private fields encapsulated by accessors and mutators according to the property they represent. For example: - public class Company { - private String name; +{% tabs java_getter %} +{% tab 'Java' for=java_getter %} +```java +public class Company { + private String name; - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; } +} +``` +{% endtab %} +{% endtabs %} In Scala, there is no distinction between fields and methods. In fact, fields are completely named and controlled by the compiler. If we wanted to adopt the Java convention of bean getters/setters in Scala, this is a rather simple encoding: - class Company { - private var _name: String = _ - - def name = _name - - def name_=(name: String) { - _name = name - } - } +{% tabs accessors_2 class=tabs-scala-version%} +{% tab 'Scala 2' for=accessors_2 %} +```scala +class Company { + private var _name: String = _ + + def name = _name + + def name_=(name: String) { + _name = name + } +} +``` +{% endtab %} +{% tab 'Scala 3' for=accessors_2 %} +```scala +class Company: + private var _name: String = _ + + def name = _name + + def name_=(name: String) = + _name = name +``` +{% endtab %} +{% endtabs %} While Hungarian notation is terribly ugly, it does have the advantage of disambiguating the `_name` variable without cluttering the identifier. @@ -190,19 +309,43 @@ are libraries that support properties and bindings. The convention is to use an immutable reference to a property class that contains its own getter and setter. For example: - class Company { - val string: Property[String] = Property("Initial Value") +{% tabs property class=tabs-scala-version%} +{% tab 'Scala 2' for=property %} +```scala +class Company { + val string: Property[String] = Property("Initial Value") +``` +{% endtab %} +{% tab 'Scala 3' for=property %} +```scala +class Company: + val string: Property[String] = Property("Initial Value") +``` +{% endtab %} +{% endtabs %} ### Parentheses Scala allows a parameterless, zero-[arity](https://en.wikipedia.org/wiki/Arity) method to be declared with an empty parameter list: - def foo1() = ... +{% tabs method_parentheses %} +{% tab 'Scala 2 and 3' for=method_parentheses %} +```scala +def foo1() = ... +``` +{% endtab %} +{% endtabs %} or with no parameter lists at all: - def foo2 = ... +{% tabs method_parentheses_2 %} +{% tab 'Scala 2 and 3' for=method_parentheses_2 %} +```scala +def foo2 = ... +``` +{% endtab %} +{% endtabs %} By convention, parentheses are used to indicate that a method has side effects, such as altering the receiver. @@ -264,16 +407,34 @@ Constant names should be in upper camel case. Similar to Java's `static final` members, if the member is final, immutable and it belongs to a package object or an object, it may be considered a constant: - object Container { - val MyConstant = ... - } +{% tabs constant_names class=tabs-scala-version%} +{% tab 'Scala 2' for=constant_names %} +```scala +object Container { + val MyConstant = ... +} +``` +{% endtab %} +{% tab 'Scala 3' for=constant_names %} +```scala +object Container: + val MyConstant = ... +``` +{% endtab %} +{% endtabs %} The value: `Pi` in `scala.math` package is another example of such a constant. Value and variable names should be in lower camel case: - val myValue = ... - var myVariable +{% tabs variable_names %} +{% tab 'Scala 2 and 3' for=variable_names %} +```scala +val myValue = ... +var myVariable +``` +{% endtab %} +{% endtabs %} ## Type Parameters (generics) @@ -281,33 +442,77 @@ For simple type parameters, a single upper-case letter (from the English alphabet) should be used, starting with `A` (this is different than the Java convention of starting with `T`). For example: - class List[A] { - def map[B](f: A => B): List[B] = ... - } +{% tabs type_parameters class=tabs-scala-version%} +{% tab 'Scala 2' for=type_parameters %} +```scala +class List[A] { + def map[B](f: A => B): List[B] = ... +} +``` +{% endtab %} +{% tab 'Scala 3' for=type_parameters %} +```scala +class List[A]: + def map[B](f: A => B): List[B] = ... +``` +{% endtab %} +{% endtabs %} If the type parameter has a more specific meaning, a descriptive name should be used, following the class naming conventions (as opposed to an all-uppercase style): - // Right - class Map[Key, Value] { - def get(key: Key): Value - def put(key: Key, value: Value): Unit - } - - // Wrong; don't use all-caps - class Map[KEY, VALUE] { - def get(key: KEY): VALUE - def put(key: KEY, value: VALUE): Unit - } +{% tabs type_parameters_2 class=tabs-scala-version%} +{% tab 'Scala 2' for=type_parameters_2 %} +```scala +// Right +class Map[Key, Value] { + def get(key: Key): Value + def put(key: Key, value: Value): Unit +} + +// Wrong; don't use all-caps +class Map[KEY, VALUE] { + def get(key: KEY): VALUE + def put(key: KEY, value: VALUE): Unit +} +``` +{% endtab %} +{% tab 'Scala 3' for=type_parameters_2 %} +```scala +// Right +class Map[Key, Value]: + def get(key: Key): Value + def put(key: Key, value: Value): Unit + +// Wrong; don't use all-caps +class Map[KEY, VALUE]: + def get(key: KEY): VALUE + def put(key: KEY, value: VALUE): Unit +``` +{% endtab %} +{% endtabs %} If the scope of the type parameter is small enough, a mnemonic can be used in place of a longer, descriptive name: - class Map[K, V] { - def get(key: K): V - def put(key: K, value: V): Unit - } +{% tabs type_parameters_3 class=tabs-scala-version%} +{% tab 'Scala 2' for=type_parameters_3 %} +```scala +class Map[K, V] { + def get(key: K): V + def put(key: K, value: V): Unit +} +``` +{% endtab %} +{% tab 'Scala 3' for=type_parameters_3 %} +```scala +class Map[K, V]: + def get(key: K): V + def put(key: K, value: V): Unit +``` +{% endtab %} +{% endtabs %} ### Higher-Kinds and Parameterized Type parameters @@ -318,7 +523,19 @@ Higher-kinds are theoretically no different from regular type parameters similar, however it is preferred to use a descriptive name rather than a single letter, for clarity: - class HigherOrderMap[Key[_], Value[_]] { ... } +{% tabs type_parameters_4 class=tabs-scala-version%} +{% tab 'Scala 2' for=type_parameters_4 %} +```scala +class HigherOrderMap[Key[_], Value[_]] { ... } +``` +{% endtab %} +{% tab 'Scala 3' for=type_parameters_4 %} +```scala +class HigherOrderMap[Key[_], Value[_]]: + ... +``` +{% endtab %} +{% endtabs %} The single letter form is (sometimes) acceptable for fundamental concepts used throughout a codebase, such as `F[_]` for Functor and `M[_]` for @@ -328,7 +545,13 @@ In such cases, the fundamental concept should be something well known and understood to the team, or have tertiary evidence, such as the following: - def doSomething[M[_]: Monad](m: M[Int]) = ... +{% tabs type_parameters_5 %} +{% tab 'Scala 2 and 3' for=type_parameters_5 %} +```scala +def doSomething[M[_]: Monad](m: M[Int]) = ... +``` +{% endtab %} +{% endtabs %} Here, the type bound `: Monad` offers the necessary evidence to inform the reader that `M[_]` is the type of the Monad. @@ -337,7 +560,13 @@ the reader that `M[_]` is the type of the Monad. Annotations, such as `@volatile` should be in lower camel case: - class cloneable extends StaticAnnotation +{% tabs annotations %} +{% tab 'Scala 2 and 3' for=annotations %} +```scala +class cloneable extends StaticAnnotation +``` +{% endtab %} +{% endtabs %} This convention is used throughout the Scala library, even though it is not consistent with Java annotation naming. @@ -345,16 +574,28 @@ not consistent with Java annotation naming. Note: This convention applied even when using type aliases on annotations. For example, when using JDBC: - type id = javax.persistence.Id @annotation.target.field - @id - var id: Int = 0 +{% tabs annotations_2 %} +{% tab 'Scala 2 and 3' for=annotations_2 %} +```scala +type id = javax.persistence.Id @annotation.target.field +@id +var id: Int = 0 +``` +{% endtab %} +{% endtabs %} ## Special Note on Brevity Because of Scala's roots in the functional languages, it is quite normal for local names to be very short: - def add(a: Int, b: Int) = a + b +{% tabs local_names %} +{% tab 'Scala 2 and 3' for=local_names %} +```scala +def add(a: Int, b: Int) = a + b +``` +{% endtab %} +{% endtabs %} This would be bad practice in languages like Java, but it is *good* practice in Scala. This convention works because properly-written Scala diff --git a/_style/nested-blocks.md b/_style/nested-blocks.md index 6970f050f9..cddf8a1027 100644 --- a/_style/nested-blocks.md +++ b/_style/nested-blocks.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Nested Blocks - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 5 - previous-page: types next-page: declarations --- @@ -16,9 +13,15 @@ next-page: declarations Opening curly braces (`{`) must be on the same line as the declaration they represent: - def foo = { - ... - } +{% tabs braces %} +{% tab 'Scala 2 and 3' for=braces %} +```scala +def foo = { + ... +} +``` +{% endtab %} +{% endtabs %} Technically, Scala's parser *does* support GNU-style notation with opening braces on the line following the declaration. However, the @@ -27,22 +30,70 @@ the way in which semi-colon inference is implemented. Many headaches will be saved by simply following the curly brace convention demonstrated above. +## End markers Scala 3 only + +In Scala 3 using curly braces is discouraged in favor of significant indentation. +The `end` keyword allows to provide a visual cue on where a particular block ends and can be useful especially in cases +of long method/class definitions or many nested control structures: + +{% tabs end_marker_1 %} +{% tab 'Scala 3 Only' for=end_marker_1 %} +```scala +class LongClass: +// many fields, methods etc. + def longMethod = + val x = 0 + // a large block of code + end longMethod +end LongClass +``` +{% endtab %} +{% endtabs %} + +The `end` marker should be indented with the same number of spaces as the element it closes: + +{% tabs end_marker_2 %} +{% tab 'Scala 3 Only' for=end_marker_2 %} +```scala +for x <- 1 to 10 do + if x > 5 then + while foo do + ... + end while + end if +end for +``` +{% endtab %} +{% endtabs %} + ## Parentheses In the rare cases when parenthetical blocks wrap across lines, the opening and closing parentheses should be unspaced and generally kept on the same lines as their content (Lisp-style): - (this + is a very ++ long * - expression) +{% tabs parentheses %} +{% tab 'Scala 2 and 3' for=parentheses %} +```scala +(this + is a very ++ long * + expression) +``` +{% endtab %} +{% endtabs %} Parentheses also serve to disable semicolon inference, and so allow the developer to start lines with operators, which some prefer: - ( someCondition - || someOtherCondition - || thirdCondition - ) +{% tabs parentheses_2 %} +{% tab 'Scala 2 and 3' for=parentheses_2 %} +```scala +( someCondition +|| someOtherCondition +|| thirdCondition +) +``` +{% endtab %} +{% endtabs %} A trailing parenthesis on the following line is acceptable in this case, for aesthetic reasons. diff --git a/_style/overview.md b/_style/overview.md deleted file mode 100644 index c86bf6a4ce..0000000000 --- a/_style/overview.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: style-guide -title: Overview - -partof: style -overview-name: "Style Guide" - -num: 1 - -next-page: indentation ---- - -Please see the [table of contents of the style guide]({{ site.baseurl }}/style) for an outline-style overview. diff --git a/_style/scaladoc.md b/_style/scaladoc.md index 12dc9528a5..073d4f657a 100644 --- a/_style/scaladoc.md +++ b/_style/scaladoc.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Scaladoc - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 10 - previous-page: files --- @@ -30,39 +27,63 @@ by detailed documentation, in the three common styles of indentation. Javadoc style: - /** - * Provides a service as described. - * - * This is further documentation of what we're documenting. - * Here are more details about how it works and what it does. - */ - def member: Unit = () +{% tabs scaladoc_1 %} +{% tab 'Scala 2 and 3' for=scaladoc_1 %} +```scala +/** + * Provides a service as described. + * + * This is further documentation of what we're documenting. + * Here are more details about how it works and what it does. + */ +def member: Unit = () +``` +{% endtab %} +{% endtabs %} Scaladoc style, with gutter asterisks aligned in column two: - /** Provides a service as described. - * - * This is further documentation of what we're documenting. - * Here are more details about how it works and what it does. - */ - def member: Unit = () +{% tabs scaladoc_2 %} +{% tab 'Scala 2 and 3' for=scaladoc_2 %} +```scala +/** Provides a service as described. + * + * This is further documentation of what we're documenting. + * Here are more details about how it works and what it does. + */ +def member: Unit = () +``` +{% endtab %} +{% endtabs %} Scaladoc style, with gutter asterisks aligned in column three: - /** Provides a service as described. - * - * This is further documentation of what we're documenting. - * Here are more details about how it works and what it does. - */ - def member: Unit = () +{% tabs scaladoc_3 %} +{% tab 'Scala 2 and 3' for=scaladoc_3 %} +```scala +/** Provides a service as described. + * + * This is further documentation of what we're documenting. + * Here are more details about how it works and what it does. + */ +def member: Unit = () +``` +{% endtab %} +{% endtabs %} Because the comment markup is sensitive to whitespace, the tool must be able to infer the left margin. When only a simple, short description is needed, a one-line format can be used: - /** Does something very simple */ - def simple: Unit = () +{% tabs scaladoc_4 %} +{% tab 'Scala 2 and 3' for=scaladoc_4 %} +```scala +/** Does something very simple */ +def simple: Unit = () +``` +{% endtab %} +{% endtabs %} Note that, in contrast to the Javadoc convention, the text in the Scaladoc styles begins on the first line of the comment. @@ -116,11 +137,16 @@ Provide Scaladoc for each package. This goes in a file named `package.scala` in your package's directory and looks like so (for the package `parent.package.name.mypackage`): - package parent.package.name +{% tabs scaladoc_5 %} +{% tab 'Scala 2 and 3' for=scaladoc_5 %} +```scala +package parent.package.name - /** This is the Scaladoc for the package. */ - package object mypackage { - } +/** This is the Scaladoc for the package. */ +package object mypackage {} +``` +{% endtab %} +{% endtabs %} A package's documentation should first document what sorts of classes are part of the package. Secondly, document the general sorts of things @@ -132,26 +158,32 @@ major classes, with some basic examples of how to use the classes in that package. Be sure to reference classes using the square-bracket notation: - package my.package - /** Provides classes for dealing with complex numbers. Also provides - * implicits for converting to and from `Int`. - * - * ==Overview== - * The main class to use is [[my.package.complex.Complex]], as so - * {{ "{{{" }} - * scala> val complex = Complex(4,3) - * complex: my.package.complex.Complex = 4 + 3i - * }}} - * - * If you include [[my.package.complex.ComplexConversions]], you can - * convert numbers more directly - * {{ "{{{" }} - * scala> import my.package.complex.ComplexConversions._ - * scala> val complex = 4 + 3.i - * complex: my.package.complex.Complex = 4 + 3i - * }}} - */ - package complex {} +{% tabs scaladoc_6 %} +{% tab 'Scala 2 and 3' for=scaladoc_6 %} +```scala +package my.package +/** Provides classes for dealing with complex numbers. Also provides + * implicits for converting to and from `Int`. + * + * ==Overview== + * The main class to use is [[my.package.complex.Complex]], as so + * {{ "{{{" }} + * scala> val complex = Complex(4,3) + * complex: my.package.complex.Complex = 4 + 3i + * }}} + * + * If you include [[my.package.complex.ComplexConversions]], you can + * convert numbers more directly + * {{ "{{{" }} + * scala> import my.package.complex.ComplexConversions._ + * scala> val complex = 4 + 3.i + * complex: my.package.complex.Complex = 4 + 3i + * }}} + */ +package complex {} +``` +{% endtab %} +{% endtabs %} ## Classes, Objects, and Traits @@ -171,14 +203,19 @@ output. If the class should be created using a constructor, document it using the `@constructor` syntax: - /** A person who uses our application. - * - * @constructor create a new person with a name and age. - * @param name the person's name - * @param age the person's age in years - */ - class Person(name: String, age: Int) { - } +{% tabs scaladoc_7 %} +{% tab 'Scala 2 and 3' for=scaladoc_7 %} +```scala +/** A person who uses our application. + * + * @constructor create a new person with a name and age. + * @param name the person's name + * @param age the person's age in years + */ +class Person(name: String, age: Int) +``` +{% endtab %} +{% endtabs %} Depending on the complexity of your class, provide an example of common usage. @@ -192,36 +229,84 @@ such here, deferring the specifics to the Scaladoc for the `apply` method(s). If your object *doesn't* use `apply` as a factory method, be sure to indicate the actual method names: - /** Factory for [[mypackage.Person]] instances. */ - object Person { - /** Creates a person with a given name and age. - * - * @param name their name - * @param age the age of the person to create - */ - def apply(name: String, age: Int) = {} - - /** Creates a person with a given name and birthdate - * - * @param name their name - * @param birthDate the person's birthdate - * @return a new Person instance with the age determined by the - * birthdate and current date. - */ - def apply(name: String, birthDate: java.time.LocalDate) = {} - } +{% tabs scaladoc_8 class=tabs-scala-version%} +{% tab 'Scala 2' for=scaladoc_8 %} +```scala +/** Factory for [[mypackage.Person]] instances. */ +object Person { + /** Creates a person with a given name and age. + * + * @param name their name + * @param age the age of the person to create + */ + def apply(name: String, age: Int) = { ... } + + /** Creates a person with a given name and birthdate + * + * @param name their name + * @param birthDate the person's birthdate + * @return a new Person instance with the age determined by the + * birthdate and current date. + */ + def apply(name: String, birthDate: java.time.LocalDate) = { ... } +} +``` +{% endtab %} +{% tab 'Scala 3' for=scaladoc_8 %} +```scala +/** Factory for [[mypackage.Person]] instances. */ +object Person: + /** Creates a person with a given name and age. + * + * @param name their name + * @param age the age of the person to create + */ + def apply(name: String, age: Int) = + ... + + /** Creates a person with a given name and birthdate + * + * @param name their name + * @param birthDate the person's birthdate + * @return a new Person instance with the age determined by the + * birthdate and current date. + */ + def apply(name: String, birthDate: java.time.LocalDate) = + ... +``` +{% endtab %} +{% endtabs %} If your object holds implicit conversions, provide an example in the Scaladoc: - /** Implicit conversions and helpers for [[mypackage.Complex]] instances. - * - * {{ "{{{" }} - * import ComplexImplicits._ - * val c: Complex = 4 + 3.i - * }}} - */ - object ComplexImplicits {} +{% tabs scaladoc_9 class=tabs-scala-version%} +{% tab 'Scala 2' for=scaladoc_9 %} +```scala +/** Implicit conversions and helpers for [[mypackage.Complex]] instances. + * + * {{ "{{{" }} + * import ComplexImplicits._ + * val c: Complex = 4 + 3.i + * }}} + */ +object ComplexImplicits { ... } +``` +{% endtab %} +{% tab 'Scala 3' for=scaladoc_9 %} +```scala +/** Implicit conversions and helpers for [[mypackage.Complex]] instances. + * + * {{ "{{{" }} + * import ComplexImplicits.* + * val c: Complex = 4 + 3.i + * }}} + */ +object ComplexImplicits: + ... +``` +{% endtab %} +{% endtabs %} #### Traits diff --git a/_style/types.md b/_style/types.md index 67df05af71..186adfee94 100644 --- a/_style/types.md +++ b/_style/types.md @@ -1,12 +1,9 @@ --- layout: style-guide title: Types - partof: style -overview-name: "Style Guide" - +overview-name: Style Guide num: 4 - previous-page: naming-conventions next-page: nested-blocks --- @@ -20,7 +17,13 @@ You should almost never annotate the type of a private field or a local variable, as their type will usually be immediately evident in their value: - private val name = "Daniel" +{% tabs types_1 %} +{% tab 'Scala 2 and 3' for=types_1 %} +```scala +private val name = "Daniel" +``` +{% endtab %} +{% endtabs %} However, you may wish to still display the type where the assigned value has a complex or non-obvious form. @@ -37,8 +40,14 @@ to improve compile times. Function values support a special case of type inference which is worth calling out on its own: - val ls: List[String] = ... - ls.map(str => str.toInt) +{% tabs types_2 %} +{% tab 'Scala 2 and 3' for=types_2 %} +```scala +val ls: List[String] = ... +ls.map(str => str.toInt) +``` +{% endtab %} +{% endtabs %} In cases where Scala already knows the type of the function value we are declaring, there is no need to annotate the parameters (in this case, @@ -52,7 +61,13 @@ annotation of parameter types. Type annotations should be patterned according to the following template: - value: Type +{% tabs types_3 %} +{% tab 'Scala 2 and 3' for=types_3 %} +```scala +value: Type +``` +{% endtab %} +{% endtabs %} This is the style adopted by most of the Scala standard library and all of Martin Odersky's examples. The space between value and type helps the @@ -60,7 +75,13 @@ eye in accurately parsing the syntax. The reason to place the colon at the end of the value rather than the beginning of the type is to avoid confusion in cases such as this one: - value ::: +{% tabs types_4 %} +{% tab 'Scala 2 and 3' for=types_4 %} +```scala +value ::: +``` +{% endtab %} +{% endtabs %} This is actually valid Scala, declaring a value to be of type `::`. Obviously, the prefix-style annotation colon muddles things greatly. @@ -88,9 +109,15 @@ colon. Function types should be declared with a space between the parameter type, the arrow and the return type: - def foo(f: Int => String) = ... +{% tabs types_5 %} +{% tab 'Scala 2 and 3' for=types_5 %} +```scala +def foo(f: Int => String) = ... - def bar(f: (Boolean, Double) => List[String]) = ... +def bar(f: (Boolean, Double) => List[String]) = ... +``` +{% endtab %} +{% endtabs %} Parentheses should be omitted wherever possible (e.g. methods of arity-1, such as `Int => String`). @@ -100,17 +127,29 @@ arity-1, such as `Int => String`). Scala has a special syntax for declaring types for functions of arity-1. For example: - def map[B](f: A => B) = ... +{% tabs types_6 %} +{% tab 'Scala 2 and 3' for=types_6 %} +```scala +def map[B](f: A => B) = ... +``` +{% endtab %} +{% endtabs %} Specifically, the parentheses may be omitted from the parameter type. Thus, we did *not* declare `f` to be of type `(A) => B`, as this would have been needlessly verbose. Consider the more extreme example: - // wrong! - def foo(f: (Int) => (String) => (Boolean) => Double) = ... +{% tabs types_7 %} +{% tab 'Scala 2 and 3' for=types_7 %} +```scala +// wrong! +def foo(f: (Int) => (String) => (Boolean) => Double) = ... - // right! - def foo(f: Int => String => Boolean => Double) = ... +// right! +def foo(f: Int => String => Boolean => Double) = ... +``` +{% endtab %} +{% endtabs %} By omitting the parentheses, we have saved six whole characters and dramatically improved the readability of the type expression. @@ -121,21 +160,33 @@ Structural types should be declared on a single line if they are less than 50 characters in length. Otherwise, they should be split across multiple lines and (usually) assigned to their own type alias: - // wrong! - def foo(a: { def bar(a: Int, b: Int): String; val baz: List[String => String] }) = ... +{% tabs types_8 %} +{% tab 'Scala 2 and 3' for=types_8 %} +```scala +// wrong! +def foo(a: { def bar(a: Int, b: Int): String; val baz: List[String => String] }) = ... - // right! - private type FooParam = { - val baz: List[String => String] - def bar(a: Int, b: Int): String - } +// right! +private type FooParam = { + val baz: List[String => String] + def bar(a: Int, b: Int): String +} - def foo(a: FooParam) = ... +def foo(a: FooParam) = ... +``` +{% endtab %} +{% endtabs %} Simpler structural types (under 50 characters) may be declared and used inline: - def foo(a: { val bar: String }) = ... +{% tabs types_9 %} +{% tab 'Scala 2 and 3' for=types_9 %} +```scala +def foo(a: { val bar: String }) = ... +``` +{% endtab %} +{% endtabs %} When declaring structural types inline, each member should be separated by a semi-colon and a single space, the opening brace should be