Introduction to Scala – Functional Programming in Scala – Basics



Introduction to Scala – Functional Programming in Scala – Basics

0 0


Introduction-to-Scala-Presentation


On Github VlachJosef / Introduction-to-Scala-Presentation

Introduction to Scala

Functional Programming in Scala

Created by Josef Vlach / vlach.josef@gmail.com

Agenda

  • Scala Basics
    • Basic syntax
    • Avoiding NullPointerException
    • Pattern matching
  • Live Coding
  • Functional Programming

What is Scala?

  • Created by prof. Martin Odersky - EPFL
  • Appeared around 2003
  • FP + OO
  • Compiling to Byte code and running in JVM
  • Statically typed
  • but with Type Inference
  • Very good Java interop
  • Open Source

Basics

Hello World

  object MyApp {
    def main(args: Array[String]): Unit = {
      println("Hello World!")
    }
  }
  
  object MyApp extends App {
    println("Hello World!")
  }
						

Define some variables

  • Scala has two kinds of variables, vals and vars
  val msg = "Hello, world!"

  msg = "Goodbye cruel world!" // error: reassignment to val

  var greeting = "Hello, world!"

  greeting = "Leave me alone, world!" // OK
  
  val msg2: java.lang.String = "Hello again, world!"

  val msg3: String = "Hello again, world!"
						

Simple Method

  def getFullName(firstName: String, lastName: String): String = {
    firstName + " " + lastName
  }
						

Real World Method

  def getFullName(firstName: String, lastName: String): String = {
    val result: StringBuilder = new StringBuilder

    if(!firstName.trim.isEmpty) {
        result append firstName
    }

    if(!lastName.trim.isEmpty) {
        if(!result.isEmpty) {
            result append " "
        }

        result append lastName
    }

    result.toString
  }
						

Infix/Dot Notation

  1 + 2

  (1).+(2)


  result.append(a).append(b).append(c)
  result append a append b append c
						

Real World Method

  def getFullName(firstName: String, lastName: String): String = {
    val result: StringBuilder = new StringBuilder

    if(!firstName.trim.isEmpty) {
        result append firstName
    }

    if(!lastName.trim.isEmpty) {
        if(!result.isEmpty) {
            result append " "
        }

        result append lastName
    }

    result.toString
  }
						

Nice

  def getFullName(firstName: String, lastName: String) = 
    List(firstName, lastName) filterNot (_.trim.isEmpty) mkString " "
						

Everything is an Expression

  val color = if(user.isBlocked) "red" else "green"
  
  val number = try "123".toInt catch {
    case e: NumberFormatException => 0
  }					
						

Default Arguments

  def getUser(
    firstName: String = "John",
    lastName: String = "Doe",
    age: Int = -1) = {
    // ...
  }
						

Named Arguments

  • How many times you saw this?
      createUser(user, true, false, false, true, false, false)
    
    
  • Isn't this better?
      createUser(
        user = user,
        encryptPassword = true,
        admin = false,
        ldapAuth = false,
        suspicious = true,
        blocked = false,
        visible = false)
    
    

Class User - Java 1/4

  public class User {
    private String firstName;
    private String lastName;
    private int age;
  
    public User(String firstName, String lastName, int age) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
    }
  
    public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
    }
  
    public User() {}
						

Class User - Java 2/4

  public String getFirstName() { return firstName; }
  public void setFirstName(String firstName) { this.firstName = firstName;}
 
  public String getLastName() { return lastName; }
  public void setLastName(String lastName) { this.lastName = lastName; }
 
  public int getAge() { return age; }
  public void setAge(int age) { this.age = age; }

Class User - Java 3/4

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result;
        + ((firstName == null) ? 0 : firstName.hashCode());
    result = prime * result
        + ((lastName == null) ? 0 : lastName.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User other = (User) o;    
    return age == other.age && firstName.equals(other.firstName) &&
        lastName.equals(other.lastName);
  }

Class User - Java 4/4

    @Override
    public String toString() {
      return "User(" + 
          firstName + ", " + 
          lastName + ", " + 
          age + ")";
    }
  }

Class User - Scala

case class User(var firstName: String, var lastName: String, var age: Int)

The Option type

The Option type

  • How offen we see NullPointerException?
  • Why?
  • For instance, Groovy has the null-safe operator foo?.bar?.baz
  • Nothing forces you to use it
  • Scala tries to solve the problem by getting rid of null values altogether

Option[A]

  • Container for an optional value of type A, i.e. values that may be present or not
  • If the value of type A is present, the Option[A] is an instance of Some[A]
  • If the value is absent, the Option[A] is the object None

Option[A]

  • By stating that a value may or may not be present on the type level, you and any other developers who work with your code are forced by the compiler to deal with this possibility
  • There is no way you may accidentally rely on the presence of a value that is really optional

Creating an option

  • Usually, you can simply create an Option[A] for a present value by directly instantiating the Some case class:
  val greeting: Option[String] = Some("Hello world")
  						
  • Or, if you know that the value is absent, you simply assign or return the None object:
  val greeting: Option[String] = None
  						

Working with optional values

case class User(
  id: Int,
  firstName: String,
  lastName: String,
  age: Int,
  gender: Option[String])

object UserRepository {
  private val users = Map(
  1 -> User(1, "John", "Doe", 32, Some("male")),
  2 -> User(2, "Johanna", "Doe", 30, None))

  def findById(id: Int): Option[User] = users.get(id)
  def findAll = users.values
}
  						

Working with optional values

One way would be to check if a value is present by means of the isDefined method of your option, and, if that is the case, get that value via its get method:
  val user1 = UserRepository.findById(1)
  if(user1.isDefined) {
    println(user1.get.firstName)
  } // will print "John"
Very often, you want to work with a fallback or default value in case an optional value is absent. This use case is covered pretty well by the getOrElse method defined on Option:
  val user = User(2, "Johanna", "Doe", 30, None)
  println("Gender: " + user.gender.getOrElse("not specified"))
  // will print "Gender: not specified"
Or use Pattern Matching

Pattern matching

Pattern matching

switch statement
switch (whatIsThis) {
  case 8:
  case 10:
    doSomething();
    break;
  case 12:
    doSomethingElse();
    break;
  default:
    doDefault();
}

match expression
whatIsThis match {
  case 8 | 10 => something
  case 12 => somethingElse
  case _ => defaultValue
}

Wildcard pattern

  whatIsThis match {
    case _ => "anything!" 
  }

Constant pattern

  whatIsThis match {
    case 42 => "a magic no." 
    case "hello!" => "a greeting"
    case math.Pi => "another magic no."
    case _ => "something else"
  }

Variable pattern

  whatIsThis match {
    case 0 => "zero"
    case somethingElse => "not zero: " + somethingElse
  }

Typed pattern

  whatIsThis match {
    case n: Int => "aah, a number!?" 
    case c: Character => "it's" + c.name
  }

Constructor pattern

  sealed abstract class Shape
  case class Circle( radius : Double ) extends Shape
  case class Rectangle( width : Double, height : Double ) extends Shape
  case class Triangle( base : Double, height : Double ) extends Shape

  whatIsThis match {
    case Circle( radius ) => Pi * ( pow( radius, 2.0 ) )
    case Rectangle( 1, height ) => height
    case Rectangle( width, 1 ) => width
    case Rectangle( width, height ) => width * height
    case Triangle( 0, _ ) | Triangle( _, 0 ) => 0
    case Triangle( base, height ) => height * base / 2
  }

Tuple patterns

  whatIsThis match {
    case (a, b) => "Tuple2"
    case (42, math.Pi, _) => "magic numbers + anything"
    case (s: String, _, _) => "matched string on first position " + s
    case (a, b, c) => "matched " + a + b + c
    case _ => "no match"
  }

Pattern Matching on Option type

  val user = User(2, "Johanna", "Doe", 30, None)
  val gender = user.gender match {
    case Some(gender) => gender
    case None => "not specified"
  }
  println("Gender:" + gender)

Functional Programming

FP - Motivation

  public long vypoctiDotaci(Zadost zadost, List<ZadostOblast> oblasti) {
  
    long celkovaDotace = 0;
  
    for (ZadostOblast oblast : oblasti) {
      long naklady = oblast.getNakladySkutecne();
      long oblastDotace = naklady * getKoeficient(zadost);
  
      oblast.setDotace(oblastDotace);
      celkovaDotace += oblastDotace;
    }
  
    return celkovaDotace;
  }

What is FP

  • Assigment less programming
  • Function don't have side effect
    • Modifying a variable
    • Modifying a data structure in place
    • Setting a field on an object
    • Throwing an exception
    • Printing to the console or reading user input
    • Reading from or writing to a file
  • Function are first class citizen
  • Functions are higher order

Function Literal

  ( x: Int, y: Int ) => x + y

  ( a: Int, b: Int, c: Int ) => {
     val aSquare = a * a
     val bSquare = b * b
     val cSquare = c * c
     
     aSquare + bSquare == cSquare
  }

Function Value

  val add = ( x: Int, y: Int ) => x + y

  val isPythagoras = ( a: Int, b: Int, c: Int ) => {
     val aSquare = a * a
     val bSquare = b * b
     val cSquare = c * c
     
     aSquare + bSquare == cSquare
  }

Function Type

  val add: (Int, Int) => Int = ( x: Int, y: Int ) => x + y

  val isPythagoras: (Int, Int, Int) => Boolean = 
  ( a: Int, b: Int, c: Int ) => {
     val aSquare = a * a
     val bSquare = b * b
     val cSquare = c * c
     
     aSquare + bSquare == cSquare
  }

Function Application

  add(3,	8) // 11

  isPythagoras(1,2,3) // false

  isPythagoras(3,4,5) // true

Traversing - Java

public List<User> findUserByFirstName(List<User> users, String firstName) {
  List<User> foundUsers = new ArrayList<User>();
  for(User user: users) {
    if(user.getFirstName().contains(firstName)) {
      foundUsers.add(user);
    }
  }
  return foundUsers;
}

public List<User> findUserByLastName(List<User> users, String lastName) {
  List<User> foundUsers = new ArrayList<User>();
  for(User user: users) {
    if(user.getLastName().contains(lastName)) {
      foundUsers.add(user);
    }
  }
  return foundUsers;
}

Traversing - Scala

  users.filter((user: User) => user.firstName.contains("o"))
  users.filter((user: User) => user.lastName.contains("Mar"))

  users.filter(user => user.firstName.contains("o"))
  users.filter(user => user.lastName.contains("Mar"))

  users.filter(_.firstName.contains("o"))
  users.filter(_.lastName.contains("Mar"))

Currying - two argument functions

  def addA(x: Int, y: Int): Int =
    x + y

  def addB(x: Int): Int => Int =
    y => x + y

  val a = addA(10, 20)

  val b = addB(10)(20)

Currying - three argument functions

  def addA(x: Int, y: Int, z: Int): Int =
    x + y + z

  def addB(x: Int): Int => (Int => Int) =
    y => (z => x + y + z)

  val a = addA(1, 2, 3)

  val b = addB(1)(2)(3)

Function are modular

A pure function of type (A => B) is safe to use wherever an A is given and a B is expected.

It can be tested by simply giving it an A and inspecting the B.

It's always safe to call from multiple threads and order never matters.

Function are compositional

f : (A => B)

g : (B => C)

g compose f : (A => C)

Strategy Pattern

object DeathToStrategy extends App {

  def add(a: Int, b: Int) = a + b
  def subtract(a: Int, b: Int) = a - b
  def multiply(a: Int, b: Int) = a * b
  
  def execute(callback:(Int, Int) => Int, x: Int, y: Int) = callback(x, y)

  println("Add:      " + execute(add, 3, 4))
  println("Subtract: " + execute(subtract, 3, 4))
  println("Multiply: " + execute(multiply, 3, 4))

}
						

THE END

We didn't cover those topics:

  • Traits - multiple inheritance done right
  • Collection API
  • Implicit Conversions and Parameters
  • Control Abstraction
  • Structural Typing
  • For Comprehension
  • Promises a Futures
  • Covariant/Contravariant/Nonvariant type parameters
  • and much more...

Sources