Tema 6: Programación funcional en Scala
Sesión 17: Introducción a Scala (1)
martes 5 de abril de 2011
Referencias
• Programming in Scala. Martin Odersky, Lex Spoon, Bil Venners. Ed. Artima.
• Programming Scala. Dean Wampler, Alex Payne. Ed. O'Reilly.
• Scala by Example. Martin Odersky. November 2010.
• A Scala Tutorial for Java programmers. Michel Schinz, Philipp Haller. November 2010.
• The Scala Language Specification. Martin Odersky. November 2010.
martes 5 de abril de 2011
El lenguaje de programación Scala
• Se desarrolla en 2001 en la universidad EPFL (Suiza) por un equipo dirigido por Martin Odersky
• El objetivo principal del lenguaje es integrar el paradigma funcional y el paradigma orientado a objetos
• Énfasis en la facilidad del desarrollo de componentes reusables
• Está basado en Java: los programas en Scala se compilan a bytecodes de Java y se ejecutan en la máquina virtual Java
• Sus características funcionales lo hacen muy apropiado para desarrollar programas muy eficientes en arquitecturas paralelas formadas por granjas de múltiples ordenadores (Twitter usa Scala)
• Sitio web: www.scala-lang.orgmartes 5 de abril de 2011
Unificación de PF y POO
• Programación funcional: hace fácil desarrollar cosas interesantes desde partes sencillas, usando:
• funciones de orden superior
• tipos algebraicos y pattern matching
• polimorfismo paramétrico
• Programación orientada a objetos: hace fácil adaptar y extender sistemas complejos, usando:
• especialización (subclases) y herencia
• configuraciones dinámicas
• clases como abstracciones parcialesmartes 5 de abril de 2011
Diseño de Scala
• Lenguaje moderno que satisface las necesidades más importantes de los desarrolladores actuales
• Completamente compatible con Java
• Modelo de objetos uniforme
• Pattern matching
• Funciones de orden superior
• Nuevas formas para abstraer y componer programas
martes 5 de abril de 2011
Características de Scala
• Estáticamente tipeado, con características de scripting porque en muchas ocasiones el compilador realiza una inferencia de tipos
• Interoperabilidad: encaja perfectamente en un entorno Java.
• Conciso// Javaclass MyClass{ private int index; private String name;
public MyClass(int index, String name){ this.index = index; this.name = name; }}
// Scalaclass MyClass(index: Int, name: String)
martes 5 de abril de 2011
Scala utiliza la PF
• Un par de ejemplos con funciones de orden superior:
//Javaboolean nameHasUpperCase = false;for (int i=0; i<name.length(); ++i){ if(Character.isUpperCase(name.charAt(i))){ nameHasUpperCase = true; break; }}
//Scalaval nameHasUpperCase = name.exists(_.isUpperCase)
martes 5 de abril de 2011
Scala utiliza la PF
scala> val matrix = Array(Array(1, 0, 0), Array(0, 1, 0), Array(0, 0, 1))matrix: Array[Array[Int]] = Array ([I@164da25,...scala> matrix.exists(row => row.forall(0 ==))res13: Boolean = false
martes 5 de abril de 2011
Scala es orientado a objetos
• A diferencia de Java o C++ en Scala no existen tipos primitivos que no son objetos
• Todas las expresiones se compilan a llamadas a métodos de objetos
• Veremos más detalles de POO en Scala en los últimos tema de la asignatura
scala> (1).hashCoderes8: Int = 1scala> (1).+(2)res10: Int = 3
martes 5 de abril de 2011
Scala como intérprete y compilador
• Scala permite ejecutar sus programas en modo scripting o en modo compilado
• Hasta ahora hemos usado el intérprete de Scala
• Cada línea de código es compilada y se genera bytecode que es ejecutado por la MV Java
martes 5 de abril de 2011
Scala como intérprete y compilador
• Se puede llamar a un script de Scala con el comando scala
• También es posible utilizar el compilador de Scala explícitamente, con el comando scalac
helloworld.scala:// Definimos un singleton: una clase con un único objetoobject HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }$ scalac helloworld.scala$ scala HelloWorld
println("Hola, "+ args(0) +"!")args.foreach(arg => println(arg))
martes 5 de abril de 2011
Expresiones y funciones simples en el intérprete
• El intérprete analiza las expresiones, realiza las inferencias de tipos necesarias y evalua la expresión:
scala> 5 + 2 * 3scala> 5 + 2.0 * 3scala> "hola" + " mundo!"
martes 5 de abril de 2011
Tema 6: Programación funcional en Scala
Sesión 18: Programación funcional en Scala
jueves 7 de abril de 2011
Referencias
• Programming in Scala. Martin Odersky, Lex Spoon, Bil Venners. Ed. Artima.
• Programming Scala. Dean Wampler, Alex Payne. Ed. O'Reilly.
• Scala by Example. Martin Odersky. November 2010.
• A Scala Tutorial for Java programmers. Michel Schinz, Philipp Haller. November 2010.
• The Scala Language Specification. Martin Odersky. November 2010.
jueves 7 de abril de 2011
Expresiones y funciones simples en el intérprete
• El intérprete analiza las expresiones, realiza las inferencias de tipos necesarias y evalua la expresión:
scala> 5 + 2 * 3scala> 5 + 2.0 * 3scala> "hola" + " mundo!"
jueves 7 de abril de 2011
Variables mutables e inmutables
• Scala diferencia entre variables inmutables (declaradas con val y variables mutables var)
• Es recomendable utilizar siempre que podamos variables inmutables (val), esto refuerza el carácter funcional de nuestros programas
• Las variables son estrictamente tipeadas, aunque no es necesario declarar el tipo porque Scala puede inferirlo
jueves 7 de abril de 2011
Variables mutables e inmutables
scala> val msg = "Hola mundo!"msg: java.lang.String = Hola mundo!scala> var saludo = "Hola mundo!"saludo: java.lang.String = Hola mundo!scala> saludo = "Hasta luego!"saludo: java.lang.String = Hasta luego!scala> saludo = 3.0<console>:5: error: type mismatch; found : Double(3.0) required: java.lang.String
jueves 7 de abril de 2011
Variables mutables e inmutables
• ¡Cuidado! El intérprete permite declarar varias veces la misma variable. Cada ejecución de una nueva sentencia crea un nuevo ámbito en el que se define la nueva variable. Las variables anteriores se pierden. Esto no es correcto en un programa; una variable sólo se puede declarar una vez.
• Las variables declaradas con val no pueden ser reasignadas. Esto no impide que cambie el valor del objeto que contienen (en el caso de estar en el paradigma imperativo). Ejemplo:
scala> val a = Array(1,0,0)a: Array[Int] = Array(1, 0, 0)scala> a(1) = 1scala> ares42: Array[Int] = Array(1, 1, 0)
jueves 7 de abril de 2011
Tipos de datos básicos
• Aunque hablemos de tipos de datos, en Scala todos los tipos de datos son clases
• Algunos tipos de datos y formas de inicializarlos:
• El API completo de Scala se puede consultar en http://www.scala-lang.org/api/current/index.html
Tipo de dato Rango Ejemplo
Byte 8-bit con signo 38Short 16-bit con signo 23Long 64-bit con signo 3434332
Int 32-bit con signo 70Char 16-bit sin signo A’Float 32 bit flotante con signo 1.234
Double 64 bit flotante con signo 1.234Boolean true o false trueString secuencia de caracteres “hola”
jueves 7 de abril de 2011
Operadores
• Aritméticos: + - = * %
• Relacionales: < <= > >= != ==
• Lógicos: && || !
jueves 7 de abril de 2011
def para dar nombre a expresiones
• def es una primitiva declarativa: le da un nombre a una expresión, pero no la evalua
• La evaluación se realiza cuando se llame al identificador
• En el caso de val o var la evaluación se realiza antes de hacer la asignación
def t = 8 / 0 --> No da errort --> al evaluar t, error división por cero
val t = 8 / 0 --> error
jueves 7 de abril de 2011
¿Cuándo se evalúan las variables en un def?
• Las variables en una expresión definida por def se evaluan cuando se invoca al identificador definido:
var x = 10def t = x / 5var x = 5t -> 1
jueves 7 de abril de 2011
def para definir funciones
• Sintaxis:
• Ejemplo:
def <nombre_funcion>(<parametro1:tipo1>,...):<tipo_resultado>={ <cuerpo de la función>}
def max(x: Int, y: Int): Int = { if (x > y) x else y}max: (Int,Int)Int
jueves 7 de abril de 2011
def para definir funciones
• Las llaves son opcionales si el cuerpo tiene una sóla sentencia
• El tipo de retorno es opcional si Scala puede inferirlo (no aplicable a funciones recursivas)
• La función se evalúa cuando se invoca:
• Los ejemplos anteriores en los que se definían expresiones se pueden interpretar de esta manera: como una definición de una función de una única sentencia (la expresión) que se evalua cuando se invoca
scala> def max2(x: Int, y: Int) = if (x > y) x else ymax2: (Int,Int)Int
scala> max2(3, 5)res6: Int = 5
jueves 7 de abril de 2011
Una función que no devuelve nada
• Por ahora todas las funciones que vamos a definir van a estar en el paradigma funcional, siempre devolverán algún valor
• Sin embargo Scala es también procedural:
• La función hola no devuelve ningún valor; la clase Unit es similar al tipo void de Java
scala> def hola() = println("Hola mundo")hola: ()Unit
jueves 7 de abril de 2011
Expresiones condicionales: if
• if: condicional; cuidado con el fin de línea si no utilizamos llaves
• Correcto:
• Incorrecto:
• Correcto:
def abs(x: Double) = if (x >= 0) x else -x
def abs(x: Double) = if (x >= 0) x else -x
def abs(x: Double) = if (x >= 0) x else -x
jueves 7 de abril de 2011
Expresiones if anidadas
def entre(x: Double, x1: Double, x2: Double) = if (x < x1) false else if (x > x2) false else true
jueves 7 de abril de 2011
Expresiones condicionales: match
• Similar al cond de Scheme
• El “default” se escribe como _
var myVar = 3;
myVar match { case 1 => "Uno" case 2 => "Dos" case 3 => "Tres" case 4 => "Cuatro"}
case _ => “otro caso”
jueves 7 de abril de 2011
Funciones recursivas
• El típico ejemplo de función recursiva: factorial en Scala
• Máximo común divisor recursivo:
def factorial(x: Long): Long = if (x == 0) 1 else x * factorial(x - 1)
def gcd(x: Long, y:Long): Long = if (y == 0) x else gcd(y, x % y)
jueves 7 de abril de 2011
Listas
• La clase List de Scala permite definir listas
• Al igual que Scheme son inmutables y tienen una estructura recursiva
• A diferencia de Scheme son estrictamente tipeadas
val fruit = List("apples", "oranges", "pears") val nums = List(1, 2, 3, 4) val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) val empty = List()
jueves 7 de abril de 2011
Operador cons
• Funciona igual que en Scheme, produce una nueva lista añadiendo un nuevo elemento a su cabeza
• Se define con 4 puntos ::
• La lista vacía se define con el identificador Nil
val dosTres = List(2, 3)val unDosTres = 1 :: dosTres
val unoDosTres = 1 :: 2 :: 3 :: Nilval fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) val nums = 1 :: (2 :: (3 :: (4 :: Nil))) val diag3 = (1::(0::(0::Nil))):: (0 :: (1 :: (0 :: Nil))) :: (0 :: (0 :: (1 :: Nil))) :: Nil val empty = Nilval nums = 1::2::3::4::Nil
jueves 7 de abril de 2011
Operaciones básicas sobre listas
• head: devuelve el primer elemento de la lista
• tail: devuelve el resto de la lista
• isEmpty: predicado que comprueba si la lista es vacía
• ::: operador similar a append que concatena dos listas
jueves 7 de abril de 2011
Funciones sobre listas
• Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme
• Inserción en una lista ordenada:
jueves 7 de abril de 2011
Funciones sobre listas
• Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme
• Inserción en una lista ordenada:
def insert(x: Int, lista: List[Int]) : List[Int] = if (lista.isEmpty) x :: Nil else if (x < lista.head) x :: lista else lista.head :: insert(x, lista.tail)
jueves 7 de abril de 2011
Tema 6: Programación funcional en Scala
Sesión 19: Programación funcional en Scala (3)
martes 12 de abril de 2011
Referencias
• Programming in Scala. Martin Odersky, Lex Spoon, Bil Venners. Ed. Artima.
• Programming Scala. Dean Wampler, Alex Payne. Ed. O'Reilly.
• Scala by Example. Martin Odersky. November 2010.
• A Scala Tutorial for Java programmers. Michel Schinz, Philipp Haller. November 2010.
• The Scala Language Specification. Martin Odersky. November 2010.
martes 12 de abril de 2011
Funciones sobre listas
• Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme
• Inserción en una lista ordenada:
• Ordenación de una lista:
martes 12 de abril de 2011
Funciones sobre listas
• Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme
• Inserción en una lista ordenada:
• Ordenación de una lista:
def insert(x: Int, lista: List[Int]) : List[Int] = if (lista.isEmpty) x :: Nil else if (x < lista.head) x :: lista else lista.head :: insert(x, lista.tail)
martes 12 de abril de 2011
Funciones sobre listas
• Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme
• Inserción en una lista ordenada:
• Ordenación de una lista:
def insert(x: Int, lista: List[Int]) : List[Int] = if (lista.isEmpty) x :: Nil else if (x < lista.head) x :: lista else lista.head :: insert(x, lista.tail)
def sort(lista: List[Int]): List[Int] = if (lista.isEmpty) Nil else insert(lista.head, sort(lista.tail))
martes 12 de abril de 2011
Funciones sobre listas
• Reverse-list:
martes 12 de abril de 2011
Funciones sobre listas
• Reverse-list:
def reverse(l: List[Int]) : List[Int] = if (l == Nil) l else reverse(l.tail) ::: List(l.head)
martes 12 de abril de 2011
Cadenas
• El tipo de Scala es String y se convierte en la clase de Java java.lang.String
• Al igual que las listas son inmutables
• Concatenación con el operador +
• Funciones head, tail, charAt
"hola" + "adios" --> "holaadios"
"hola".head --> h"hola".tail -->"ola""hola".charAt(0) --> 'h'
martes 12 de abril de 2011
Tuplas
• Scala permite construir tuplas de n elementos de distinto tipo
• Es un tipo también inmutable
• La forma de definir una tupla de n tipos es: (tipo1, tipo2,..., tipoN)
• Métodos de acceso al elemento de la tupla ._1, ._2, …
• Ejemplo con una tupla de tres elementos:
val miTupla = (99, "Hola", true)println(miTupla._1)println(miTupla._2)println(miTupla._3)
martes 12 de abril de 2011
Tuplas
• Muy útiles para funciones que tienen que devolver más de un elemento:
martes 12 de abril de 2011
Tuplas
• Muy útiles para funciones que tienen que devolver más de un elemento:
def sumaCadenas(s1: String, s2: String): (String, Int) = (s1+s2, s1.length+s2.length)
martes 12 de abril de 2011
Ámbito de variables
• Una vez definida una variable en un ámbito no podemos volver a definirla
• Sería un error el siguiente script:
• Sí es posible hacerlo en el intérprete, porque cada evaluación de una expresión crea una nuevo ámbito
val a = 1val a = 2 // errorprintln(a)
martes 12 de abril de 2011
Ámbito de variables
• Podemos crear un ámbito interior utilizando llaves:
• Podemos utilizar funciones locales que definen su propio ámbito:
val a = 1;{ val a = 2; println(a)}println(a)
def function1(x : Int) = { def function2(y: Int) = { x*y } function2(3)}function1(2)
martes 12 de abril de 2011
Funciones como objetos de primera clase
• En Scala las funciones son también objetos de primera clase
• Podemos:
• Definir variables y parámetros de tipo función
• Almacenar funciones en estructuras de datos como listas o arrays
• Construir funciones en tiempo de ejecución (closures) y devolverlas como valor de retorno de otra función
martes 12 de abril de 2011
Ejemplo: sumatorio
• La diferencia con Scheme es que Scala es un lenguaje tipeado y hay que definir el tipo de la función que se pasa como parámetro
• La función f que se pasa como primer parámetro de sum debe recibir un entero y devolver un entero
• Si pasamos otro tipo de función, Scala detecta el error de tipos
def square(x: Int): Int = x * x
def sum(f: Int => Int, a:Int, b:Int): Int = if (a>b) 0 else f(a) + sum(f, a+1, b)
martes 12 de abril de 2011
Funciones anónimas
• Al igual que en Scheme con la forma especial lambda, en Scala podemos definir funciones anónimas creadas en tiempo de ejecución
• La función square anterior se podría definir de forma anónima como:
• Podemos definirla como parámetro de sum y no hace falta definir el tipo de las variables, porque Scala lo infiere a partir del tipo del primer parámetro:
•
(x: Int) => x * x
sum(x => x * x, a, b)
martes 12 de abril de 2011
Funciones anónimas
• Una notación más concisa utiliza subrayados como huecos (placeholders) de los parámetros de la función:
• Es equivalente a:
sum(_+2, a, b)
sum(x => x+2, a, b)
martes 12 de abril de 2011
Variables de tipo función
• Al igual que en Scheme, podemos asignar funciones a variables:
• Para asignar una función ya definida a una variable hay que utilizar la sintaxis de huecos (placeholders) para indicar al compilador que no tiene que aplicar la función:
def suma3(a: Int, b: Int, c: Int) = a + b + cval f = suma3 _ f(1,2,3) --> 6
val f = (s:String) => s + “adios”f(“hola”)
martes 12 de abril de 2011
Variables de tipo función
• Podemos almacenar funciones en listas, pero todas las funciones deben tener el mismo perfil:
def suma3(a: Int, b: Int, c: Int) = a + b + c def mult3(a: Int, b: Int, c: Int) = a * b * c
val listaFuncs = List(suma3 _, mult3 _)
val f = listaFuncs.headf(1,2,3)
martes 12 de abril de 2011
Variables de tipo función
• Ejemplo de función que toma una lista de funciones de un argumento y las aplica:
def aplicaLista (lista: List[(Int) => Int], x: Int): Int = if (lista.length == 1) lista.head(x) else lista.head(aplicaLista(lista.tail,x))
def mas5(x: Int) = x+5def por8(x: Int) = x*8val l = List(mas5 _, por8 _)aplicaLista(l, 10)
martes 12 de abril de 2011
Variables de tipo función
• Es posible utilizar la sintaxis de los huecos para proporcionar algunos de los argumentos y dejar libres otros.
• Con la siguiente expresión definimos una variable de tipo función de un argumento a partir de una función anterior
val g = suma3(1, _: Int, 10)
• Es posible utilizar la sintaxis de los huecos para proporcionar algunos de los argumentos y dejar libres otros.
• Con la siguiente expresión definimos una variable de tipo función de un argumento a partir de una función anterior
martes 12 de abril de 2011
Devolviendo funciones anónimas
• Las funciones pueden crearse y ser devueltas por otras funciones
• ¿Cómo declararías el tipo devuelto por la función makeSumador(Int)?
def makeSumador(k: Int) = (x: Int) => x + kval f = makeSumador(10)val g = makeSumador(100)f(4)g(4)
martes 12 de abril de 2011
Closures
• Al igual que en Scheme, las funciones definidas en un ámbito mantienen el acceso a ese ámbito y pueden usar los valores allí definidos.
• Llamamos closure a estas funciones
• En el ejemplo siguiente, el ámbito en el que se crea la función devuelta contiene la variable i (el argumento de makeClosure) y la variable valor x declarada con el valor 5
def makeClosure(i : Int) : Int => Int = { val x = 5 (j : Int) => i + j + x}val returnedFuncion : Int => Int = makeClosure(10)println(returnedFuncion) //=> <function>println(returnedFunction(20)) //=> 35, porque 10 + 20 + 5 = 35
martes 12 de abril de 2011