Using null in Scala code is not idiomatic at all.
private var author:Author = null
If you can't give a default value, you really should use
var author: Option[Author] = None
Don't rebind names for different uses:
Use vals
Avoid using 's to overload reserved names:
typ instead of 'type'
Don't prefix getters with get
money.count not money.getCount
Do not use relative imports from other packages :
Avoid
import com.twitter
import concurrent
In favor of the unambiguous
import com.twitter.concurrent
return
keyword exits from the enclosing method, not the enclosing function. It behaves like a restricted throw, so don't use return
unless it's in an exceptional situation where an API mandates error codes. In all other situations, prefer Option, Either, case classes, or, if necessary, exceptions.private type FooParam = { val baz: List[String => String] def bar(a: Int, b: Int): String } def foo(a: FooParam) = ... |
In singleton class types , visibility can be constrained by declaring the returned type:
def foo(): Foo with Bar = new Foo with Bar with Baz { ... } |
Traits that contain implementation are not directly usable from Java: extend an abstract class with the trait instead.
// Not directly usable from Java trait Animal { def eat(other: Animal) def eatMany(animals: Seq[Animal) = animals foreach(eat(_)) } // But this is: abstract class JavaAnimal extends Animal |
Keep traits short and orthogonal: don’t lump separable functionality into a trait, think of the smallest related ideas that fit together.
For example, imagine you have an something that can do IO:
trait IOer { def write(bytes: Array[Byte]) def read(n: Int): Array[Byte] } |
separate the two behaviors:
trait Reader { def read(n: Int): Array[Byte] } trait Writer { def write(bytes: Array[Byte]) } |
and mix them together to form what was an IOer:
new Reader with Writer… |
Names
Don't repeat names that are already encapsulated in package or object name.
Prefer:
object User{ def user(id: Int): Option[User] } |
Instead of:
object User{ def getUser(id: Int): Option[User] } |
Use pattern matching directly in function definitions whenever applicable
Use this :
list map { case Some(x) => x case None => default } |
Instead of :
list map { item => item match { case Some(x) => x case None => default } } |
Pattern matching works best when also combined with destructuring
Use this:
animal match { case Dog(breed) => "dog (%s)".format(breed) case other => other.species } |
Instead of:
animal match { case dog: Dog => "dog (%s)".format(dog.breed) case _ => animal.species } |
Don't use pattern matching for conditional execution when defaults make more sense.
Use:
val x = list.headOption getOrElse default |
NOT:
val x = list match { case head :: _ => head case Nil => default } |
Use:
trait Repository[Key, Value] { def get(key: Key): Option[Value] } |
Instead of:
trait Repository[Key, Value] { def get(key: Key): Value } |
/** * ServiceBuilder builds services * ... */ |
Instead of the standard ScalaDoc style:
/** ServiceBuilder builds services * ... */ |
When using collections, qualify names by importing scala.collection.immutable and/or scala.collection.mutable
e.g. "immutable.Map"
Scala allows return type annotations to be omitted.
Return type is especially important when instantiating objects with mixins
Use:
def make(): Service = new Service{} |
instead of
trait Service def make() = new Service { def getId = 123 } |
Use the mutable namespace explicitly. Use:
import scala.collection.mutable val set = mutable.Set() |
instead of importing scala.collection.mutable._ and referring to Set.
Require and assert both serve as executable documentation. Both are useful for situations in which the type system cannot express the required invariants. assert is used for invariants that the code assumes (either internal or external), for example:
val stream = getClass.getResourceAsStream("someclassdata") assert(stream != null) |
Whereas require is used to express API contracts:
def fib(n: Int) = { require(n > 0) ... } |
Some exceptions are fatal and should never be caught; the block of code below is unsafe for this reason:
try { operation() } catch { case _ => ... } // almost always wrong, as it would catch fatal errors that need to be propagated. |
This can be improved by using scala.util.control.NonFatal extractor to handle only non-fatal exceptions:
import scala.util.control.NonFatal ... try { operation() } catch { case NonFatal(exc) => ... } |
However, try/catch blocks are low-level control constructs which are not always as expressive or idiomatic as other aspects of Scala. Therefore, better still would be to use scala.util.Try in order to automatically only catch non-fatal errors and to have monadic access patterns for error handling (see https://www.scala-lang.org/api/current/scala/util/Try.html for more details):
import scala.util.{Try, Success, Failure} ... val result: Try[ResultType] = Try( operation() ) result match { case Success(v) => ... //do something with the return value case Failure(ex) => ... //non-fatal error handling here } |
Braces
Use braces for if, while for, etc. even when there's just a single line:
if (cond) { result } else { otherResult } for (x <- y if cond) yield { operation(x) } while (notDone()) { doTheThing() } |
val seq = Seq(1, 2, 3) val set = Set(1, 2, 3) val map = Map(1 -> "one", 2 -> "two", 3 -> "three") |
It is often appropriate to use lower level collections in situations that require better performance or space efficiency.
Use arrays instead of lists for large sequences (the immutable Vector collections provides a referentially transparent interface to arrays); and use buffers instead of direct sequence construction when performance matters.
for (item <- container) { if (item != 2) return } |
It is often preferable to call foreach, flatMap, map, and filter directly — but do use fors when they clarify.
List(1, 2, 3).map(_ * 2)
.filter(_ > 2)
.foldLeft(0)(_ + _) //> res1: Int = 10
Use implicits in the following situations:
Do not use implicits to do automatic conversions between similar datatypes (for example, converting a list to a stream); these are better done explicitly because the types have different semantics, and the reader should beware of these implications. Phrasing your problem in recursive terms often simplifies it, and if the tail call optimization applies (which can be checked by the @tailrec annotation), the compiler will even translate your code into a regular loop.
trait SocketFactory extends (SocketAddress => Socket |
A SocketFactory is a function that produces a Socket.
Using a type alias
type SocketFactory = SocketAddress => Socket |
is better. We may now provide function literals for values of type SocketFactory and also use function composition:
val addrToInet: SocketAddress => Long val inetToSocket: Long => Socket val factory: SocketFactory = addrToInet andThen inetToSocket |
Use type aliases when they provide convenient naming or clarify purpose, but do not alias types that are self-explanatory.
() => Int
is clearer than
type IntMaker = () => Int
IntMaker
since it is both short and uses a common type. However,
class ConcurrentPool[K, V] { type Queue = ConcurrentLinkedQueue[V] type Map = ConcurrentHashMap[K, Queue] ... } |
is helpful since it communicates purpose and enhances brevity.
people.sortBy(_.name)
.zipWithIndex
.map { case (person, i) => "%d: %s".format(i + 1, person.name) }
.foreach { println(_) }