6 years ago I wanted to learn a new statically typed language
Scala combines object-oriented and functional programming in one concise, high-level language. Scala's static types help avoid bugs in complex applications [2]
import static java.lang.String.format;
String hello = "hi";
var foo = "bar";
System.out.println(
format("%s, %s", hello, foo));
val hello: String = "hi"
val foo = "bar"
println(s"$hello, $foo")
val hello: String = "hi"
val foo = "bar"
println("$hello, $foo")
Variables and String Interpolation
public class SomeService {
private final String status;
public SomeService(String stauts) {
this.status = stauts;
}
public void printStatus() {
System.out.println(status);
}
}
var s = new SomeService("OK");
s.printStatus();
class SomeService(
val status: String) {
def printStatus(): Unit = {
println(status)
}
}
val s = new SomeService("BETTER")
s.printStatus()
class SomeService(
val status: String) {
fun printStatus(): Unit {
println(status)
}
}
val s = SomeService("GREAT")
s.printStatus
Classes, Constructors, and Methods
double powerOf(double n, double p) {
return Math.pow(n, p);
}
double powerOf(double n) {
powerOf(n, 2.0);
}
powerOf(2, 2);
powerOf(2);
def powerOf(n: Double,
p: Double = 2): Double = {
Math.pow(n, p)
}
powerOf(2, 2)
powerOf(2)
fun powerOf(n: Double,
p: Double = 2.0): Double {
return n.pow(p)
}
powerOf(2, 2)
powerOf(2)
Default arguments
@Data
@AllArgsConstructor
class Service {
public String name;
}
Service service = new Service("GK");
service.getName();
case class Service(name: String)
val service = Service("Infosphere")
service.name
data class Service(val name: String)
val service = Service("Cromulon")
service.name
Data/Case Classes
public static class Single {
// ...
}
object Single {
// ...
}
object Single {
// ...
}
Singletons
public enum Fruits {
BANANA, APPLE, KIWI
}
object Fruits extends Enumeration {
val BANANA, APPLE, KIWI = Value
}
enum class Fruits {
BANANA, APPLE, KIWI
}
Enums
Fruits x = Fruits.BANANA;
switch (x) {
case BANANA:
System.out.println("Banana");
break;
case APPLE:
System.out.println("Apple");
break;
case KIWI:
System.out.println("Kiwi");
break;
default:
System.out.println("Unknown");
}
val x = Fruits.BANANA
x match {
case Fruits.BANANA => println("Banana")
case Fruits.APPLE => println("Apple")
case Fruits.KIWI => println("Kiwi")
case _ => println("Unknown")
}
val x = Fruits.BANANA
when(x) {
Fruits.BANANA -> println("Banana")
Fruits.APPLE -> println("Apple")
Fruits.KIWI -> println("Kiwi")
else -> println("Unknown")
}
Switch Statements & Pattern Matching
Pattern Matching
public static class SUtils {
public static String
removeFirst(String s) {
return s.substring(1);
}
}
String oo = SUtils.removeFirst("foo");
object Utils {
implicit class StringUtils(s: String) {
def removeFirst(): String = {
s.substring(1)
}
}
}
import Utils.StringUtils
val oo = "foo".removeFirst()
fun String.removeFirst(): String {
return this.substring(1)
}
val oo = "foo".removeFirst()
Extension Methods/Functions
Optional<String> optional =
Optional.empty();
optional.ifPresent(s ->
System.out.println(s.toUpperCase()));
System.out.println(optional
.map(String::toUpperCase)
.orElse(null));
var optional: Option[String] = None
optional.foreach {s =>
println(s.toUpperCase)
}
println(optional
.map(_.toUpperCase)
.orNull)
fun String.removeFirst(): String {
return this.substring(1)
}
val oo = "foo".removeFirst()
Dealing with optional values
val optional: String? = null
if (optional != null) {
println(optional.toUpperCase())
}
println(optional?.toUpperCase())
Fruits x = Fruits.BANANA;
String result;
if (x == Fruits.BANANA) {
result = "banana";
} else if (x == Fruits.APPLE) {
result = "apple";
} else {
result = "some other fruit";
}
val x = Fruits.BANANA
val result = if (x == Fruits.BANANA) {
"banana"
} else if (x == Fruits.APPLE) {
"apple"
} else {
"some other fruit"
}
If-else and expressions
val x = Fruits.BANANA
val result = if (x == Fruits.BANANA) {
"banana"
} else if (x == Fruits.APPLE) {
"apple"
} else {
"some other fruit"
}
public class Example {
public static void main(String[] args) {
// ...
}
}
object Main extends App {
if (args.length == 0) {
println("args is required")
}
// ...
}
Main Methods
fun main(args: Array<String>) {
// ...
}
case class Person(id: Int, name: String) {
// . . .
}
object Main extends App {
val juan = new Person(1, "Juan")
juan.id
}
def loves(that: Person): String = {
s"${name} loves ${that.name}"
}
Every expression returns a value
def loves(that: Person): String = {
s"${name} loves ${that.name}"
}
def changeName(newName: String): Option[Person] = {
if(newName.nonEmpty) {
val personWithNewName = this.copy(name = newName)
Some(personWithNewName)
} else {
None
}
}
def doNothing: Unit = {
"foo"
}
val juan = new Person(1, "Juan")
juan.name = "Foo" // This is a compile time error
// Error:(26, 25) reassignment to val juan.name = "Foo"
"It allows you to decompose a given data structure, binding the values it was constructed from to variables."
case class Person(id: Int, name: String) {
def changeName(newName: String): Option[Person] = {
if(newName.nonEmpty) {
val personWithNewName = this.copy(name = newName)
Some(personWithNewName)
} else {
None
}
}
def doNothing: Unit = {
"foo"
}
}
object Main extends App {
val juan = new Person(1, "Juan")
val juanManuel = juan.changeName("Juan Manuel") match {
case Some(person) => person
case None => throw new Exception("Can't do that!")
}
}
case class Person(id: Int, name: String) {
def loves(that: Person): String = {
s"${name} loves ${that.name}"
}
def changeName(newName: String): Option[Person] = {
if(newName.nonEmpty) {
val personWithNewName = this.copy(name = newName)
Some(personWithNewName)
} else {
None
}
}
def doNothing: Unit = {
"foo"
}
}
object Main extends App {
val juan = Person(1, "Juan")
// juan.name = "Foo" // This is a compile time error
// Error:(26, 25) reassignment to val juanManuel.name = "Foo"
val jolene = Person(2, "Jolene")
val loveNote = juan.loves(jolene)
println(loveNote)
val juanManuel = juan.changeName("Juan Manuel") match {
case Some(person) => person
case None => throw new Exception("Can't do that!")
}
println(juanManuel)
}
//Juan loves Jolene
//Person(1,Juan Manuel)
data class Person(val id: Int, val name: String) {
fun loves(that: Person): String {
"$name loves ${that.name}"
}
fun changeName(newName: String): Person? {
return if(newName.isNotBlank()) {
val personWithNewName = this.copy(name = newName)
personWithNewName
} else {
null
}
}
fun doNothing(): Unit {
"foo"
}
}
fun main(args: Array<String>) {
val juan = Person(1, "Juan")
// juan.name = "Foo" // This is a compile time error
//
val jolene = Person(2, "Jolene")
val loveNote = juan.loves(jolene)
println(loveNote)
val juanManuel = when(val x = juan.changeName("Juan Manuel")) {
is Person -> x
else -> throw Exception("Can't do that!")
}
println(juanManuel)
}
Is a way to construct programs only using:
Use Pure Functions
Immutable Values
1
2
This means no interaction with the outside world!
class Foo {
def getFoo: Int = {
// ...
}
}
class Bar {
def doBar(Type param): Unit = {
// Do something
}
}
Functions that don't take any parameters
Functions that don't return anything
What programming would be like without the ability to do these things? [1]
Functional programming restricts how we write programs, but it is not a restriction on what programs can express.
Learning how to express all your programs without side effects , including the ones that do IO, handle errors, and modify data, can be extremely beneficial to increase modularity, simplify testing, increase reuse, enable parallelization, and are easier to understand [1]
Pure FP
Core
Impure Outer
Layer
The best FP Code reads like algebra. Take the following program:
val a = f(x)
val b = g(a)
val c = h(b)
return c
We could replace the value of 'c' with the result of 'h(b)' and the meaning of the program will remain unchanged
val c = 42
return c
With immutable values, we have a strong guarantee that when we replace c = h(g(f(x)) with it's result 42, the meaning of the program will not be affected[1]
With immutable values, we have a strong guarantee that when we replace c = h(g(f(x)) with it's result 42, the meaning of the program will not be affected[1]
object AsyncExample extends App {
// Nums is a Java array which can be modified in place!
val nums = (-100000 to 100000).reverse.toArray
// Sum all the numbers in the array while we sort the array
val task1 = Future {nums.sum}
val task2 = Future {util.Arrays.sort(nums); nums}
// printVal is a helper function
task1.onComplete(printVal)
task2.onComplete(printVal)
Seq(task2, task1).foreach(Await.result(_, 50.millis))
}
# first run
This is the reversed array: 100000, 99999, 99998 ... -99998, -99999, -100000
This is the ordered array: -100000, -99999, -99998 ... 99998, 99999, 100000
This is the sum: 21788228
# second run
# ...
This is the sum: 25184250
# third run
# ...
This is the sum: 599994
# fourth run
# ...
This is the sum: 0
object AsyncImmutableExample extends App {
val nums = (-1000000 to 1000000).reverse
printVal(nums)
val task1 = Future {nums.sum}
val task2 = Future {nums.sorted}
task1.onComplete(printVal)
task2.onComplete(printVal)
Seq(task2, task1).foreach(Await.result(_, 50.millis))
}
# first run
This is the reversed array: 1000000, 999999, 999998 ... -999998, -999999, -1000000
This is the ordered array: -1000000, -999999, -999998 ... 999998, 999999, 1000000
This is the sum: 0
# second run
# ...
This is the sum: 0
# third run
# ...
This is the sum: 0
# fourth run
# ...
This is the sum: 0
It is simply a function that can take other functions as parameters, or return functions
In this instance, the filter method of the collections class is a HOF as it takes a function and applies it to every element of the in the range the isEven function
The filter method is defined as:
val isEven = (i: Int) => i % 2 == 0
val myRange = (1 to 10)
val evenNumbers = myRange.filter(i => isEven(i))
// Write it more concisely
val evenNumbers = myRange.filter(isEven)
// This will result in
(2, 4, 6, 8, 10)
def filter(p: (T) => Boolean): List[T]
It is a technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions [3]
def sum(a: Int)(b: Int): Int = {
a + b
}
val addOne: Int => Int = sum(1)
val addTwo: Int => Int = sum(2)
val addTen: Int => Int = sum(10)
println(addOne(1)) // 2
println(addTwo(1)) // 3
println(addTen(1)) // 11
This syntactic sugar is equivalent to
val sum: Int => (Int => Int) = {
a => {
b => a + b
}
}
A partial application is the process of applying a function to some of its arguments. A partially-applied function gets returned for later use [1]
def myWhile(condition: => Boolean)(codeBlock: => Unit): Unit = {
while(condition) {
codeBlock
}
}
var i = 0
myWhile(i < 10) {
println(i)
i += 1
}
def wrapper(tag: String)(codeBlock: => String): String = {
s"<$tag>" + codeBlock + s"</$tag>"
}
def wrapperWithParameters(tag: String)(parameters: Seq[(String, String)])(codeBlock: => String): String = {
s"<$tag " +
parameters.map{ case (key, value) => s"$key=$value"}.mkString(" ") + " >" +
codeBlock +
s"</$tag>"
}
val htmlWrapper = wrapperWithParameters("html") _
val divWrapper = wrapper("div")(_)
val strongWrapper = wrapper("strong")(_)
val preWrapper = wrapper("pre")(_)
val html: String = htmlWrapper(Seq("key" -> "value")) {
divWrapper {
preWrapper {
strongWrapper {
"My Awesome functional website!"
}
}
}
}
<html key=value >
<div>
<pre>
<strong>My Awesome functional website!</strong>
</pre>
</div>
</html>