+ - 0:00:00
Notes for current slide
Notes for next slide

Pattern Matching
&
Reflection

Scala Functional Subset

FP Functions:

• Total

• Deterministic

• Pure or without actions

General JVM recommendations:

• Ignore null

???

Definition





"Reflection is the ability of a program to inspect, and possibly even modify itself"

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
robot.getClass.getMethods
.map(_.getName)
// res: Array[String] = Array(
// "equals",
// "toString",
// "hashCode",
// "position",
// "copy",
// "direction",
// "move",
// "productElementNames",
// ...

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
classOf[Robot]
.getDeclaredMethod("move")
.invoke(robot)
// res: Object = Robot(1, Forward)

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
classOf[Robot]
.getDeclaredMethod("move")
.invoke(robot)
// res: Object = Robot(1, Forward)

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
classOf[Robot]
.getDeclaredMethod("move")
.invoke(robot)
// res: Object = Robot(1, Forward)

Reflection

case class Robot(position: Int, direction: Direction) {
def move: Robot =
direction match {
case Backward =>
copy(position = position - 1)
case Forward =>
copy(position = position + 1)
}
}
sealed trait Direction
case object Forward extends Direction
case object Backward extends Direction
val robot = Robot(0, Forward)
classOf[Robot]
.getDeclaredMethod("move")
.invoke(robot)
// res: Object = Robot(1, Forward)

Reflection



        1. Runtime

        2. Compile time (Macros)

List in the standard library







case class ::[A](val head: A, private var next: List[A]) extends List[A]

List in the standard library







case class ::[A](val head: A, private var next: List[A]) extends List[A]

Runtime Reflection

val list = List(1,2,3)
// list: List[Int] = List(1, 2, 3)
val field = classOf[::[Int]].getDeclaredField("next")
field.setAccessible(true)
field.set(list, List(4,5,6))
list
// list: List[Int] = List(1, 4, 5, 6)

Runtime Reflection

val list = List(1,2,3)
// list: List[Int] = List(1, 2, 3)
val field = classOf[::[Int]].getDeclaredField("next")
field.setAccessible(true)
field.set(list, List(4,5,6))
list
// list: List[Int] = List(1, 4, 5, 6)

Runtime Reflection

val list = List(1,2,3)
// list: List[Int] = List(1, 2, 3)
val field = classOf[::[Int]].getDeclaredField("next")
field.setAccessible(true)
field.set(list, List(4,5,6))
list
// list: List[Int] = List(1, 4, 5, 6)

Runtime Reflection

val list = List(1,2,3)
// list: List[Int] = List(1, 2, 3)
val field = classOf[::[Int]].getDeclaredField("next")
field.setAccessible(true)
field.set(list, List(4,5,6))
list
// list: List[Int] = List(1, 4, 5, 6)

Runtime Reflection

val list = List(1,2,3)
// list: List[Int] = List(1, 2, 3)
val field = classOf[::[Int]].getDeclaredField("next")
field.setAccessible(true)
field.set(list, List(4,5,6))
list
// list: List[Int] = List(1, 4, 5, 6)

Mutate an immutable list 😱😱😱

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"
def increment(value: Any) =
if(value.isInstanceOf[Int])
value.asInstanceOf[Int] + 1
else if(value.isInstanceOf[Double])
value.asInstanceOf[Double] + 1.0
else
value

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"
def increment(value: Any) =
if(value.isInstanceOf[Int])
value.asInstanceOf[Int] + 1
else if(value.isInstanceOf[Double])
value.asInstanceOf[Double] + 1.0
else
value

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}


increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"
def increment(value: Any) =
if(value.isInstanceOf[Int])
value.asInstanceOf[Int] + 1
else if(value.isInstanceOf[Double])
value.asInstanceOf[Double] + 1.0
else
value

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case x: Option[Int] => x.map(_ + 1)
case other => other
}
increment(1)
// res: Any = 2
increment(5.3)
// res: Any = 6.3
increment("Hello")
// res: Any = "Hello"
increment(Some(1))
// res: Any = Some(2)

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case x: Option[Int] => x.map(_ + 1)
case other => other
}
increment(Some("Hello"))
// java.lang.ClassCastException: class String cannot be cast to class Integer

Pattern matching

def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case x: Option[Int] => x.map(_ + 1)
case other => other
}
increment(Some("Hello"))
// java.lang.ClassCastException: class String cannot be cast to class Integer
Some("Hello").isInstanceOf[Option[Int]]
// res: Boolean = true

Open Trait

trait Auth {
def authenticate(token: Token): Future[User]
}

Open Trait

trait Auth {
def authenticate(token: Token): Future[User]
}
class OktaAuth(client: RestClient) extends Auth { /* ... */ }
class MockAuth(users: Map[Token, User]) extends Auth { /* ... */ }

HTTP Server




HTTP Middleware




HTTP Middleware




Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}

Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}

Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden()) // 403 Status
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}

Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden()) // 403 Status
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}

Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden()) // 403 Status
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user) // pass it down the line
)
}

Authentication Middleware

def checkUser(request: Request, auth: Auth) =
request.headers.get("Authorization") match {
case None => reject(Forbidden()) // 403 Status
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user) // pass it down the line
)
}

Auth instance is defined in a config file

production.conf

{
auth : {
type : "Okta"
baseUrl : "https://okta.com/auth0"
api-key : "X28fd-OS0-8S"
}
}

local.conf

{
auth : {
type : "Mock"
users : {
"bob" : "abcd"
"eda" : "xxxx"
}
}
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ =>
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ =>
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}
enum Env {
case Local, UAT, Prod
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ =>
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}
enum Env {
case Local, UAT, Prod
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ =>
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}
enum Env {
case Local, UAT, Prod
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // same as before
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}
enum Env {
case Local, UAT, Prod
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ =>
request.headers.get("Authorization") match {
case None => reject(Forbidden())
case Some(token) =>
auth.authenticate(token).map(user =>
provide(user)
)
}
}

Increase load


Cache authenticate requests


Cache authenticate requests


Cache authenticate requests

class CachedAuth(
cache : Cache,
underlying: Auth,
) extends Auth { /* ... */ }

Cache authenticate requests

class CachedAuth(
cache : Cache,
underlying: Auth,
) extends Auth { /* ... */ }

Cache authenticate requests

class CachedAuth(
cache : Cache,
underlying: Auth,
) extends Auth { /* ... */ }

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // check header and dispatch to auth
}

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // check header and dispatch to auth
}


val env : Env = Prod
val auth: Auth = new CachedAuth(cache, new MockAuth(...))

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // check header and dispatch to auth
}


val env : Env = Prod
val auth: Auth = new CachedAuth(cache, new MockAuth(...))

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // check header and dispatch to auth
}


val env : Env = Prod
val auth: Auth = new CachedAuth(cache, new MockAuth(...))

Authentication Middleware

def checkUser(request: Request, auth: Auth, env: Env) =
(env, auth) match {
case (Prod, _: MockAuth) => InternalServerError()
case _ => // check header and dispatch to auth
}


val env : Env = Prod
val auth: Auth = new CachedAuth(cache, new MockAuth(...))

Pattern Matching

Unsafe

auth match {
case _: MockAuth => ...
case _ => ...
}

Safe

env match {
case Local => ...
case UAT => ...
case Prod => ...
}

Safe pattern matching keywords

1. final class

2. sealed class/trait

3. case class/object

4. primitives (Int, Boolean, ...)

Unsafe pattern matching keywords

1. abstract class

2. open class/trait (new in Scala 3)

3. no keyword, open by default

Contract

trait Auth {
def authenticate(token: Token): Future[Option[User]]
// if the token is valid, revoke makes it invalid
def revoke(token: Token): Future[Unit]
}

Contract

trait Auth {
def authenticate(token: Token): Future[Option[User]]
// if the token is valid, revoke makes it invalid
def revoke(token: Token): Future[Unit]
}

Contract

trait Auth {
def authenticate(token: Token): Future[Option[User]]
// if the token is valid, revoke makes it invalid
def revoke(token: Token): Future[Unit]
}
def checkAuth(auth: Auth) =
test("revoke makes token invalid") {
forAll { (token: Token) =>
for {
_ <- auth.revoke(token)
user <- auth.authenticate(token)
} assert(user == None)
}
}

Contract

trait Auth {
def authenticate(token: Token): Future[Option[User]]
// if the token is valid, revoke makes it invalid
def revoke(token: Token): Future[Unit]
}
def checkAuth(auth: Auth) =
test("revoke makes token invalid") {
forAll { (token: Token) =>
for {
_ <- auth.revoke(token)
user <- auth.authenticate(token)
} assert(user == None)
}
}

Contract

trait Auth {
def authenticate(token: Token): Future[Option[User]]
// if the token is valid, revoke makes it invalid
def revoke(token: Token): Future[Unit]
}
def checkAuth(auth: Auth) =
test("revoke makes token invalid") {
forAll { (token: Token) =>
for {
_ <- auth.revoke(token)
user <- auth.authenticate(token)
} assert(user == None)
}
}

Summary


Ignore runtime reflection

But, pattern matching uses reflection

Two dangerous use cases:

  • Classes with type parameters

  • Classes meant for extension

Scala 3: Matchable trait


def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}
// warning pattern selector should be an instance of
// Matchable, but it has unmatchable type Any instead

Scala 3: Matchable trait


def increment(value: Any) =
value match {
case x: Int => x + 1
case x: Double => x + 1.0
case other => other
}
// warning pattern selector should be an instance of
// Matchable, but it has unmatchable type Any instead

Scala Functional Subset

FP Functions:

• Total

• Deterministic

• Pure or without actions

General JVM recommendations:

• Ignore null

???

Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow