Scala for Java Developers
by Paweł Dolega @pdolega
and Paweł Lipski @plipski
and Michał Pociecha @mpociecha
Intro
Expectations
Curriculum
private[sbt] class AWSLambdaClient(region: Region) {
private lazy val lambdaClient = buildAwsClient
def deployLambda(lambdaParams: LambdaParams, roleArn: RoleArn, s3Params: S3Params):
Try[Either[CreateFunctionResult, UpdateFunctionCodeResult]] = {
for {
exists <- lambdaExist(lambdaParams.name)
} yield {
if (exists) {
Right(updateExistingLambda(lambdaParams, s3Params, roleArn))
} else {
Left(createNewLambda(lambdaParams, roleArn, s3Params))
}
}
}
...
Code sample 👍
// Free monad over the free functor of TerminalOp
type TerminalIO[A] = Free.FreeC[TerminalOp, A]
// It's a monad (but the instance is not inferrable)
implicit val monadTerminalIO: Monad[TerminalIO] =
Free.freeMonad[({type λ[α] = Coyoneda[TerminalOp, α]})#λ]
// Smart constructors in TerminalIO
def readLine: TerminalIO[String] = Free.liftFC(ReadLine)
def writeLine(s: String): TerminalIO[Unit] = Free.liftFC(WriteLine(s))
Code sample 👎
What's Scala
Separate language (like Java, Kotlin, Clojure)
Compile to bytecode / JVM runtime
Separate ecosystem (libs, tooling, build chain, philosophy etc)
Tooling
IDE
Build chain (Sbt, Gradle, Maven)
REPL
Scala hallmarks
Statically typed
Basic syntax lightweight
(yet allows complex constructs)
Interoperabillity
Used to be exploration / testing ground
FP bias
Stable (16 years old now)
Scala hallmarks
(with code examples in Repl - IntroSpec)
Type inference
Immutability
Everything is an expressions
Everything is an object
Scala Feel
Code exercise
com.virtuslab.scalaworkshop.intro.JavaFeel
* don't use "Open URL..." option
OO Basics
Basics of type system
OO
Classes
Objects
Traits
Inheritance
Tools
sbt + console (Scala repl)
:javap -p
Class example
class MyFirstClass(name: String) {
println("My class with name: ${name}")
}
var vs val
access modifiers
methods
getters / setters?
body execution
constructors
more constructors
More basic constructs
named argument
default parameters
extends & overriding
package & imports
qualified access modifiers
type parameters
operators as methods?
equality
More basic feats
Methods / operators
Singletons and companion objects
Equality
Default and named args
Basic inheritance
Singletons and companion objects
Exercise
ShapesSpec
Case classes
Case class
Have you ever heard of or used Project Lombok or AutoValue in Java?
They allow to generate code by using annotations instead of writing everything again and again.
In Scala a keyword 'case' before 'class' makes the compiler generate additional code for you.
Case class
We can easily verify what is generated by using javap for a class and its companion object.
Case class
case class Client(name: String, age: Int)
1) The constructor's parameters are promoted to public vals.
You can write
case class Client(val name: String, val age: Int)
but it's redundant.
Case classes are ideal to model immutable data.
Case class
2) You get for free toString, equals and hashCode methods which take the constructor parameters into account.
scala> case class Client(name: String, age: Int)
defined class Client
scala> val c1 = Client("Jan Kowalski", 27)
c1: Client = Client(Jan Kowalski,27)
scala> val c2 = Client("Jan Kowalski", 27)
c2: Client = Client(Jan Kowalski,27) // here we have toString used
scala> c1.toString // but just to confirm
res0: String = Client(Jan Kowalski,27)
scala> c1 == c2 // equals compares the structure instead of the references
res1: Boolean = true
scala> c1 eq c2
res1: Boolean = false
Case class
3) You can create a new instance without using new keyword thanks to apply method generated in a companion object.
object Client { // this is an equivalent of what the compiler generates
def apply(name: String, age: Int): Client = new Client(name, age)
(...)
}
scala> case class Client(name: String, age: Int)
defined class Client
scala> Client("Jan Kowalski", 27)
res1: Client = Client(Jan Kowalski,27)
Case class
In fact apply is just syntactic sugar.
You can create your own factory methods and override them.
object ParseInt {
def apply(str: String): Integer = Integer.parseInt(str)
def apply(chars: Array[Char]): Integer = Integer.parseInt(new String(chars))
}
scala> ParseInt("500")
res0: Integer = 500
scala> ParseInt.apply("500")
res1: Integer = 500
scala> ParseInt(Array('2', '0'))
res2: Integer = 20
Case class
4) You can use copy method to create a shallow clone of an instance.
Optionally you can change the values of params.
scala> case class Client(name: String, age: Int)
defined class Client
scala> val c1 = Client("Jan Kowalski", 27)
c1: Client = Client(Jan Kowalski,27)
scala> val c2 = c1.copy()
c2: Client = Client(Jan Kowalski,27)
scala> c1 eq c2
res0: Boolean = false
scala> c1.copy(age = 28)
res1: Client = Client(Jan Kowalski,28)
Case class
5) You can extract the values of parameters thanks to unapply method.
(Pattern matching will be discussed in detail later)
scala> case class Client(name: String, age: Int)
defined class Client
scala> val Client(name, age) = Client("Jan Kowalski", 27)
name: String = Jan Kowalski
age: Int = 27
Case class
Exercise!
ApplySandbox.scala
in com.virtuslab.scalaworkshop.cc package
Just get familiar with case classes and the factory methods.
In sbt shell, run:
testOnly *ApplySandboxSpec
Case class
Can't we just make all classes case classes?
- sometimes you don't want to make class params public
(can be solved by using private val explicitly)
- case classes can't inherit from other case classes
- there's much code generated so the bytecode size would be much bigger
Basic constructs
Loops
Loops
Scala supports similar types of loops as Java
1. while
2. do while
3. for loop
But provides also the additional features
4. for expression (for comprehension)
Loops
while loop
scala> var i = 0
i: Int = 0
scala> while (i < 3) { i += 1; println(i) }
1
2
3
Loops
do while loop
scala> var i = 0
i: Int = 0
scala> do { i+= 1; println(i) } while (i < 3)
1
2
3
Loops
for loop
In Scala there's no equivalent of a standard for loop.
Only the version like enhanced for for collections and arrays in Java
scala> val cities = List("London", "Paris", "Madrid", "Kielce")
cities: List[String] = List(London, Paris, Madrid, Kielce)
scala> for (city <- cities) println(city)
London
Paris
Madrid
Kielce
There's no value returned (Unit type).
Loops
for loop
You can specify additional filters using if (called guard).
scala> for (i <- 1 to 10 if i % 2 == 0 && i != 6) println(i)
2
4
8
10
Loops
for expression (for comprehension)
You can use keyword yield to return the collection of computed values.
scala> for (i <- 1 to 10 if i % 2 == 0 && i != 6) yield i
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 8, 10)
scala> for (i <- Set(1, 2, 3, 4) if i % 2 == 0) yield i
res1: scala.collection.immutable.Set[Int] = Set(2, 4)
Note the type of the enumerator determines what will be a type of a result.
Loops
for expression (for comprehension)
You can use more than one enumerator / generator in one for.
They can be separated using semicolons.
for (i <- 1 to 6 if i % 2 == 0; j <- i to i + 2) yield (i, j)
But it's nicer when using curly braces without semicolons, isn't it?
for {
i <- 1 to 6 if i % 2 == 0
j <- i to i + 2
} yield (i, j)
Loops
for expression (for comprehension)
Btw, what would be a result of?
for {
i <- List(1, 2, 3, 4, 5, 6) if i % 2 == 0
j <- i to i + 2
} yield (i, j)
Loops
for expression (for comprehension)
The result is:
scala> for {
| i <- List(1, 2, 3, 4, 5, 6) if i % 2 == 0
| j <- i to i + 2
| } yield (i, j)
res0: List[(Int, Int)] =
List((2,2), (2,3), (2,4), (4,4), (4,5), (4,6), (6,6), (6,7), (6,8))
Loops
for expression (for comprehension)
You can also define local variables directly in for block.
scala> for {
| person <- people
| address = getAddress(person) if address.country == "Poland"
| } yield s"${person.name} is from ${address.city}"
res0: List[String] =
List(Jan Kowalski is from Kielce, Piotr Nowak is from Krakow)
Loops
Exercise!
ForComprehensionSandbox.scala
in com.virtuslab.scalaworkshop.loops package
Use a for comprehension to solve the specified problems.
In sbt shell, run:
testOnly *ForComprehensionSandboxSpec
Loops
In practice you'll also often use the built-in higher order functions (functions that take other functions as parameters) on collections which iterate elaments.
Collections will be discussed in detail later.
Loops
higher-order functions – map
scala> val peanutsInPackages = Seq(500, 1000, 2000)
def prepareSpecialOffer(weight: Int) = weight * 1.2
val newWeights = peanutsInPackages.map(prepareSpecialOffer)
res0: newWeights: Seq[Int] = List(600, 1200, 2400)
This can be shortened by using lambdas
val peanutsInPackages = Seq(500, 1000, 2000)
val newWeights = peanutsInPackages.map((weight: Int) => weight * 1.2)
You can pass a function which will convert elements
Loops
higher-order functions – map
val peanutsInPackages = Seq(500, 1000, 2000)
val newWeights = peanutsInPackages.map(_ * 1.2)
Or shortened even further by using a wildcard
Loops
higher-order functions – foreach
scala> val peanutsInPackages = Seq(500, 1000, 2000)
peanutsInPackages.foreach(weight => println(weight * 1.2))
600.0
1200.0
2400.0
foreach is similar to map but there are no values returned
Loops
higher-order functions – flatMap
scala> List(1, 10, 20).flatMap(i => List(i, i + 1, i + 2))
res0: List[Int] = List(1, 2, 3, 10, 11, 12, 20, 21, 22)
It's like map but for each element we can possibly produce many values (or even no value!) and eventually we put them all in one collection
Loops
higher-order functions – flatMap
val allNumbers = customers.flatMap(customer => getPhoneNumbers(customer))
More practical example
Now we can call all these numbers and offer some pots!
Loops
higher-order functions – filter
scala> (1 to 10).filter(i => i % 2 == 0)
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
You can pass the predicate to get a subset of elements as a result
Loops
Why did we mention these methods right now?
for loops are translated to map, flatMap, foreach and withFilter (a bit different filter)
Select a for loop and press Ctrl-Alt-D (Linux) in your IntelliJ to desugar Scala code (unless you're on Ubuntu and it show your desktop instead...).
Loops
How for loop is translated
for (x <- c1) {...}
c1.foreach(x => {...})
Normal for without yield is translated into foreach methods (and possibly also withFilter)
Loops
How for loop is translated
for {
x <- c1
y <- c2
z <- c3
} (...)
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Which possibly can be nested
Loops
How for expression is translated
for (x <- c if condition(x)) yield {...}
c.withFilter(x => condition(x)).map(x => {...})
if guard turns into withFilter
Loops
How for expression is translated
for {
a <- 1 to 5
b <- 1 to a
} yield (a, b)
(1 to 5).flatMap(a => (1 to a).map(b => (a, b)))
For expression is translated into flatMaps, maps and withFilter (potentially also nested)
Loops
Exercise!
ForLoopTranslationSandbox.scala
in com.virtuslab.scalaworkshop.loops package
Let's try to use methods to reproduce the for loops' behaviour.
In sbt shell, run:
testOnly *ForLoopTranslationSandboxSpec
Loops
The usage is not limited to collections.
Each type implementing map, flatMap, foreach and filter/withFilter can be used in for comprehension.
Traits
Trait
- On JVM there's only single inheritance.
- In Java one can implement many interfaces. Currently also having default implementations.
Trait
- In Scala there are classes and traits.
- It's still possible to extends only one class.
- But you can mix-in many traits.
- Traits are more than interfaces because they can contain also state (not only final static fields like interfaces in Java).
- This allows to work around single inheritance limitation.
Trait
The fully abstract traits are turned into standard interfaces
trait Logger {
val prefix: String
def logInfo(msg: String)
}
Let's check using javap
Trait
The traits with implementation has additional methods generated
trait StdOutLogger {
val prefix: String = "stdout: "
def logInfo(msg: String) = println(s"$prefix $msg")
}
Let's check using javap
Trait
When inheriting, a class must be specified first.
We always use extends at the beginning and with for another traits.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
class Animal
class Duck extends Animal with HasLogger with Comparable {
(...)
}
Trait
We always use extends at the beginning and with for another traits.
Even if you inherit only traits.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
class Animal extends HasLogger with Comparable {
(...)
}
Trait
We always use extends at the beginning and with for another traits.
Also when you define a trait.
trait HasLogger {
protected val logger: Logger
}
trait Comparable {
def compare[T](other: T): Int
}
trait Animal extends HasLogger with Comparable {
(...)
}
Trait
Which implementation will be used when a member is overridden in many places?
The rules are different than in Java.
Scala uses so-called trait linearization.
Trait
The most specific version is used when there's linear hierarchy
trait MakingSound {
def getSound: String = "beep"
}
trait Dog extends MakingSound {
override def getSound = "hau"
}
class SheepDog extends Dog with MakingSound
class Bulldog extends MakingSound with Dog
println(new SheepDog().getSound)
hau
println(new Bulldog().getSound)
hau
Trait
Scala (unlike Java) is able to choose when there's diamond inheritance
trait MakingSound {
def getSound: String = "beep"
}
trait Dog extends MakingSound {
override def getSound = "hau"
}
trait UltraFast extends MakingSound {
override def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast
println(new MysteriousCreature().getSound)
wzium...
Trait
But (like in Java) no when there are unrelated types with the same member
trait Dog {
def getSound = "hau"
}
trait UltraFast {
def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast
Error:(48, 7) class MysteriousCreature inherits conflicting members:
method getSound in trait Dog of type => String and
method getSound in trait UltraFast of type => String
(Note: this can be resolved by declaring an override in class MysteriousCreature.)
class MysteriousCreature extends Dog with UltraFast
Trait
For unrelated types it's needed to choose explicitly
trait Dog {
def getSound = "hau"
}
trait UltraFast {
def getSound = "wzium..."
}
class MysteriousCreature extends Dog with UltraFast {
override def getSound: String = super[Dog].getSound
}
println(new MysteriousCreature().getSound)
// hau
Trait
Sealed traits
- All subtypes must be defined in the same file so there's a finite number of those.
- More restrictive than for sealed classes.
- Compiler can perform special checks. We'll show this later.
- Using with objects can be an equivalent of enums.
sealed trait Answer
case object Unsure extends Answer
case object Yes extends Answer
case object No extends Answer
Trait
You can inherit traits at the instantiation time
trait HasLogger {
protected val logger: Logger = new SplunkLogger()
}
class Person extends HasLogger {
(...)
}
trait HasTestLogger extends HasLogger {
override protected val logger: Logger = new StdOutLogger()
}
new Person() with HasTestLogger
Pattern matching
Pattern matching
In math we often meet definitions like
f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2)
It's possible to express this using if-else blocks but we can do better.
Pattern matching
In Java 12 you can write such code when using the preview version of a switch expression
int fib(int n) { return switch (n) { case 0 -> 0; case 1, 2 -> 1; default -> fib(n - 1) + fib(n - 2); }; }
What's different from the standard switch?
Pattern matching
In Scala you can use match keyword to match pattern
def fib(n: Int): Int = n match { case 0 => 0 case 1 | 2 => 1 case _ => fib(n - 1) + fib(n - 2) }
Pattern matching
It can be generalised as
expression match {
case pattern1 [if predicate1] => expression1
case pattern2 [if predicate2] => expression2
(...)
}
Pattern matching
In java you can match numbers or Strings.
In Scala there are many types of patterns and they can be very complex.
Let's check some of them.
Pattern matching
When the expression doesn't match any case, MatchError is thrown.
To avoid this, usually one uses wildcard pattern matching everything
expression match {
case <some_pattern> => <some_result>
case _ => <default_value>
}
Pattern matching
Matching types using type annotations
something match {
case duck: Duck => duck.quack()
case dog: Dog => dog.bark()
case _: Animal => // do nothing
}
Pattern matching
Matching types using type annotations
When using sealed traits, compiler will warn you when you omit case.
sealed trait DecisionStatus
class Choice(val number: Int) extends DecisionStatus
object Resigned extends DecisionStatus
object NeedsMoreTime extends DecisionStatus
val d: DecisionStatus = (...)
d match {
case c: Choice => println(s"You've chosen ${c.number}")
case Resigned => println("You resigned")
}
// Warning:(21, 3) match may not be exhaustive.
// It would fail on the following input: NeedsMoreTime
// d match {
Pattern matching
Matching types using type annotations - pitfalls
Remember there's type erasure on JVM so you can't rely on the generic type parameter. The compiler will generate a warning.
List(1, 2, 3) match {
case strings: List[String] => strings.foreach(println)
case _ =>
}
1
2
3
// Warning:(38, 17) fruitless type test: a value of type List[Int]
// cannot also be a List[String] (the underlying of List[String])
// (but still might match its erasure)
// case strings: List[String] => strings.foreach(println)
Pattern matching
Additional conditions (guards)
employee match {
case p: Programmer if p.isOnSupport => p.call(incidentDescription)
case p: Programmer => p.sendEmail(incidentDescription)
case _ => // do nothing
}
Pattern matching
Matching structure – case classes
case class Player(nick: String, credits: Int)
player match {
case Player(name, 0) =>
println(s"$name, maybe do you want to buy some credits?")
case Player(_, credits) if credits > 1000 =>
println("We have a special offer for you. Wanna spend credits on lootbox?")
case _ =>
}
Pattern matching
Matching structure – tuples
(competition1Score, competition2Score) match {
case (score1, score2) if score1 > 90 || score2 > 90 => Qualification
case _ => Rejection
}
Pattern matching
Matching structure – sequences
val list: List[Int] = (...)
list match {
case List(1, 1, 1) => "3 x 1"
case _ :: Nil => "Only one element"
case head :: tail => s"First element is $head; others are $tail"
case Nil => "No elements"
}
Pattern matching
Bindings
You can match the structure as previously and still be able to use base instance.
case class Person(name: String, age: Int)
person match {
case p @ Person(_, age) <- people if age >= 18 => checkIdCard(p)
case _ => checkStudentCard(person)
}
Pattern matching
Matching values of stable indentifiers
You can check whether a value is the same as value of local variable or constant defined in object.
Use backticks to really match value instead of defining an identifier.
object TimeUtils {
val UtcTimeZone = "UTC"
}
val cetTimeZone = "CET"
timeZone match {
case `cetTimeZone` => "Are you in Poland?"
case TimeUtils.UtcTimeZone => s"You're in $timeZone"
case _ => s"$timeZone is not CET and UTC"
}
Pattern matching
Regular expressions
val pattern = "([0-9]+) ([A-Za-z]+)".r
"10 kg" match {
case pattern(number, unit) => (...)
}
Pattern matching
Beyond match block
Pattern matching is not only match expression. You'll find it in other places too.
Pattern matching
Beyond match block
Extractors in vals
scala> case class Person(name: String, age: Int)
val Person(n, a) = Person("Jan", 27)
n: String = Jan
a: Int = 27
Pattern matching
Beyond match block
Extractors in generators / enumerators in for comprehension
case class Person(name: String, age: Int)
for (p @ Person(_, age) <- people if age >= 18) yield p
Pattern matching
Beyond match block
Catching exceptions
try {
// some unsafe operations
} catch {
case e: IOException => (...)
case e: Exception => (...)
}
Pattern matching
Partial function
Function which has values only for a part of possible values passed as params.
E.g. div(a: Double, b: Double) could be a partial functions with results for all values except b == 0.
val div = new PartialFunction[(Double, Double), Double] {
def apply(numbers: (Double, Double)): Double = numbers match {
case (a, b) => a / b
}
def isDefinedAt(numbers: (Double, Double)): Boolean = numbers._2 != 0
}
Pattern matching
Partial function
Usually we don't need to think that something is partial function.
We just create them using blocks with case patterns.
val div: PartialFunction[(Double, Double), Double] = {
case (a, b) if b != 0 => a / b
}
println(div.isDefinedAt((1, 1)))
// true
println(div.isDefinedAt((1, 0)))
// false
Pattern matching
Partial function – usage example
Partial functions can be used e.g. when operating on collections.
E.g. collect method is similar to map but returns values only for elements for which function isDefinedAt.
scala> List((1.0, 1.0), (1.0, 0.0), (5.0, 2.0)).collect {
| case (a, b) if b != 0 => a / b
| }
res0: List[Double] = List(1.0, 2.5)
Exercise!
PatternMatchingSandbox.scala
in com.virtuslab.scalaworkshop.patmat package
Let's familiarise with various usages!
In sbt shell, run:
testOnly *PatternMatchingSandboxSpec
Pattern matching
Collections
Mutable vs Immutable
Characteristics
Few more constructs
Apply
Tuples
Higher order function
Currying
Code samples
CollectionSpec
Exercises
Collections Exercises
CollectionSpec2
FP Basics
Optional values
Option
It's an abstract class with two implementations:
Some(x): case class, wraps a value
None: case object, expresses lack of a value
In some ways similar to Java's Optional...
except that Optional is a final class instead.
Option
public final class Optional<T> {
...
private final T value;
...
public T get() {
if (this.value == null) {
throw new NoSuchElementException(...);
} else {
return this.value;
}
}
}
// disclaimer: headers are simplified
abstract class Option[A] {
...
def get: A
...
}
final case class Some[A](value: A) extends Option[A] {
...
def get: A = value
}
case object None extends Option[Nothing] {
...
def get = throw new NoSuchElementException("None.get")
}
How is it different than Optional?
Okay, how do I make one?
Option
val foo: Option[String] = Some("hello")
val bar: Option[String] = None
// Or to be less explicit, but extra null-safe:
val qux: Option[String] = Option(someStringValue)
// This will resolve to None if someStringValue is null,
// and Some(...) otherwise
Don't use .get...
Option
val a: String = foo.get // ok...
val b: String = bar.get // throws NoSuchElementException!
Don't use .get...
...unless you really know what you're doing!
Calling .get on None ends up in NoSuchElementException - just like with Java's Optional :/
Better do .getOrElse(x) - as with Optional, or...
Option
...use match instead!
Option
myOption match {
case Some(x) => ...
case None => ...
}
Or even better: map/flatMap
Think of Option as 0- or 1-element collection!
Option
val bar: Option[String] = None
val foo: Option[String] = Some("Hello")
bar.map(x => x + "World") // --> still None!
foo.map(x => x + "World") // --> Some("HelloWorld")
bar.flatMap(x => None) // --> still None...
foo.flatMap(x => None) // --> also None!
bar.flatMap(x => Some(x + "World")) // --> still None
foo.flatMap(x => Some(x + "World")) // Some("HelloWorld")
Option
case class Data(someImportantPart: String, /* more members */ ... )
def fetchData(input: String): Option[Data] = ...
for {
data <- fetchData(input)
} yield data.someImportantPart
// This will desugar into:
fetchData(input).map { data =>
data.someImportantPart
}
Also: use in for comprehensions
Option
def merge(d1: Data, d2: Data): Data = ...
for {
data <- fetchData(input)
anotherData <- fetchData(anotherInput)
} yield merge(data, anotherData)
// This will desugar into:
fetchData(input).flatMap { data =>
fetchData(anotherInput).map { anotherData =>
merge(data, anotherData)
}
}
Also: use in for comprehensions
Option
What happens with a None in for?
You can think of for as a fail-fast flow over Options... continuing through Somes but immediately stopping on first None!
If right sides of all arrows are Some... the final result is Some
If any right side of any arrow is None... the final result is None
Option
Exercise!
OptionSandbox.scala:
Express a flow first using flatMap/map over Options, then write an equivalent for comprehension.
In sbt shell, run:
testOnly *OptionSandboxSpec
Alternative (Either)
It's an abstract class with two implementations:
Left(x): case class, typically wraps an error
Right(x): case class, typically wraps a correct value
No equivalent in Java standard library
Javaslang/Vavr has a similar concept, though
Either
Either
// disclaimer: class headers are simplified
abstract class Either[A, B] {
...
}
final case class Left[A, B](value: A) extends Either[A, B] {
...
}
final case class Right[A, B](value: B) extends Either[A, B] {
...
}
Okay, how do I make one?
Either
val foo: Either[Exception, String] = Right("Hello")
val bar: Either[Exception, String] = Left(new Exception("Something wrong"))
Either
.right.get, .left.get - well...
val w: String = foo.right.get // ok, will be "Hello"
val x: Exception = foo.left.get // compiles, but throws NoSuchElementException!
val y: Exception = bar.left.get // ok, will be Exception("Something wrong")
val z: String = bar.right.get // compiles, but throws NoSuchElementException!
Either
.right.get, .left.get - well...
Similar concern as with .get on Option in case of None...
If called on wrong type, ends up in a NoSuchElementException
It's not a type-safe way of dealing with things :/
Either
Again, match...
myEither match {
case Left(l) => ...
case Right(r) => ...
}
Either
map/flatMap - right-biased!
val foo: Either[Exception, String] = Right("Hello")
val bar: Either[Exception, String] = Left(new Exception("Something wrong"))
foo.map((x: String) => x + "World") // --> Right("HelloWorld")
bar.map((x: String) => x + "World") // --> still Left(Exception("Something wrong"))
foo.flatMap((x: String) => Left(new OtherException(""))) // --> Left(OtherException(""))
bar.flatMap((x: String) => Left(new OtherException(""))) // --> still Left(Exception("Something wrong"))
foo.flatMap((x: String) => Right(x + "World")) // --> Right("HelloWorld")
bar.flatMap((x: String) => Right(x + "World")) // --> still Left(Exception("Something wrong"))
Either
case class Tree(root: Node, /* more members */ ... )
def parseInput(data: Data): Either[Exception, Tree] = ...
for {
tree <- parseInput(data)
} yield tree.root
// This will desugar into something like:
parseInput(data).map { tree =>
tree.root
}
Use in for comprehensions
def computeResult(tree: Tree): Double = ...
for {
tree <- parseInput(data)
result <- computeResult(tree)
} yield result
// This will desugar into something like:
parseInput(data).flatMap { tree =>
computeResult(tree).map { result =>
result
}
}
Either
Use in for comprehensions
Either
What happens with a Left in for?
You can think of for as a fail-fast flow over Eithers... continuing through Rights but immediately stopping on first Left!
If right sides of all arrows are Right... the final result is Right
If any right side of any arrow is Left... the final result is Left (*)
(*) storing the first encountered Left value
Either
Option&Either in the same for?
Well, can it be done?... let's give it a shot in an exercise!
Either
Exercise!
EitherSandbox.scala:
Try using Option and then Either in the same for comprehension... rewrite to flatMap/map to see more clearly why it couldn't work!
In sbt shell, run:
compile
Either
Exercise!
EitherSandbox.scala:
Use Option's toRight method to work the problem around.
In sbt shell, run:
testOnly *EitherSandboxSpec
Dealing with failure (Try)
It's an abstract class with two implementations:
Success(x): case class, wraps a successful result
Failure(e): case class, wraps a Throwable
Try
Try
// disclaimer: headers are simplified
abstract class Try[T] {
...
def get: T
...
}
final case class Failure[T](exception: Throwable) extends Try[T] {
...
override def get: T = throw exception
...
}
final case class Success[T](value: T) extends Try[T] {
...
override def get: T = value
...
}
val foo: Try[Int] = Try {
...
someJavaLibraryMethodThrowingExceptions()
...
}
// `foo` is an instance of Failure[Int] if the above code threw an exception,
// or an instance of Success[Int] if no exception has been thrown.
Try
Try block (Try.apply)
for {
a <- Try(potentiallyThrowingMethod())
b <- Try(otherPotentiallyThrowingMethod())
} yield ()
Try
Use in for comprehensions
Not much surprise at this point...
Try
What happens with a Failure in for?
You can think of for as a fail-fast flow over Tries... continuing through Successes but immediately stopping on first Failure!
If right sides of all arrows are Success... the final result is Success
If any right side of any arrow is Failure... the final result is Failure (*)
(*) storing the first encountered exception
Avoid throwing exceptions in functional code, even if they're meant to end up wrapped in Try.
Better resort to Options, Eithers and other similar constructs!
Try
Exceptions in FP
Exceptions should typically be reserved for fatal failures (VirtualMachineError, ThreadDeath, InterruptedException, LinkageError etc.)
Business errors should be wrapped into Either/Try... to make reasoning about the execution flow easier
Try
Exceptions in FP
TrySandbox.scala:
Some piece of code that throws exceptions... let's represent the result as a Try!
In sbt shell, run:
Try
Exercise!
testOnly *TrySandboxSpec
Bonus
Recursion
In practice, you won't come across recursion very often in production code.
Usually higher-order functions and for-comprehensions serve for the purposes traditionally covered by imperative loops in Java.
Recursion
Production use...
Compiler will raise an error if the method is not tail-recursive or can be overridden.
@tailrec private def fib(n: Int): Int = {
if (n == 0) 1
else fib(n - 1) + fib(n - 2) // this won't compile!
}
Recursion
@tailrec
Custom extractors
How is it actually possible that we can use the word Company in the extractor?
Is there some case-class-specific magic going on?
case class Company(name: String, taxId: Int)
company match {
case Company(name: String, taxId: Int) => ...
}
Custom extractors
No! It's just a matter of the special unapply method generated for case classes.
Let's emulate the similar effect without making Company a case class...
class Company(val name: String, val taxId: Int)
object Company {
def unapply(company: Company): Option[(String, Int)] = {
Some((company.name, company.taxId))
}
}
myCompany match {
case Company(name: String, taxId: Int) => ...
}
Custom extractors
What we've just done is that we created the instruction on how to reduce a Company to a tuple (or, unapply it)! This is a very powerful mechanism that can be used on pre-existing types like String as well...
object Email {
def unapply(str: String): Option[(String, String)] = {
val parts = str.split("@")
if (parts.length == 2) Some(parts(0), parts(1))
else None
}
}
"pawel@lipski.com" match {
case Email(user: String, domain: String) =>
// user will be "pawel", domain will be "lipski.com"
}
Custom extractors
What do we really use?
Loops
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep 'do {' | wc -l
0
$ git grep 'while (' | wc -l
4
$ git grep 'for [{(]' | wc -l
463
Pattern matching
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep ' match {' | wc -l
185
$ git grep 'case.*=>' | wc -l
954
Case vs regular classes/objects
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w 'case class' | wc -l
772
$ git grep -w '^class' | wc -l
586
$ git grep -w 'case object' | wc -l
16
$ git grep -w '^object' | wc -l
427
(Sealed) traits
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w trait | wc -l
368
$ git grep -w 'sealed trait' | wc -l
23
flatMap/map/for
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w flatMap | wc -l
220
$ git grep -w map | wc -l
1181
$ git grep -w 'for [{(]' | wc -l
463
Option/Either/Try
$ git ls-files '*.scala' | xargs wc -l
....
95111 total
$ git grep -w 'Option\(T\?\)' | wc -l
1555
$ git grep -w 'Either\(T\?\)' | wc -l
665
$ git grep -w Try | wc -l
33
Thank you
Contact - Paweł Dolega
Software Engineer /
Entrepreneur
twitter: @pdolega
github: github.com/pdolega
mail: plipski@virtuslab.com
github: github.com/PawelLipski
Contact - Paweł Lipski
mail: mpociecha@virtuslab.com
github: github.com/mpociecha
Contact - Michał Pociecha
Software Engineer (mostly Scala)
Scala for Java developers
By Pawel Dolega
Scala for Java developers
- 2,098