LARA

Scala Best Practice

Printing lists is easy

When pretty-printing an AST or simply debugging, avoid the following (albeit functional, unnecessarily long) code:

printList(listOfThings)
 
def printList(lst: List[A]): Unit = {
  def printList0(lst: List[A]): Unit = lst match {
    case x1 :: x2 :: Nil => print(x1 + ", "); printList0(lst.tail)
    case x1 :: Nil => print(x1)
    case Nil => ;
  }
 
  print("(")
  printList0(lst)
  print(")")
}

…and prefer the built-in:

listOfThings.mkString("(", ", ", ")")

Transforming lists

Suppose you have a node in your AST with a list of children (say, a block of statements), and you are writing a function that transforms your whole tree (say, you're type-checking it). Don't bother writing a recursive function with a match on empty/non-empty lists, prefer .map(…) instead:

def transform(tree: Tree): Tree = tree match {
  ...
  case Block(statList) => Block(statList.map(transform(_)))
  ...
}

(or use .foreach(…) if the method has Unit return type)

No more ''null'', here comes ''Option[A]''

Don't attach any semantic value to null. A good convention is to only use it for uninitialized references. When a method can return a value or no value, use an Option type to encode this fact, and return None when there is nothing to return, not null.

When you have a structure of trees of algebraic data types, using an Option to encode a, well, optional child is also better than using null. Errors involving option types are easier to avoid and/or pinpoint thanks to type-checking.

Manipulating option types

Working with options, one often finds oneself writing code such as:

myOpt match {
  case Some(value) => doSomething(value)
  case None => ;
}

or:

myOpt match {
  case Some(value) => Some(transform(value))
  case None => None
}

Both these constructs (and some others) can be greatly simplified by the use of higher-order functions. The first one becomes:

myOpt.foreach(doSomething(_))

…and the second one:

myOpt.map(transform(_))

(Many) more examples are available through the second link below.

A few blog posts about options and their use:

''case'' classes and objects

Know the difference between case classes with no constructor parameters and case objects: objects are really singletons, that is whatever you do to one occurrence of your object, you do it to all of them (since they are simply different references to the same thing). Objects are therefore well-suited to represent enumeration types, for instance, but classes should be preferred as soon as you start using fields or (potentially multiple) inheritance to attach more information to your instances.