Scala Best Practices
Rather than using random examples from Scala projects in the wild, these are meant as my evolving "best" way of doing things. If you're new to Scala, start here.
-
Scala returns the last expression in a function. Use this to return things over using
returndirectly. The latter is implemented by throwing and catching aNonLocalReturnExceptionand is inefficient. Details. -
Scala Collection Performance Characteristics. Of particular note,
List/Seqhas O(n) access (apply), update, and append. If often accessing indexes directly, useVector/IndexedSeqinstead. -
Use
valinstead ofvar. Immutable data is easier to reason about and simplifies concurrency. -
Use immutable data structures for the same reason as above. Details. An example of recursive depth-first traversal via immutable
Map,def traverseDepthFirst(treeId: TreeId) = {val tree = getTree(treeId)@scala.annotation.tailrecdef traverseDepthFirst(stack: Set[NodeId],visited: List[Node],nodeIdToAncestorIds: Map[NodeId, Set[NodeId]],ord: Int): (List[Node], Map[NodeId, Set[NodeId]]) =if (stack.isEmpty) (visited, nodeIdToAncestorIds)else {val state = tree.state(stack.head)traverseDepthFirst(state.childNodeIds.filter(pId => !visited.exists(_.nodeId === pId)) ++ stack.tail,createNode(tree, stack.head, ord, state) :: visited,nodeIdToAncestorIds + (stack.head -> Option(state.ancestorNodeIds).getOrElse(Set())),ord + 1)}traverseDepthFirst(Set(tree.rootNodeId), Nil, Map[NodeId, Set[NodeId]](), 0)} -
Prefer
Either[SomeError, ExpectedResult]tothrow. Exceptions aren't documented in function signatures, are inefficient, and violate structured programming principles. Details. -
Catch
NonFatalinstead ofThrowableto avoid catching fatal exceptions like out-of-memory errors. Details. -
Use
Optioninstead ofnulland do not callOption.get.nullisn't documented in function signatures and is error prone since the compiler cannot protect you. CallingOption.getdefeats the purpose ofOption, which is to explicitly handle theNonecase. Details.- Related: Prefer
Option's.mapand.foldto.isDefined/.isEmpty. They are more idiomatic.
// 👎if (someOption.isDefined) s"value=${someOption.get}" else "Default"// 👍someOption.fold("Default")(v => s"value=$v")- Related: use
Seq.headOptioninstead ofSeq.head. The latter throws aNoSuchElementExceptionon an empty list. Details.
- Related: Prefer
-
Prefer stronger types and pattern matching to
Any,AnyRef,isInstanceOf, andasInstanceOf. The latter circumvent the type system that is meant to protect you. Details. -
Use
===from the Cats library instead of==. The latter is syntactic sugar for Java's.equals, which accepts anObjectparameter. This allows comparing values of differing types. Details.import cats.instances.string._import cats.syntax.eq._"hi" === "hi" -
Use sealed traits for enumerations (until Scala 3 comes out). Sealed traits can only be extended in the file they're declared so the compiler knows all subtypes and can issue warnings for non-exhaustive matches. Details.
sealed trait JobStatus { def value: Int }object JobStatus {def apply(code: Int): JobStatus =code match {case 1 => Runningcase 2 => Completecase _ => Invalid}}case object Invalid extends JobStatus { val value = 0 }case object Running extends JobStatus { val value = 1 }case object Complete extends JobStatus { val value = 2 } -
Use simple constructor arguments for dependency injection instead of a framework.
-
Avoid hard-coding execution contexts, pass them as implicit parameters instead. Details.
-
Declare dependencies in
project/Dependencies.scala. If a dependency is failing to resolve, ensure you're using the proper number of%. To make it easier for automated dependency updates, prefersomeLib.revisionfor shared version numbers over variables. -
forcomprehensions are a simplified way of chainingflatMaps. Anything that exposesflatMapcan be used inforcomps. This includesFuture[T],Option[T],Either[T], etc. Use<-if the statement you're calling returns something you want toflatMapover. Otherwise, use=.for {item <- methodReturningFuture()myValue = 5res <- anotherFuture(item, myValue)} yield res// Is equivalent to,val res = methodReturningFuture().flatMap { item =>val myValue = 5anotherFuture(item, myValue)}