Código fuente de los ejercicios en
// 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
}
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!")
}
¿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
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
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.
Reusando la pila de ejecución:
def fact(n: Int, acc: Int): Int =
if(n <= 1)
acc
else
fact(n-1, n*acc)
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:
¿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
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)
}
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
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
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))
}
def formatFact(x: Int): String = {
val msg = "The factorial of %d is %d"
msg.format(x, fact(x))
}
¿Cómo podemos generalizar esto?
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))
}
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)
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)
}
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?
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:
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
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)
}
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
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"
}
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"
}
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
Tomada de "Functional Programming in Scala"
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
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) ) )
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)
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
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)
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)
scala> myList.filter(x => x>10)
res2: List[Int] = List(30, 22, 60)
scala> myList.find(x => x>10)
res3: Option[Int] = Some(30)
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)
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!
Implementar filter
¿Cuál es el caso base?
¿Cuándo una lista es no vacía que debemos hacer?
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)
}
}
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)
}
}
¿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)
}
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?
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))
}
}
List(a,b,c,...,x,y).foldRight(z,f)
==
f(a, f(b, f(c, ... f(x,f(y,z)) ... )
Implementar sum, product, all y totalLength en términos de foldRight
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)