For Java developers
Joost den Boer
Contractor, Senior developer, Scala enthusiast
@diversit
www.diversit.eu
THIS IS NOT A SCALA COURSE !!
val height = 1.9d
val name = "James"
println(f"$name%s is $height%2.2f meters tall") // James is 1.90
val name = "Piet"
s"Hello $name"
s"Sum is ${3 + 3}"
s"User name: ${user.name}"
"a\nb"
raw"a\nb"
"""a\nb"""
Extendable: create your own.
E.g. json"{ name:Piet }"
Only compatible with Java when Trait only contains method definitions.
(might change with Java 8's default methods)
trait SomeService {
val value = 1
abstract def getValue: Int
def toString = s"Value is ${value}"
}
class MySomeService extends SomeService
val mySomeService = new SomeService {}
trait Logging
trait SomeService extends Logging
trait OtherService extends Logging
class MySomeService extends SomeService with OtherService
val person = new Employee extends Management with HRM with CFO
trait UserRepository {
def find(id: Int): User
}
trait UserService {
self: UserRepository =>
def findUser(id: Int) = find(id)
}
// Create XML
val phoneBook =
<phonebook>
<descr>This is the <b>phonebook</b></descr>
<entry>
<name>Burak</name>
<phone where="work">+41 21 693 68 67</phone>
<phone where="mobile">+41 79 602 23 23</phone>
</entry>
</phonebook>
// Manupilate XML
val updatedPhonebook = phonebook match {
case <phonebook>{ ch @ _* }</phonebook> =>
<phonebook>{ ch }<entry> ... </entry></phonebook>
}
// XPath-like
phonebook \\ "phone" map (_.text)
// returns List[String]
Option's done right
No more 'null's and NPE's !
sealed abstract class Option[T]
final case class Some[T] extends Option[T]
object None extends Option[Nothing]
val i = Some(1) // Option[Int]
val user = Some(user) // Option[User]
val none: Option[User] = None // Option[User]
var noneWrong = None // None.type
noneWrong = Some(1) // Error: type mismatch;
// found: Some[Int], required None.type
User u = someMethodReturningNUll();
u.getName(); // --> NPE
// Alternative
if (u != null) u.getName();
// Or
try {
u.getName();
} catch (NullPointerException e) { .. }
In Java:
In Scala:
val user: Option[User] = someMethodReturningAUserOption()
u.map(_.name) // Option[String]
u.map(_.name).filter(_ contains "Piet") // Option[String]
u.map(_.name).filter(_ contains "Piet").getOrElse("Niet Piet") // String
u.map(_.name).filter(_ contains "Piet").orElse(Some("Niet Piet")) // Option[String]
For comprehension
for {
user <- someMethodReturningAUserOption()
name <- Some(user.name) if (user.name == "Piet")
} yield name // Option[String]
Pattern matching
someMethodReturningAUserOption match {
case Some(user) if (user.name == "Piet") => ..
case Some(User("Piet")) => .. // werkt ook
case Some(user) => ..
case None => ..
case _ => .. // catches all others possibilities
}
Other box types:
Try { u.name } // Try[String]: Success(name) or Failure(exception)
Alternative for Option for dealing with missing values
Left = ok, Right = wrong
Can be used to gather multiple validation errors (Right: List[String])
In Java:
List<User> people = ...
List<String> adults = new ArrayList<String>();
for(User user : people) {
if(user.getAge() > 18) {
adults.add(user.getName());
}
}
In Scala:
List[User] people = ...
val adults = people.filter(_.age > 18).map(_.name)
List<User> people = ...
List<String> adults = people.stream()
.filter(p -> p.getAge() > 18)
.map(User::getName())
.collect(Collectors.toList());
In Java8:
map, flatMap, filter, filterNot, find, forall, foreach, fold, mkString, drop, take, head, last, init, tail, exists, contains,
partition, slice, zip, reduce, reverse, sliding,
sum, max, min, product,
union, diff, intersect, ...
Some other functions:
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
public Integer getAge() { return age; }
public void setAge(Integer age) {
this.age = age;
}
public boolean equals(Object that) { ... }
public int hashCode() { ... }
public String toString() { ... }
}
Java:
Scala:
case class User(name: String, age: Int)
This includes:
Collection type matching
val aList = List(1,2,3)
// Example matching Lists
aList match {
case 1 :: twee :: rest => println(twee)
case head :: tail => println(tail)
case Nil => println("Nothing")
}
Case class matching
case class User(name: String, age: Int)
val piet = User("Piet", 80)
piet match {
case u @ User(name, _) => ..
case _ => ..
}
val userList = List(User("Piet", 80), User("Ruud", 54), User("Kees", 23))
userList map {
case User(name, age) if age > 18 => name
case _ => "too young"
}
val names = userList map {
case User(name, age) if age > 18 => Some(name)
case _ => None
} // List[Option[String]]
names.flatten // List[String] without the None's
Matching Tuples
val t = (1, "abc", List(1,2,3))
val (i, s, l2) = t // i=1, s="abc", l2=List(1,2,3)
t match {
case (_: Int, _, _) => println("Int")
}
Regex matching
val regex = """^INFO: (.*)$""".r
val regex(msg1) = "INFO: testing" // msg1 = "testing"
val msg2 = "INFO: testing" match {
case regex(msg) => msg
} // msg2 = "testing"
Introducted by Moses Schönfinkel.
Later developed by Haskell Curry.
"currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application)"
// uncurried
def sum(x: Int, y: Int): Int = x + y
sum(1,4) // 5
// curried
def sum: Int => Int => Int = x => y => x + y
val addOne = sum(1) // Int => Int = <function1>
addOne(2) // 3
sum(1)(4)
To curry and uncurry
// To curry
def sum1(x: Int, y: Int) = x + y // sum1: (x: Int, y: Int)Int -> NOT a Function!
val fSum1 = sum1 _ // (Int, Int) => Int = <function2> -> Function taking 2 args
val cSum1 = fSum1.curried // res48: Int => (Int => Int) = <function1>
Function.uncurried(cSum1) // (Int, Int) => Int = <function2>
Partial functions: PartialFunction[A-, B+]
// Actor's receive method
def receive: Receive = {
case SomeMessage(content) => // do something with content
}
'case' matching to create partial function
val list = List(1,2,3,4,5,6)
val result = list.collect { case x if x > 3 => x }
// What is result?
result == List(4,5,6)
Java implicits:
To automatically determine or refer to a value
Scala implicits:
Implicits are looked up in specific order: Scopes
implicit val one: Int = 10
def addOne(x: Int)(implicit y: Int) = x + y // (x: Int)(implicit y: Int)Int
addOne(3) // 4
Bad example!
Implicits look at types, not at value names.
So, use types when using implicit values.
type One = Int // wrong: will still match all implicit Int's
type IntCheck = Int => Boolean // already more specific
implicit val biggerThanTwo: IntCheck = x => x > 2
def check(x: Int)(implicit checker:IntCheck) = checker(x)
class Adder(value: Int)
implicit val one = Adder(1)
def addOne(x: Int)(implicit y:Adder) = x + y
Implicit conversions: Very powerfull !!
Also here: use Types
class Adder(val x: Int)
implicit def adderToString(a: Adder) = s"Adder value: ${a.x}"
def someMethod(s: String) = println(s)
someMethod(new Adder(10)) // implicit conversion to String!
Extend existing classes with new functionality!
(like Caterogies in Objective C)
implicit class IsTodayDate(val date: java.util.Date) extends AnyVal {
def isToday: Boolean = new Date().equals(date)
}
new java.util.Date().isToday
Implicit Value classes are NOT instanciated!
Convert between Java <-> Scala types:
Best practive: Use JavaConverters, not JavaConversions !
import scala.collection.JavaConverters._
val sl = new scala.collection.mutable.ListBuffer[Int]
val jl : java.util.List[Int] = sl.asJava
val sl2 : scala.collection.mutable.Buffer[Int] = jl.asScala
assert(sl eq sl2)
Lamda expression: a function defined, and possible called, without being bound to an identifier.
Closure: a function which accesses non-local variables
// Assume Person class with name and age.
List<Person> persons = new ArrayList() {{
add(new Person("Piet", 40));
add(new Person("Kees", 18));
add(new Person("Eva", 18));
}};
Map<Integer, List<String>> personsByAge = persons.stream()
.filter(p -> p.age > 16)
.collect(
Collectors.groupingBy(
p -> p.getAge(),
Collectors.mapping(p -> p.getName(), Collectors.toList())
)
);
System.out.println(personsByAge);
// Assume Person class with name and age.
List<Person> persons = new ArrayList() {{
add(new Person("Piet", 40));
add(new Person("Kees", 18));
add(new Person("Eva", 18));
}};
Map<Integer, List<String>> personsByAge = persons.stream()
.filter(personsOver16())
.collect(
Collectors.groupingBy(
getPersonAge(),
personNamesList()
)
);
System.out.println(personsByAge);
static Predicate<Person> personsOver16() {
return p -> p.getAge() > 16;
}
static Function<Person, Integer> getPersonAge() {
return p -> p.getAge();
}
static Function<Person, String> getPersonName() {
return p -> p.getName();
}
static Collector<? super Person, ?, List<String>> personNamesList() {
return Collectors.mapping(getPersonName(), Collectors.toList());
}
// Scala oplossing
case class Person(val name: String, val age: Int)
val persons = List(Person("Piet", 40), Person("Kees", 18), Person("Eva", 18))
def personsNameList: List[Person] => List[String] = ps => ps.map(_.name)
val personsByAge = persons.groupBy(_.age).mapValues(personsNameList)
println(personsByAge)
How to do this in Java?
def sum: Int => Int => Int = x => y => x + y
sum(1)(3) // 4
static Function<Integer,Function<Integer, Integer>> fSum() {
return x -> y -> x + y;
}
fSum().apply(1).apply(2);
Macros
Reflection
Monads, Monoids, Functors, Applicatives
Advanced Generics
Covariant, Contravarant, Invariant
Covariant: convert from wider to narrower type.
Contravariant: convert from type to wider type.
Invariant: Not able to convert.
IntelliJ, Eclipse
SBT, Maven Plugin
Download from Typesafe, Homebrew (Mac)
Mixing with Java: Scala compile first (default)
Testing excelent way to get started!
?