Programación Funcional Básica en Scala
Agenda
- Scala básico
- Loops funcionales
- Funciones de Orden Superior
- Pattern matching
- Estructuras de datos funcionales
Código fuente de los ejercicios en
Scala básico
// A comment!
/* Another comment */
/** A documentation comment */
object MyModule {
def abs(n: Int): Int =
if (n < 0) -n
else n
def formatAbs(x: Int): String = {
val msg = "The absolute value of %d is %d"
msg.format(x, abs(x))
}
def main(args: Array[String]): Unit =
println(formatAbs(-42))
}
def abs(n: Int): Int = {
val resultado = if (n < 0) -n
else n
resultado
}
Orientado a expresiones
def abs(n: Int): Int = {
if (n < 0) -n
else n
}
def abs(n: Int): Int =
if (n < 0) -n
else n
object MyApp {
def main(args: Array[String]): Unit =
println("Hello world!")
}
object MyApp extends App {
println("Hello world!")
}
Loops Funcionales
Loops Funcionales
¿Cómo iterar en un lenguaje funcional?
Usando recursión
def fact(n: Int): Int = {
if(n <= 1)
1
else
n * fact(n-1)
}
Caso base
Paso de reducción
¿Por qué es útil la recursión?
- Muchas funciones se pueden definir naturalmente en términos de sí mismas
- Las propiedades de funciones recursivas pueden ser demostradas usando inducción matemática.
Loops funcionales
Problema: se llena la pila de ejecución
fact(4) == 4 * fact(3)
== 4 * ( 3 * fact(2) )
== 4 * ( 3 * ( 2 * fact(1) ) )
== 4 * ( 3 * ( 2 * 1 ) )
== 4 * ( 3 * 2 )
== 4 * 6
== 24
Recursión de cola
Idea: si lo último que hace una función es llamarse a sí misma entonces el stack frame puede reusarse. Esto se denomina tail recursion o recursión de cola
La recursión de cola es un proceso iterativo
La recursión de cola permite escribir código tan eficiente en uso de la pila de ejecución como un ciclo while.
Recursión de cola
Reusando la pila de ejecución:
def fact(n: Int, acc: Int): Int =
if(n <= 1)
acc
else
fact(n-1, n*acc)
Loops funcionales
fact(4, 1) == fact( 3 , 4 * 1 )
== fact( 3 , 4 )
== fact( 2 , 3 * 4 )
== fact( 2 , 12 )
== fact( 1 , 2 * 12 )
== fact( 1 , 24 )
== 24
def factorial(n: Int) : Int = {
def go(n: Int, acc: Int): Int =
if(n <= 1)
acc
else
go(n-1, n*acc)
go(n,1)
}
Encapsulando la implementación:
Ejercicio
¿Cómo computar la suma de unos números en un rango?
scala> sumInRange(1,2)
res0: Int = 3
scala> sumInRange(1,10)
res1: Int = 55
scala> sumInRange(6,6)
res2: Int = 6
scala> sumInRange(4,2)
res3: Int = 0
Solución
def sumInRangeNTR(start: Int, end: Int): Int =
if(start > end)
0
else
start + sumInRangeNTR(start+1, end)
def sumInRange(start: Int, end: Int): Int = {
def go(n: Int, acc: Int): Int =
if(n>end)
acc
else
go(n+1, n+acc)
go(start, 0)
}
Funciones de Órden Superior
Una función de orden superior lo puede serlo por dos razones:
1. Toma una función como argumento
2. Retorna una función como resultado

¿Por qué son útiles?
1. Expresiones idiomáticas comúnes pueden ser codificadas como funciones dentro del lenguaje en sí mismo: no hay necesidad de extender el lenguaje
2. Lenguajes de dominio específico pueden ser definidos como colecciones de funciones de orden superior
Formateando resultados
def abs(x: Int): Int =
if(x<0)
-x
else
x
def formatAbs(x: Int): String = {
val msg = "The absolute value of %d is %d"
msg.format(x, abs(x))
}
Formateando resultados
def formatFact(x: Int): String = {
val msg = "The factorial of %d is %d"
msg.format(x, fact(x))
}
¿Cómo podemos generalizar esto?
Formateando resultados
def formatFact(x: Int): String = {
val msg = "The factorial of %d is %d"
msg.format(x, fact(x))
}
¿Qué varía? ¿Qué se mantiene?
def formatAbs(x: Int): String = {
val msg = "The absolute value of %d is %d"
msg.format(x, abs(x))
}
Una función de orden superior
def formatResult(name: String, x: Int, f: Int => Int): String = {
val msg = "The %s of %d is %d"
msg.format(name, x, f(x))
}
def formatAbs2(x: Int): String =
formatResult("absolute value", x, abs)
def formatFact2(x: Int): String =
formatResult("factorial", x, fact)
Otro ejemplo: Encontrando elementos
def findFirst(strings: Array[String], key: String): Int = {
def loop(n: Int): Int =
if(n >= strings.length)
-1
else if(strings(n) == key)
n
else
loop(n+1)
loop(0)
}
Encontrando elementos
def findFirst(strings: Array[String], predicate: String => Boolean): Int = {
def loop(n: Int): Int =
if(n >= strings.length)
-1
else if(predicate( strings(n) ))
n
else
loop(n+1)
loop(0)
}
Generalizando un poco:
¿Esta función solamente debería funcionar para arreglos de Strings? ¿Hay algo en la implementación que solo funcione con Strings?
¿Qué podemos generalizar?
Parámetros de tipos
def findFirst[A](array: Array[A], predicate: A => Boolean): Int = {
def loop(n: Int): Int =
if(n >= array.length - 1)
-1
else if(predicate( array(n) ))
n
else
loop(n+1)
loop(0)
}
Los tipos pueden ser un parámetro más:
Ejercicio
def isSorted[A](array: Array[A], ordered: (A,A) => Boolean ): Boolean
Escribir una función que indique si un arreglo está ordenado o no:
Algunos detalles:
Propiedad length de un array:
Para acceder al elemento i de un array:
Array(4,5,6).length == 3
val arr = Array(4,5,6)
arr(0) == 4
arr(2) == 6
Solución
def isSorted[A](array: Array[A], ordered: (A,A) => Boolean ): Boolean = {
def loop(n: Int): Boolean =
if(n >= array.length-1)
true
else if(!ordered(array(n), array(n+1)))
false
else
loop(n+1)
loop(0)
}
Pattern Matching
Case Classes
Son clases con propiedades inmutables entre otras cosas
Permiten hacer pattern matching
case class Persona(nombre: String, edad: Int)
val laura = Persona("Laura", 33)
def saludar(persona: Persona): String = persona match {
case Persona(nombre,edad) => s"Hola, $nombre"
}
Patrón contra el que se compara el valor
Resultado si el valor coincide con el patrón
Case Classes
También se pueden incluir predicados:
def mensajeVerificacionEdad(persona: Persona): String =
persona match {
case Persona(nombre, edad) if edad >= 18 =>
s"$nombre tiene mas de 18 años"
case Persona(nombre, edad) =>
s"$nombre es menor de edad"
}
Expresando variantes
trait Usuario
case class Persona(id: String, nombre: String) extends Usuario
case class Empresa(nit: String, nombre: String) extends Usuario
def toString(usuario: Usuario): String = usuario match {
case Persona(nombre,id) =>
s"Persona con nombre $nombre e id $id"
case Empresa(nombre,nit) =>
s"Empresa con nombre $nombre y nit $nit"
}
Estructuras de datos funcionales
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
Listas Enlazadas

Tomada de "Functional Programming in Scala"
(Sobre varianza)
El + en la declaración List[+A] indica que "A es un tipo covariante o positivo de List"
Si por ejemplo el tipo Perro es subtipo de Animal entonces el tipo List[Perro] también será subtipo del tipo List[Animal]
Es decir gracias al + lo siguiente compila:
val perros : List[Perro] = List(perro1, perro2)
val animales: List[Animal] = perros
(Sobre varianza)
Nothing es un subtipo de todos los tipos
Como List es covariante eso quiere decir que para cualquier tipo A, tenemos que Nil se puede considerar un List[A]
Nil sirve para describir la lista vacía de cualquier tipo
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
Cons(1, Cons(2, Cons(3, Nil) ) )
Contruyendo Listas
Una forma de construir la lista 1 -> 2 -> 3 es:
Podemos crear una función constructora que haga esto más fácil:
object List {
def apply[A](as: A*): List[A] =
if(as.isEmpty) {
Nil
} else {
Cons( as.head, apply(as.tail: _*) )
}
}
Multiples argumentos de tipo A
Primer argumento
Otros argumentos
(como algo de tipo Seq[A])
Convierte un Seq[A] en una lista de argumentos varíadicos
List.apply( 1, 2, 3)
Contruyendo Listas
En scala cuando un método se llama apply se puede invocar normalmente:
Pero también se puede invocar así:
List( 1, 2, 3)
Con esto hemos logrado construir listas encadenadas de una forma más sencila:
List( 1, 2, 3) == Cons(1, Cons(2, Cons(3, Nil) ) )
List() == Nil
Funciones de Orden Superior en listas

scala> val myList = List(1,2,3)
myList: List[Int] = List(1, 2, 3)
scala> myList.map(x => 10*x)
res1: List[Int] = List(10, 20, 30)
map
Funciones de Orden Superior en listas
filter

scala> val myList = List(2,30,22,5,60,1)
myList: List[Int] = List(2, 30, 22, 5, 60, 1)
scala> myList.filter(x => x>10)
res2: List[Int] = List(30, 22, 60)
Funciones de Orden Superior en listas
find

scala> myList.filter(x => x>10)
res2: List[Int] = List(30, 22, 60)
scala> myList.find(x => x>10)
res3: Option[Int] = Some(30)
Funciones de Orden Superior en listas
flatMap
scala> val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala> def g(v:Int) = List(v-1, v, v+1)
g: (v: Int)List[Int]
scala> list.map(x => g(x))
res0: List[List[Int]] = List(List(0, 1, 2), List(1, 2, 3),
List(2, 3, 4), List(3, 4, 5), List(4, 5, 6))
scala> list.flatMap(x => g(x))
res1: List[Int] = List(0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6)
¿Cómo funciona map?
List(3 , 2 , 4)
List("abc", "xy" , "pqrs").map({ x: String => x.length })
Cons("abc", Cons("xy" , Cons("pqrs", Nil) ) )
Cons( 3 , Cons( 2 , Cons( 4 , Nil) ) )
Desestructurando:
¡Podríamos implementar map usando pattern matching!
Ejercicio
Implementar filter
¿Cuál es el caso base?
¿Cuándo una lista es no vacía que debemos hacer?
Solución
trait List[+A] {
def filter(f: A => Boolean): List[A] = this match {
case Nil =>
Nil
case Cons(x,xs) =>
if(f(x)) Cons(x, xs.filter(f)) else xs.filter(f)
}
}
Solución
trait List[+A] {
def filter(f: A => Boolean): List[A] = this match {
case Nil =>
Nil
case Cons(x,xs) if f(x) =>
Cons(x, xs.filter(f))
case Cons(_,xs) =>
xs.filter(f)
}
}
Folds
¿Cómo combinar todos los elementos de una lista en uno solo?
def sum(list: List[Int]): Int = list match {
case Nil => 0
case Cons(x,xs) => x + sum(xs)
}
def product(list: List[Int]): Int = list match {
case Nil => 1
case Cons(x,xs) => x * product(xs)
}
def all(list: List[Boolean]): Boolean = list match {
case Nil => true
case Cons(x,xs) => x && all(xs)
}
Folds
El tipo del resultado puede ser distinto al tipo del contenido de la lista:
def totalLength(list: List[String]): Int = list match {
case Nil => 0
case Cons(x,xs) => x.length + totalLength(xs)
}
¿Qué varía? ¿Qué se mantiene?
foldRight
trait List[+A] {
def foldRight[B](z: B, f (A,B) => B): B =
this match {
case Nil => z
case Cons(x,xs) => f(x, xs.foldRight(z, f))
}
}
foldRight

List(a,b,c,...,x,y).foldRight(z,f)
==
f(a, f(b, f(c, ... f(x,f(y,z)) ... )
Ejercicio
Implementar sum, product, all y totalLength en términos de foldRight
Solución
def sum2(list: List[Int]): Int =
list.foldRight[Int](0, (a,b) => a + b )
def product2(list: List[Int]): Int =
list.foldRight[Int](1, (a,b) => a * b )
def all2(list: List[Boolean]): Boolean =
list.foldRight[Boolean](true, (a,b) => a && b )
def totalLength2(list: List[String]): Int =
list.foldRight[Int](0, (str,acc) => str.length + acc)
Programación Funcional Básica en Scala
By Miguel Vilá
Programación Funcional Básica en Scala
- 1,667