Classes
Class, object, and 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 Entity
with Logging
with Identifiable
with 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 20-ish
chars, one line) definitions:
class Foo {
val bar = 42
val baz = "Daniel"
def doSomething(): Unit = { ... }
def add(x: Int, y: Int): Int = x + y
}
Fields should precede methods in a scope. The only exception is if the
val
has a block definition (more than one expression) and performs
operations which may be deemed “method-like” (e.g. computing the length
of a List
). In such cases, the non-trivial val
may be declared at a
later point in the file as logical member ordering would dictate. This
rule only applies to val
and lazy val
! It becomes very difficult
to track changing aliases if var
declarations are strewn throughout
class file.
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
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)
}
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 braces:
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
}
}
Multiple Parameter Lists
In general, you should only use multiple parameter lists if there is a good reason to do so. These methods (or similarly declared functions) have a more verbose declaration and invocation syntax and are harder for less-experienced Scala developers to understand.
There are three main reasons you should do this:
-
For a fluent API
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") }
-
Implicit Parameters
When using implicit parameters, and you use the
implicit
keyword, it applies to the entire parameter list. Thus, if you want only some parameters to be implicit, you must use multiple parameter lists. -
For type inference
When invoking a method using only some of the parameter lists, the 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)
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 different styles in use:
-
Split the parameter lists, one parameter per line with trailing commas 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 = { ... }
-
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 = { ... }
Higher-Order Functions
It’s worth keeping in mind when declaring higher-order functions the
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) = ...
In Scala, the preferred style is the exact inverse:
def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B = ...
By placing the function parameter last, we have enabled invocation syntax like the following:
foldLeft(List(1, 2, 3, 4))(0)(_ + _)
The function value in this invocation is not wrapped in parentheses; it
is syntactically quite disconnected from the function itself
(foldLeft
). This style is preferred for its brevity and cleanliness.
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 braces should be separated from the code within them by a one-space gap, to give the visually busy braces “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 braces 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 brace on the same line as the assignment or
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
}
As noted earlier, function values should leverage type inference whenever possible.