Why Scala?
Marc Saegesser (@marcsaegesser)
August 27, 2019
What is Scala?
- Scala is a JVM language*
- Compiles to Java byte code
- Runs anywhere there's JVM
- Interoperability between Java and Scala
- Multi-paradigm language
- Object oriented programming
- Functional programming
def factorial(x: BigInt): BigInt =
if(x == 0)
1
else
x * factorial(x-1)
Some Simple Scala
// In Java
import java.util.BigInteger
BigInteger factorial(BigIteger x) {
if(x == BigInteger.ZERO) {
return BigInteger.ONE;
} else {
return x.multiply(factorial(x.subtract(BigInteger.ONE));
}
}The equivalent in Java
Programming in Scala (Chapter 2) http://a.co/c1HfcNR
Growing a Language

A 'Real-World' Example
Sort Of

package puzzle
sealed trait Tile {
def label: String
def edges: Edges
def withConstraint(constraint: Constraint): Vector[FixedTile]
def show: String = s"$label. $edges"
def matchesConstraint(c: Constraint): Boolean = // ...
}
case class FreeTile(label: String, edges: Edges) extends Tile {
val rotations: Vector[FixedTile] = edges.rotations.map(r => FixedTile(label, r))
def withConstraint(c: Constraint): Vector[FixedTile] =
rotations.filter(_.matchesConstraint(c))
}
case class FixedTile(label: String, edges: Edges) extends Tile {
def withConstraint(c: Constraint): Vector[FixedTile] =
if(matchesConstraint(c)) Vector(this)
else Vector()
}
object Tile {
def apply(label: String, edges: Array[String]): Tile = FreeTile(label, Edges(edges))
}Traits, Classes and Objects
case class FreeTile(label: String, edges: Edges) extends Tile {
val rotations: Vector[FixedTile] = edges.rotations.map(r => FixedTile(label, r))
def withConstraint(c: Constraint): Vector[FixedTile] =
rotations.filter(_.matchesConstraint(c))
}
Traits, Classes and Objects
trait BoardBuilder {
def boardsFrom(board: Board): Vector[Board]
}
object SymmetryBuilder extends BoardBuilder {
def updateTiles(ts: Vector[Tile], free: Tile, fixed: Tile,
currIdx: Int, destIdx: Int): Vector[Tile] = ???
def boardsFrom(board: Board): Vector[Board] = {
board.freeTiles.headOption.map { case (free, point) => // The first free tile and its index
val c = board.constraintsFor(point) // Constraints for point
board.freeTiles
.map { case (t, i) => (t.withConstraint(c), i) } // Constraint search for point
.filterNot { case (ts, _) =>
ts.isEmpty || // Ignore tiles with no matches
(point == 0 && ts.head.label >= "7") || // Symmetry constraints
((point == 2 || point == 6 || point == 8) && (ts.head.label < board.tiles(0).label))
}.flatMap { case (ts, i) =>
ts.map(t => new Board(updateTiles(board.tiles, free, t, point, i)))
}
}.getOrElse(Vector())
}
}The Core Algorithm
sealed trait Option[A] { // ... }
case class Some(a: A) extends Option[A] { // ... }
case object None extends Option[A] { // ... }
Option, Either and Try
sealed trait Option[A] { // ... }
case class Some(a: A) extends Option[A] { // ... }
case object None extends Option[A] { // ... }
val aValue: Option[Int] = Some(42)
val negValue: Option[Int] = Some(-42)
val noValue: Option[Int] = None
Option, Either and Try
sealed trait Option[A] { // ... }
case class Some(a: A) extends Option[A] { // ... }
case object None extends Option[A] { // ... }
val aValue: Option[Int] = Some(42)
val negValue: Option[Int] = Some(-42)
val noValue: Option[Int] = None
trait Option[A] {
def map[B](f: A => B): Option[B]
}
aValue.map(_.toString) // Some("42")
noValue.map(_.toString) // None
Option, Either and Try
sealed trait Option[A] { // ... }
case class Some(a: A) extends Option[A] { // ... }
case object None extends Option[A] { // ... }
val aValue: Option[Int] = Some(42)
val negValue: Option[Int] = Some(-42)
val noValue: Option[Int] = None
trait Option[A] {
def map[B] (f: A => B ): Option[B]
def flatMap[B](f: A => Option[B]): Option[B]
}
aValue.map(_.toString) // Some("42")
noValue.map(_.toString) // None
aValue.flatMap { x => if(x > 0) Some(x.toString) else None } // Some("42")
negValue.flatMap { x => if(x > 0) Some(x.toString) else None } // None
noValue.flatMap { x => if(x > 0) Some(x.toString) else None } // NoneOption, Either and Try
sealed trait Either[A, B]
case class Left[A, B](a: A) extends Either[A, B]
case class Right[A, B](b: B) extends Either[A, B]
Option, Either and Try
sealed trait Either[A, B]
case class Left[A, B](a: A) extends Either[A, B]
case class Right[A, B](b: B) extends Either[A, B]
val aRight: Either[String, Int] = Right(42)
val aLeft: Either[String, Int] = Left("Oops!")
Option, Either and Try
sealed trait Either[A, B]
case class Left[A, B](a: A) extends Either[A, B]
case class Right[A, B](b: B) extends Either[A, B]
val aRight: Either[String, Int] = Right(42)
val aLeft: Either[String, Int] = Left("Oops!")
trait Either[A, B] {
def map[C] (f: B => C ): Either[A, C]
def flatMap[A1 >: A, C](f: B => Either[A1, C]): Either[A1, C]
}
aRight.map(_*2) // Right(84)
aLeft.map(_*2) // Left("Oops")
aRight.flatMap { x => if(x >= 0) Right(x*2) else Left("Negative") } // Right(84)
Option, Either and Try
sealed trait Try[A]
case class Success[A](a: A) extends Try[A]
case class Failure[A](t: Throwable) extends Try[A]
val success = Try { 2 / 1 } // Success(2)
val fail = Try { 2 / 0 } // Failure(java.lang.ArithmeticException: / by zero)
trait Try[A] {
def map[B] (f: A => B ): Try[B]
def flatMap[B](f: A => Try[C]): Try[C]
}
success.map(_*2) // Success(4)
fail.map(_*2) // Failure(java.lang.ArithmeticException: / by zero)
Option, Either and Try
def getDBConnection(): Connection
def getUserIdForName(conn: Connection, name: UserName): Int
def updateUserAddress(conn: Connection, user: Int, address: Address): ConfirmationCode
val conn = getDBConnection()
if (conn != null) {
val user = getUserIdForName(conn, name)
if (user != -1) {
val conf = updateUserAddress(conn, user, address)
if (conf != null) {
// Report success
} else {
// Report address change failure
}
} else {
// Report get user failure
}
} else {
// Report get connection failure
}
Option, Either and Try
An ugly example
def getDBConnection(): Either[Throwable, Connection]
def getUserIdForName(conn: Connection, name: UserName): Either[Throwable, UserId]
def updateUserAddress(conn: Connection, user: UserId,
address: Address): Either[Throwable, ConfirmationCode]
def updateAddress(name: UserName, newAddress: Address): Either[Throwable, ConfirmationCode] = ???
Option, Either and Try
def getDBConnection(): Either[Throwable, Connection]
def getUserIdForName(conn: Connection, name: UserName): Either[Throwable, UserId]
def updateUserAddress(conn: Connection, user: UserId,
address: Address): Either[Throwable, ConfirmationCode]
def updateAddress(name: UserName, newAddress: Address): Either[Throwable, ConfirmationCode] =
for {
c <- getDBConnection()
u <- getUserIdForName(c, name)
r <- updateUserAddress(c, u, address)
} yield r
Option, Either and Try
def getDBConnection(): Try[Connection]
def getUserIdForName(conn: Connection, name: UserName): Try[UserId]
def updateUserAddress(conn: Connection, user: UserId, address: Address): Try[ConfirmationCode]
def updateAddress(name: UserName, newAddress: Address): Try[ConfirmationCode] =
for {
c <- getDBConnection()
u <- getUserIdForName(c, name)
r <- updateUserAddress(c, u, address)
} yield r
A Digression on Types of Types
Option, Either, Try are examples of Sum types
AKA: Union types or Disjoint Union types or Variants
Tuples, classes, etc. are examples of Product types
Together Product and Sum types are called
Algebraic Data Types (ADTs)
An Example
Implementing a network client
- A network socket
- A heartbeat timer
- A connect timer
- A reconnect timer
- A reconnect count
Requirements
An Example
Implementing a network client
case class ConnectionState(
state: State,
socket: Option[Socket],
hbTimer: Option[Timer],
connTimer: Option[Timer],
reconnTimer: Option[Timer],
rconnCount: Option[Int]
)sealed trait ConnectionState
case object Disconnected extends ConnectionState
case class Connecting(s: Socket, connectTimer: TimerTask) extends ConnectionState
case class Connected(s: Socket, heartBeatTimer: TimerTask) extends ConnectionState
case class Reconnecting(s: Socket, attempts: Int, connectTimer: TimerTask) extends ConnectionState
case class ReconnectWait(attempts: Int, reconnectTimer: TimerTask) extends ConnectionState
Make Illegal State Unrepresentable
sealed trait ConnectionState
case object Disconnected extends ConnectionState
case class Connecting(s: Socket, connectTimer: TimerTask) extends ConnectionState
case class Connected(s: Socket, heartBeatTimer: TimerTask) extends ConnectionState
case class Reconnecting(s: Socket, attempts: Int, connectTimer: TimerTask) extends ConnectionState
case class ReconnectWait(attempts: Int, reconnectTimer: TimerTask) extends ConnectionState
def connect(host: String): Try[Unit] =
currentState match {
case Disconnected => ???
case Connecting(s, t) => ???
case Connected(s, hbt) => ???
case Reconnecting(s, a, ct) => ???
case ReconnectWait(a, rct) => ???
}Make Illegal State Unrepresentable
Make Illegal State Unrepresentable

Video: Effective ML by Yaron Minsky
So, Why Scala?
- Expressive type system
- Rich domain models express constraints in Types
- Type driven development
- Improve maintainability
- Easier refactoring
- Programming with functions
- Error handling - Errors are values
- Reason about code behavior
- Code re-use
The Bigger Picture
References and Further Reading
- Programming in Scala by Odersky, Spoon and Venners
- Growing a Language by Guy Steele - https://youtu.be/_ahvzDzKdB0
-
Effective ML by Yaron Minsky - https://youtu.be/-J8YyfrSwTk
-
The puzzle solver - https://github.com/marcsaegesser/ArrowPuzzleSolver
- Programming Scala by Dean Wampler
-
Functional Programming in Scala by Chiusano & Bjarnason
-
Functional Programming for Mortals by Sam Halliday
-
Functional and Reactive Domain Modeling by Debasish Ghosh
-
An Outsiders Guide to Statically Typed Functional Programming by Brian Marick
-
Exercism.io - https://exercism.io/tracks/scala
Why Scala?
By Marc Saegesser
Why Scala?
Chicago Java Users Group March 26, 2019
- 446