Study notes of serveral videos.

1 12 steps to better Scala

This video talks best practices in four areas: data, inheritance, funciton and type.

1.1 Data

Step 1: Model with algebraic data types: product, sum and mixed types.

Step 2: Refine with smart constructors Instead of case class Email(value: String), using a method that domain logic only works on valid domain data.

1
2
3
4
sealed abstract case class Email private (value: String)
object Email {
def fromString(value: String): Option[Email] = if (check(value) Some(new Email(value){}) else None
}

Step 3: embrace variance

Step 4: learn to love folds. Option, Either or Try have fold method to handle both cases.

1.2 Inheritance

Step 5: prefer type classes over interface. The interface doesn’t work in deserialization.

Step 6: make methods final or abstract. Inheritance tangles types via super.m() and it is hard to understand it.

Step 7: use type bound only for variance. As inheritance, type bounds couple types. Types are erased at runtime. No asInstanceOf, no isInstanceOf. Prism can help. Type class may help.

1.3 Functions

Step 8: Prefer values over expected errors. Use Option/Try/Either/ZIO for possible failures. No error type: Option; Throwable: Try; Any error (pure): Either; Any error (effect): ZIO

Step 9: defer non-determinism. Move the non-determinism out of the local scope.

Step 10: describe, don’t do. Create a data structure to describe what you want to do. It is easy to test the data structure.

1.4 Types

Step 11: Prefer global coherency. Implicit value is sensitive to location of the code. Defintion: for any type, at most one implicit that Scala can find. Better to put implicit into a type’s companion object.

Step 12: Peter one type per scope. For example: do(true, false, true, false) could take wrong arguments. Use case class for each case. Instead of transfer(from: Account, to: Account), use transfer (receiver, sender). Sometimes use shadowing to hide outer variable.