Cristian Spinetta
Software developer. More presentations on account /cspinetta
Cristian Spinetta | Gonzalo Guglielmo
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
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
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
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
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
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
val status = if (age >= minAge) "adult" else "minor"
if como expresión
Sin sentencias: todo es una expresión
def audit(message: String): Unit = {
auditClient.post(message)
logger.debug("audit with message: $message")
}
Valor Unit
Los valores asignados no cambian
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
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.
Funciones puras, valores y composición, nada más!
Implicancias:
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
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?
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.
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
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 = Person("Juan", 9) :: Person("Pepe", 6) :: Nil
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
val unNerd = students.find(_.grade >= 9)
¿De qué tipo es unNerd?
Dado el siguiente código:
Tipo de datos con info extra
sealed trait Option[+T]
case class Some[T](value: T) extends Option[T]
case object None extends Option[Nothing]
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:
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
sealed trait Either[+A, +B]
case class Left[A, B](left: A)
extends Either[A, B]
case class Right[A, B](right: A)
extends Either[A, B]
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
Either[T]: Effect para modelar errores
Es puro (sin side-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
Muchos otros...
(Para algún E)
Programa en F que computa un valor T
case class Email(subject: String, text: String)
val addMissingSubject = (email: Email) =>
if (email.subject.isEmpty) email.copy(subject = "No subject")
else email
val checkSpelling = (email: Email) =>
email.copy(text = email.text.replaceAll("youre", "you're"))
val removeInappropriateLanguage = (email: Email) =>
email.copy(text = email.text.replaceAll("dynamic typing", "**CENSORED**"))
val addAdvertismentToFooter = (email: Email) =>
email.copy(text = email.text + "\nThis mail sent via Super Awesome Free Mail")
val pipeline = addMissingSubject
.andThen(checkSpelling)
.andThen(removeInappropriateLanguage)
.andThen(addAdvertismentToFooter)
Ejemplo: preparar Email
Funciones con valores (en "crudos")
scala> pipeline(Email(subject = "Apply for Dev", text = "Hi! Do you have opportunities
with dynamic typing lang like Groovy or with static typing like Scala?"))
res1: Email =
Email(Apply for Dev,Hi! Do you have opportunities with **CENSORED** lang like Groovy or
with static typing like Scala?
This mail sent via Super Awesome Free Mail)
(f o g) (x)
case class Email(subject: String, text: String)
type EmailPipeline = Email => Option[Email]
val checkSubject: EmailPipeline = (email: Email) =>
if (email.subject.isEmpty) None
else Some(email)
val fixSpelling: EmailPipeline = (email: Email) =>
Some(email.copy(text = email.text.replaceAll("youre", "you're")))
val removeInappropriateLanguage: EmailPipeline = (email: Email) =>
if (email.text.contains("dynamic typing")) None
else Some(email)
val addAdvertismentToFooter: EmailPipeline = (email: Email) =>
Some(email.copy(text = email.text + "\nThis mail sent via Super Awesome Free Mail"))
Ejemplo: preparar Email
Funciones con Effects
Las funciones devuelven Option[Email]
scala> checkSubject.andThen(fixSpelling)
<console>:13: error: type mismatch;
found : Email => Option[Email]
required: Option[Email] => ?
checkSubject.andThen(fixSpelling)
^
Funciones con Effect no componen directamente
def effectComposition[A, B, C, F[_]](f: A => F[B], g: B => F[C]): A => F[C]
Necesitamos componer funciones que devuelven Effect:
Funciones con Effects
A => F[B]
B => F[C]
+
A => F[C]
En código:
Ejemplo: funciones que retornan Option[T]
def optionComposition[A, B, C](f: A => Option[B],
g: B => Option[C]): A => Option[C] =
(a: A) => f(a) match {
case Some(b) => g(b)
case None => None
}
def optionComposition[A, B, C](f: A => Option[B],
g: B => Option[C]): A => Option[C] =
(a: A) => f(a).flatMap(b => g(b))
No se puede generalizar para cualquier Effect
sealed trait Option[+T] {
def flatMap[B](f: T => Option[B]): Option[B] = this match {
case Some(c) => f(c)
case None => None
}
}
case class Some[T](value: T) extends Option[T]
case object None extends Option[Nothing]
Que tal si cada Effect implementa flatMap()?
Funciones con Effects
F[A]
A => F[B]
+
F[B]
Option[T] ya lo implementa:
Volviendo a nuestro ejemplo de emails:
val email = Email(subject = "Apply for Dev", text = "Hi! Do you have opportunities
with dynamic typing lang like Groovy or with static typing like Scala?")
checkSubject(email) // Some(Email(..))
.flatMap(fixSpelling) // Some(Email(..))
.flatMap(removeInappropriateLanguage) // None
.flatMap(addAdvertismentToFooter) // None
trait Monad[F[_]] {
def unit[A](value: A): F[A]
def flatMap[A, B](effect: F[A])(f: A => F[B]): F[B]
}
flatMap() viene de una abstracción muy nombrada últimamente: Monad
de Effects mediante flatMap()
Un Effect es un Monad si implementa:
Option es un Monad con unit:
def unit[A](value: A): Option[A] = Some(value)
Either es un Monad con unit:
def unit[A, B](value: B): Either[A, B] = Right(value)
List es un Monad con unit:
def unit[A](value: A): List[A] = List(value)
Ofrece una manera conveniente de secuenciar operaciones que generan Effect
Y además debe cumplir las leyes de identidad y asociatividad
Algunas Monad implementadas en Scala:
Scala ofrece una manera muy conveniente de operar con Monads: for-comprehension
de Effects mediante Monad
case class Email(subject: String, text: String, from: String, to: List[String])
case class Person(name: String, email: String)
case class Envelope(email: Email, destination: Person)
def getPersonByEmail(email: String): Option[Person] = ???
Ejemplo: Preparar Envelope para los Emails
def generateEnvelopes(emails: List[Email]): List[Envelope] =
emails
.flatMap(email => email.to
.flatMap(destEmail => getPersonByEmail(destEmail)
.map(destination => Envelope(email, destination))))
def generateEnvelopesViaFor(emails: List[Email]): List[Envelope] = {
for {
email <- emails
destEmail <- email.to
destination <- getPersonByEmail(destEmail)
} yield {
Envelope(email, destination)
}
}
Lo mismo mediante for-comprehension
By Cristian Spinetta
Introducimos el lenguaje de programación Scala y las bases de la programación funcional, tomando como ejemplo casos de uso real, en vez de académicos.