Intro a Programación Funcional en Scala

Cristian Spinetta | Gonzalo Guglielmo

Agenda

  • Qué es Scala?
  • Sintaxis: de Java a Scala
  • Inmutabilidad en Scala
  • Programación funcional: Qué es?
  • Estructuras funcionales
  • Codear con Side Effects

Qué es Scala?

  • Corre sobre la JVM
  • Java++
  • Funcional + OO
  • Tipado estático + inferencia de tipos
  • Conciso/expresivo
  • REPL

Sintaxis: De Java a Scala

Sintaxis

Ejemplo en SCALA

Operaciones elementales

val radius: Double = 2.3;

var color: String = "red";

def area(radius: Double): Double = {
  val pi: Double = 3.141592;
  val sqRad: Double = radius * radius;
  return pi * sqRad;
}

val description: String =
  "Area is: " + area(radius);

'return' implícito

val radius: Double = 2.3;

var color: String = "red";

def area(radius: Double): Double = {
  val pi: Double = 3.141592;
  val sqRad: Double = radius * radius;
  pi * sqRad;
}

val description: String =
  "Area is: " + area(radius);

Ejemplo en SCALA

Operaciones elementales

Sintaxis

val radius: Double = 2.3;

var color: String = "red";

def area(radius: Double): Double = {
  val pi: Double = 3.141592;
  val sqRad: Double = radius * radius;
  return pi * sqRad;
}

val description: String =
  "Area is: " + area(radius);

inferencia de tipos

val radius = 2.3;

var color = "red";

def area(radius: Double) = {
  val pi = 3.141592;
  val sqRad = radius * radius;
  pi * sqRad;
}

val description = "Area is: " + area(radius);

Ejemplo en SCALA

Operaciones elementales

Sintaxis

val radius: Double = 2.3;

var color: String = "red";

def area(radius: Double): Double = {
  val pi: Double = 3.141592;
  val sqRad: Double = radius * radius;
  pi * sqRad;
}

val description: String =
  "Area is: " + area(radius);
val radius = 2.3;

var color = "red";

def area(radius: Double) = {
  val pi = 3.141592;
  val sqRad = radius * radius;
  pi * sqRad;
}

val description = "Area is: " + area(radius);

; opcional

val radius = 2.3

var color = "red"

def area(radius: Double) = {
  val pi = 3.141592
  val sqRad = radius * radius
  pi * sqRad
}

val description = "Area is: " + area(radius)

Ejemplo en SCALA

Operaciones elementales

Sintaxis

val radius = 2.3

var color = "red"

def area(radius: Double) = {
  val pi = 3.141592
  val sqRad = radius * radius
  pi * sqRad
}

val description = "Area is: " + area(radius)

{ } opcional

val radius = 2.3

var color = "red"

def area(radius: Double) = 3.141592 * radius * radius

val description = "Area is: " + area(radius)

Ejemplo en SCALA

Operaciones elementales

Sintaxis

val radius = 2.3

var color = "red"

def area(radius: Double) = 3.141592 * radius * radius

val description = "Area is: " + area(radius)

String interpolation

val radius = 2.3

var color = "red"

def area(radius: Double) = 3.141592 * radius * radius

val description = s"Area is: ${area(radius)}"

Ejemplo en SCALA

Operaciones elementales

Sintaxis

public class User {

    private Integer id;
    private String name;
    private LocalDate birthday;

    public User(Integer id, String name,
        LocalDate birthday) {
        this.id = id;
        this.name = name;
        this.birthday = birthday;
    }

    public Integer getId() { return id; }
    public void setId(Integer id){ this.id = id;}
   // Getter and Setter for rest of fields...

    @Override
    public boolean equals(Object o) {
        // usually a lot of code...
    }

    @Override
    public int hashCode() {
        // more code!
    }
}
case class User(id: Int,
                name: String,
                birthday: LocalDate)

JAVA

SCALA

DTO

  • Constructor
  • Getters (y Setters si hay var)
  • copy()
  • toString()
  • equals() y hashCode()

Sintaxis

val status = if (age >= minAge) "adult" else "minor"

if como expresión

Sin sentencias: todo es una expresión

Sintaxis

def audit(message: String): Unit = {
  auditClient.post(message)
  logger.debug("audit with message: $message")
}

Valor Unit

Inmutabilidad

en Scala

Los valores asignados no cambian

Inmutabilidad

A balanced attitude for Scala programmers

“Prefer vals, immutable objects, and methods without side effects. Reach for them first. Use vars, mutable objects, and methods with side effects when you have a specific need and justification for them.”

scala> val name = "Peter"
name: String = Peter

scala> name = "Paul"
<console>:8: error: reassignment to val
       name = "Paul"
            ^

Variable inmutable

scala> val names = List("Peter", "Paul")
names: List[String] = List(Peter, Paul)

scala> names :+ "Moley"
res0: List[String] = List(Peter, Paul, Moley)

Lista inmutable

Ejemplo: Cuenta bancaria

Inmutabilidad

class BankAccount {
  var balance = 0

  def deposit(amount: Int) {
    if (amount > 0) balance += amount
    else balance
  }

  def withdraw(amount: Int): Int =
    if (amount >= 0 && amount <= balance) {
      balance -= amount
      balance
    } else {
      println("insufficient funds")
      balance
    }
}

Con mutabilidad

val account = new BankAccount
account.deposit(30) // return 30
account.deposit(20) // return 20
account.withdraw(10) // return 10

// Current balance: 40
case class BankAccount(balance: Int) {

  def deposit(amount: Int) =
    if (amount > 0)
      BankAccount(balance = balance + amount)
    else
      this

  def withdraw(amount: Int) =
    if (amount >= 0 && amount <= balance) {
      BankAccount(balance = balance - amount)
    } else {
      println("insufficient funds")
      this
    }
}

Con inmutabilidad

BankAccount(0)
  .deposit(30) // return a new BankAccount
  .deposit(20) // return a new one
  .withdraw(10) // return a new one

// Balance in the last BankAccount: 40

Inmutabilidad: No se cambia el valor, se crea uno nuevo.

Funcional

Funcional: Qué es?

  • Output determinado sólo por su input
  • Evaluar una expresión siempre da el mismo resultado
  • Se puede reemplazar una expresión por su resultado: transparencia referencial

Funciones puras, valores y composición, nada más!

Implicancias:

Transparencia Referencial

val value = 20
(value, value)

Hay Transparencia Referencial si la evaluación de una expresión se puede reemplazar por su resultado y el programa no cambia.

(20, 20)

Ejemplos:

(iter.next(), iter.next())
val value = iter.next()
(value, value)

Toda expresión, o tiene side-effect o tiene transparencia referencial

def compute(i: BigDecimal): Int = {
  logger.info("Computing value")
  i * 30
}
(compute(2.05), compute(2.05))
val value = compute(2.05)
(value, value)
def addMissingSubject(email: Email) = {
  if (email.subject.isEmpty)
    email.copy(subject = "No subject")
  else email
}
(addMissingSubject(email1),
 addMissingSubject(email1))
val value = addMissingSubject(email1)
(value, value)

Estructuras funcionales

Funciones de Orden Superior

Son aquellas funciones que reciben funciones como parámetro o retornan funciones como resultado.

class Person(firstName: String, lastName: String, val age: Int) {
    def itIsTrueThat(f: Person => Boolean): Boolean = f(this)
}



def isOlderThan30(p: Person): Boolean = p.age > 30



val mirtha = new Person("Mirtha", "Legrand", 9999)


def isOlderThan30(p: Person): Boolean = p.age > 30
def isOlderThan50(p: Person): Boolean = p.age > 50
def isOlderThan5000(p: Person): Boolean = p.age > 5000
def isOlderThan10000(p: Person): Boolean = p.age > 10000

mirtha.itIsTrueThat(isOlderThan30) // true
mirtha.itIsTrueThat(isOlderThan50) // true
mirtha.itIsTrueThat(isOlderThan5000) // true
mirtha.itIsTrueThat(isOlderThan10000) // false

Funciones de Orden Superior

def isOlderThan(n: Int, p: Person): Boolean = p.age > n

Podríamos extraer la edad mínima contra la que queremos comparar:

Pero, ¿ya no podemos pasarla como parámetro directamente?

Aplicación Parcial

Aplicar una función con menos parámetros de los que originalmente recibe, devolviendo una nueva función que recibe los parámetros que le faltan y retorna lo que retornaba la función original.

Aplicación Parcial

def isOlderThan(n: Int)(p: Person): Boolean = p.age > n

Una función de tipo Int => Person => Boolean

Ejemplo:

val olderThan300: (Person => Boolean) = isOlderThan(300)

La podemos aplicar solo con el parámetro n:

mirtha.itIsTrueThat(olderThan300) // true

Y usamos la nueva función con la persona:

o

mirtha.itIsTrueThat(isOlderThan(300)) // true

Funciones anónimas (Lambdas)

Son funciones sin nombre, definidas en línea para ser usadas una única vez.

mirtha.itIsTrueThat(isOlderThan(300)) // true
mirtha.itIsTrueThat((p: Person) => p.age > 300) // true
mirtha.itIsTrueThat(p => p.age > 300) // true con inferencia de tipos
mirtha.itIsTrueThat(_.age > 300) // true con _
List<Person> students = new ArrayList<Person>(new Person("Juan", 9),
                                                 new Person("Pepe", 6));
List<Person> nerds = new ArrayList<Person>();


for (Person student : students) {
    if (student.getGrade >= 9) {
        nerds.add(student);
    }

}

return nerds;

Veamos éste código en Java 1.7

Resolvemos el mismo problema en Scala

val students = List(Person("Juan", 9), Person("Pepe", 6))
students.filter(_.grade >= 9)

Declaratividad: ocuparse de qué problema resolver, y no tanto del cómo resolverlo

Expresividad: se refiere a lo auto-descriptivo del código

Map

Se aplica una función a cada elememento de la colección, transformándolo.

List(1,2,3,4).map(elem => s"$elem !")
// List("1 !", "2 !", "3 !", "4 !")
mails.map(_.subject)

Obtengo una nueva colección con los elementos transformados

val unNerd = students.find(_.grade >= 9)

¿De qué tipo es unNerd?

Dado el siguiente código:

Cómo resolvemos...

  • Uso de null
  • Excepciones
  • Logging
  • Métricas
  • Mantener un estado global
  • ...

?

Effect

Tipo de datos con info extra

Option[T]: Effect para modelar null

Ejemplo:

¿De qué tipo es nerd?

Volviendo al find()...

Option[Person]

students
  .find(_.grade >= 9)
  .map(nerd => Response.Ok(nerd.name))
  .getOrElse(Response.NotFound("no nerd found"))
val nerd = students.find(_.grade >= 9)
val nerd: Option[Person] = students.find(_.grade >= 9)

Ejemplo de uso:

Effect

Otro ejemplo: errores

def divide(dividend: Int, divisor: Int):Int = {
  if (divisor == 0)
    throw new RuntimeException(
      "Divisor can't be 0")
  dividend / divisor
}

En imperativo usaríamos Exception

En funcional usamos Effect...

def divide(dividend: Int,
           divisor: Int):
    Either[String, Int] = {
  if (divisor == 0)
    Left("Divisor can't be 0")
  else
    Right(dividend / divisor)
}

Es un side-effect

  • Pierdo Transparencia Referencial
  • Es dependiente del contexto
  • Difícil de tracear/razonar/refactorizar/etc.

Either[T]: Effect para modelar errores

Es puro (sin side-effect)

  • Tiene Transparencia Referencial
  • Tipado
  • Separación de responsabilidades

Effect

Tiene la forma F[T]

type F[T] = Option[T]

Puede tener un valor T o no

type F[T] = Either[E, T]

Devuelve un error E o un resultado T

type F[T] = List[T]

Genera entre 0 y N valores T

Y muchos otros...

(Para algún E)

Programa en F que computa un valor T

¿Preguntas?

¡ Muchas Gracias !

We are hiring!     

  http://www.despegar.com/sumate/#

  IT Talent Acq: maria.arean@despegar.com 

 

Made with Slides.com