Declarations
In this lesson, we will go over Scala's declaration rules, particularly for classes and function values.
Classes
Class/Object/Trait constructors should be declared all on one line unless the line becomes “too long” (about 100 characters). In that case, put each constructor argument on its own line with trailing commas.
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 = …}
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; closing parenthesis provides 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 Entitywith Loggingwith Identifiablewith Serializable {def firstMethod: Foo = …}
Ordering of Class Elements
All class/object/trait members should be declared interleaved with newlines. The only exceptions to this rule are var
and val
. These 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 around 20 chars, one line) definitions.
class Foo {val bar = 42val baz = "Daniel"def doSomething(): Unit = { ... }def add(x: Int, y: Int): Int = x + y}
Fields should precede methods in a scope.
Methods
Methods should be declared according to the following pattern:
def foo(bar: Baz): Bin = expr
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
You should specify a return type for all public members. Consider it documentation checked by the compiler. It also helps in preserving binary compatibility in the face of changing type inference (changes to the method implementation may propagate to the return type if it is inferred).
Local methods or private methods may omit their return type
private def foo(x: Int = 6, y: Int = 7) = x + y
Modifiers
Method modifiers should be given in the following order (when each is applicable):
-
Annotations, each on their own line
-
Override modifier (
override
) -
Access modifier (
protected
,private
) -
Implicit modifier (
implicit
) -
Final modifier (
final
) -
def
@Transaction@throws(classOf[IOException])override protected final def foo(): Unit = {...}
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
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)(_ + _)
The distinction between these two cases is somewhat artificial. Generally speaking, you should choose whichever style is more readable on a case-by-case basis. For example, your method declaration may be very long, while the expression body may be quite short. In such a case, it may be more readable to put the expression on the next line rather than making the declaration line too long.
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 brackets:
def sum(ls: List[String]): Int = {val ints = ls map (_.toInt)ints.foldLeft(0)(_ + _)}
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}
Not like this:
// WRONG!def sum(ls: List[Int]): Int = {ls match {case hd :: tail => hd + sum(tail)case Nil => 0}}
Fields
Fields should follow the declaration rules for methods, taking special note of access modifier ordering and annotation conventions.
Lazy vals should use the lazy
keyword directly before the val
.
private lazy val foo = bar()
Function Values
Scala provides a number of different syntactic options for declaring function values. For example, the following declarations are exactly equivalent:
val f1 = ((a: Int, b: Int) => a + b)
val f2 = (a: Int, b: Int) => a + b
val f3 = (_: Int) + (_: Int)
val f4: (Int, Int) => Int = (_ + _)
Of these styles, (1) and (4) are to be preferred at all times. (2) appears shorter in this example, but whenever the function value spans multiple lines (as is normally the case), this syntax becomes extremely unwieldy. Similarly, (3) is concise, but obtuse. It is difficult for the untrained eye to decipher the fact that this is even producing a function value.
When styles (1) and (4) are used exclusively, it becomes very easy to distinguish places in the source code where function values are used. Both styles make use of parentheses since they look clean on a single line.
Spacing
There should be no space between parentheses and the code they contain. Curly brackets should be separated from the code within them by a one-space gap, to give the visually busy brackets “breathing room”.
Multi-Expression Functions
Most function values are less trivial than the examples given above. Many contain more than one expression. In such cases, it is often more readable to split the function value across multiple lines. When this happens, only style (1) should be used, substituting brackets for parentheses. Style (4) becomes extremely difficult to follow when enclosed in large amounts of code. The declaration itself should loosely follow the declaration style for methods, with the opening bracket on the same line as the assignment or invocation, while the closing bracket is on its own line immediately following the last line of the function. Parameters should be on the same line as the opening bracket, as should the “arrow” (=>
).
val f1 = { (a: Int, b: Int) =>val sum = a + bsum}
As noted earlier, function values should leverage type inference whenever possible.