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​

Software Engineer (Scala)

also (aspiring) git hacker...

github.com/Virtuslab/git-machete

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

  • 1,904