Functionally Insane

Overview

  • Why Scala
  • Syntax comparison
  • Writing a simple program
  • Functional programming

How I got Started with Scala?

6 years ago I wanted to learn a new statically typed language

I quickly fell in love

  • Modern language
  • Statically typed
  • Concise
  • Well suited for FP and OOP
  • Helped me improved the way I code
  • Well suited for distributed and reactive systems
  • Pattern matching
  • +++

Scala combines object-oriented and functional programming in one concise, high-level language. Scala's static types help avoid bugs in complex applications [2]

JVM LanguagesSide By Side

JVM LanguagesSide By Side

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

JVM LanguagesSide By Side

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

JVM LanguagesSide By Side

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

JVM LanguagesSide By Side

@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

JVM LanguagesSide By Side

public static class Single {
  // ...
}
object Single {
  // ...
}
object Single {
  // ...
}

Singletons

JVM LanguagesSide By Side

public enum Fruits {
  BANANA, APPLE, KIWI
}
object Fruits extends Enumeration {
  val BANANA, APPLE, KIWI = Value
}
enum class Fruits {
    BANANA, APPLE, KIWI
}

Enums

JVM LanguagesSide By Side

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

JVM LanguagesSide By Side

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

JVM LanguagesSide By Side

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())

JVM LanguagesSide By Side

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"
}

JVM LanguagesSide By Side

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>) {
  // ...
}

Writing a Love Note

Love Letter

  • Define a Person class
  • Each person must have an ID and a Name
  • Make a Person love another Person
  • Allow to "change" the name
  • Return an Option[Person] on name change
  • Create the main object

Defining a Person class

case class Person(id: Int, name: String) {
  // . . .
}

object Main extends App {
  val juan = new Person(1, "Juan")
  juan.id
}

Person loves person


  def loves(that: Person): String = {
    s"${name} loves ${that.name}"
  }

Change 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"
  }

Re-assignment of values will cause a compile time error


  val juan = new Person(1, "Juan")
  juan.name = "Foo" // This is a compile time error 
  // Error:(26, 25) reassignment to val juan.name = "Foo"
  

Pattern matching is a powerful feature

"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!")
  }
}

The full program in

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)

The full program in

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)
}

What is Functional Programming?

What is Functional Programming?

Is a way to construct programs only using:

Use Pure Functions
Immutable Values
1
2

So what is a pure function?

  1. Depends only on its input parameters and its internal algorithm
  2. Has no side effects

This means no interaction with the outside world!

How can you detect impure functions?

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

Side
Effects
Include

Side

effects

include

  • Modifying an existing variable
  • Printing to the screen (including CLI)
  • Making API calls
  • Writing to a file
  • Reading from a file
  • Setting a field on an object
  • Throwing exceptions
  • Printing
  • Returning null values!
  • Reading from a database
  • . . .

Consider for a moment

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

What "pure" functional applications look like

 

 

Immutability

What is an immutable value?

  1. Is a value that never changes, once a value has been assigned it is permanent

But, Why???

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]

Immutable Values

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]

Easier
To
Reason

Functional programming is easier to reason about

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))
}

Functional programming is easier to reason about

# 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

Functional programming is easier to reason about

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))
}

Functional programming is easier to reason about

# 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

Higher
Order
Functions

What are Higher order functions (hof)?

It is simply a function that can take other functions as parameters, or return functions

Assigning functions to vals

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]

Currying

Currying

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 
    }
}

In this context we say that sum is a partially applied function!

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]

Custom Control Structure

def myWhile(condition: => Boolean)(codeBlock: => Unit): Unit = {
  while(condition) {
    codeBlock
  }
}


var i = 0
myWhile(i < 10) {
  println(i)
  i += 1 
}

Wrapping it all up

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")(_)

Functional HTML Wrappers

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>

Functional HTML Wrappers

Conclusion

Conclusion

  • Challenge yourself to write code using only "immutable" variables
  • Functional programming principles will enable you to write better and safer programs
  • We didn't cover it, but pure functions are easier to test
  • Functional programs can be more modular
  • Let me know if  you want to learn more about Scala!

References

Made with Slides.com