1 LPP 2012-2013 Objetivos Ser capaz de comprender y escribir programas sencillos en Scala que utilicen las caractersticas de Programacin Funcional utilizadas en Scheme: programacin declarativa, valores inmutables, listas, funciones como objetos de primera clase, mbitos y clausuras Ser capaz de comprender y escribir programas sencilos en Scala que amplan las caractersticas de Programacin Funcional utilizadas en Scheme: variables y funciones tipeadas, denicin por comprensin de colecciones y currying 2 LPP 2012-2013 Referencias Martin Odersky: Programming in Scala Cay S. Horstmann: Scala for the impatient, Addison-Wesley Professional, 2012. Martin Odersky, A Postfunctional Language Mario Gleichman, Serie de posts Functional Scala 3 LPP 2012-2013 Programacin declarativa No hay pasos de ejecucin Valores inmutables No hay efectos laterales Recursin Listas Funciones como objetos de primera clase Funciones de orden superior Closures Caractersticas funcionales de Scala Caractersticas funcionales de Scheme y Scala Funciones y variables tipeadas Denicin por comprensin y pattern matching de listas y secuencias Currying Caractersticas funcionales de Scala Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data Denicin de programacin funcional en Scala in Action: 4 LPP 2012-2013 Caractersticas funcionales de Scala Variables y funciones tipeadas: funciones Como hemos visto en el seminario, Scala es un lenguaje strictamente tipeado: todas las variables, funciones y parmetros tienen un tipo denido, y el compilador detecta un error cuando usamos un valor de un tipo que no corresponde al tipo de la variable o del parmetro. Scala puede inferir el tipo devuelto por la funcin. Tuplas: las tuplas se denen entre parntesis con los tipos de sus elementos entre comas def max(x: Int, y: Int): Int = if (x > y) x else y (Int,Int) => Int Prototipo (rma) de la funcin: se declaran entre parntesis los tipos de los argumentos y despus de la echa el tipo del resultado de la funcin. Scala tambin utiliza esta notacin para denir funciones annimas (lo veremos ms adelante) 5 val t: (Int, String, Double) = (21, "Veintiuno", 21.0) LPP 2012-2013 Caractersticas funcionales de Scala Variables y funciones tipeadas: colecciones (Listas) Colecciones: las colecciones se denen en Scala como clases genricas en las que es necesario denir el tipo de los elementos contenidos. Para ello se debe denir entre corchetes el tipo de los valores contenidos en el dato compuesto. val miListaInt: List[Int] = List(1,2,3) val miListaString: List[String] = List("Hola","que","tal") 6 La clase List es una estructura inmutable a la que podemos aplicar mtodos como: !"# lista vaca $%&' devuelve el primer elemento de la lista (&"# devuelve el resto ")*+,(- comprueba si la lista es vaca .. devuelve una nueva lista aadiendo un elemento a su cabeza (/01) de Scheme) ... devuelve una nueva lista concatenando dos listas (&,,%1' de Scheme) val lista: List[Int] = List(1, 2, 3, 4) val primero = lista.head val resto = lista.tail val dobleLista = primero :: resto ::: lista LPP 2012-2013 Caractersticas funcionales de Scala Variables y funciones tipeadas: Qu es Any? El tipo 21- es el tipo (o clase) padre de todos los tipos de Scala, es similar a la clase 345%/( de Java. Cualquier objeto es de tipo 21-. Mtodos ")61)(&1/%37 y &)61)(&1/%37 comprueban si el valor almacenado en una variable de tipo 21- es de otro tipo y permiten recuperarlo como un objeto de ese tipo val s: String = "Hola" val cualquierTipo: Any = s al ser de tipo Any puede contener cualquier tipo de valor val listaCualquiera: List[Any] = List("Hola",1,"Adios",2) scala> val cualquierTipo: Any = "Hola" scala> cualquierTipo.isInstanceOf[String] res1: Boolean = true scala> cualquierTipo.asInstanceOf[String] res2: String = Hola 7 LPP 2012-2013 Caractersticas funcionales de Scala Denicin de funciones de forma declarativa En Scala utilizando el paradigma funcional no debemos utilizar bucles ni pasos de ejecucin. Al igual que hacamos en Scheme, la funcin debe quedar denida en una nica sentencia. Pero en esa sentencia pueden haber llamadas anidadas a otras funciones o sentencias adicionales: Permitimos denir variables locales inmutables al comienzo de las deniciones de las funciones: def max3(x: Int, y: Int, z: Int): Int = max(max(x,y),z) def max3(x: Int, y: Int, z: Int): Int = { val maxPrimeros = max(x,y) max(maxPrimeros,z) } 8 LPP 2012-2013 Caractersticas funcionales de Scala Valores inmutables Variables mutables e inmutables: en Scala se recomienda utilizar variables de tipo 8&# para reforzar el carcter funcional y evitar los efectos laterales en los programas. Una variable de tipo 8&# no puede ser modicada (re- asignada a otro objeto). Tipos mutables e inmutables: Independientemente del tipo de mutabilidad de las variables, en Scala tambin se diferencia entre tipos mutables y tipos inmutables. Las estructuras de datos inmutables no pueden ser modicadas una vez creadas. Los tipos mutables si pueden modicarse. Por ejemplo, la clase List es inmutable, mientras que la clase Array es mutable. val num = 1 num = num + 1 Error al intentar reasignar una variable inmutable inmutable var num = 1 num = num + 1 mutable no hay error val numbers = Array(1, 2, 3, 4) val num = numbers(3) numbers(3) = 100 val numCambiado = numbers(3) Almacena un objeto de tipo mutable variable inmutable 9 LPP 2012-2013 Caractersticas funcionales de Scala Uso de la recursin Nmero de elementos de una lista: Insercin en una lista ordenada: Ordenacin de una lista: Reverse-list: def numElems(lista:List[Int]):Int = if (lista.isEmpty) 0 else 1 + numElems(l.tail) def insert(x: Int, lista: List[Int]) : List[Int] = if (lista == Nil) 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)) def reverse(lista: List[Int]) : List[Int] = if (lista.isEmpty) Nil else reverse(lista.tail) ::: List(lista.head) 10 LPP 2012-2013 Caractersticas funcionales de Scala Funciones como objetos de primera clase En Scala las funciones tambin son objetos de primera clase Las funciones de orden superior son funciones que utilizan otras funciones como parmetros, tambin las tenemos en Scala. Se puede pasar como argumento a otra funcin Se puede devolver como resultado de otra funcin, construyndolas en tiempo de ejecucin (closures) Se puede ligar a variables Almacenar funciones en estructuras de datos como listas 11 LPP 2012-2013 Funciones como objetos de primera clase Creando funciones annimas (x: Int, y: Int) => {x + y} (lambda (x y) (+ x y)) Funcin annima en Scala Funcin annima en Scheme Espera dos argumentos de tipo Int Cuerpo de la funcin Dene un objeto funcin sin nombre Prototipo de la funcin: (Int, Int) => Int Se inere el tipo de retorno Podemos guardarla en una variable: val fun = (x: Int, y: Int) => x + y Podemos poner el tipo de la variable: val fun: (Int, Int) => Int = (x: Int, y: Int) => x + y Se pueden quitar las llaves al tener slo una sentencia en el cuerpo de la funcin Llamada a la funcin: fun(2,3) 5 12 LPP 2012-2013 Funciones como objetos de primera clase Sintaxis de los huecos o placeholders En Scala, para usar funciones como objetos de primera clase, debemos utilizar la sintaxis de huecos (placeholders) para indicar que no se tiene que evaluar la funcin. Tambin es posible utilizar la sintaxis de placeholders para dejar libres ciertos argumentos de la funcin y jar algunos otros. De esta forma podemos transformar una funcin en otra de menos argumentos. def suma3(a: Int, b: Int, c: Int) = a + b + c val f = suma3 _ f(1,2,3) -> 6 Esta sintaxis es necesaria para trabajar con funciones de primera clase, ya que debemos indicarle al compilador que no se tienen que evaluar hasta que no se utilicen val f : (Int) => Int = suma3(10, _: Int, 5) f(3) 18 Esta expresin guarda en la variable f una funcin de un nico parmetro construida a partir de la funcin )9+&: y jando el primer y ltimo argumento a los valores 10 y 5. La funcin guardada en la variable 7 slo tiene un argumento y devuelve el resultado de llamar a )9+&: pasando ese argumento y los valores a = 10 y c = 5 13 LPP 2012-2013 Funciones como objetos de primera clase Funciones como argumento sumatorio(f,a,b) = f(a)+sumatorio(f,a+1,b) sumatorio(f,a,b) = 0 si a > b Sumatorio: def sumatorio(a: Int, b:Int, f:(Int) => Int): Int = { if (a > b) 0 else f(a) + sumatorio(a+1,b,f) } Sumatorio en Scala: Recibe como arg una funcin f que recibe un Int y devuelve un Int Llamada a Sumatorio con una funcin annima: sumatorio(1, 10, (x:Int) => {x+3}) Funcin annima que suma 3 a su argumento sumatorio(1, 10, (x) => {x+3}) Podemos dejar que el compilador inera el tipo, sabe que es un Int por el tipo del tercer argumento de sumatorio: (Int) => Int sumatorio(1, 10, x => x+3 ) sumatorio(1, 10, _+3 ) Notacin ms concisa: eliminamos parntesis y llaves Equivalente con la sintaxis de los huecos: slo se puede usar cuando se recibe un argumento y la funcin opera sobre l Llamada a Sumatorio con una funcin ya creada: def suma3(x:Int) = x+3 sumatorio(1,10,suma3 _) 14 LPP 2012-2013 Funciones como objetos de primera clase Funciones como elementos de listas Al igual que en Scheme es posible guardar funciones en otras estructuras. Por ejemplo, podemos crear una lista de funciones, pero todas las funciones deben tener la misma signatura (prototipo o rma), que ser el tipo de los elementos de la lista: Ejemplo de funcin que toma una lista de funciones de un argumento y las aplica a un nmero de forma correlativa: 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[(Int,Int,Int)=>Int] = List(suma3 _, mult3 _, (x:Int,y:Int,z:Int)=>x+y*z) val f = listaFuncs.head f(1,2,3) Los elementos de la lista son funciones que reciben como argumentos 3 Int y devuelven un Int placeholders para que no se evalen las funciones 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+5 def por8(x: Int) = x*8 val l = List(mas5 _, por8 _, suma3(1, _: Int, 10)) aplicaLista(l, 10) se crea una funcin de un argumento a partir de la funcin de 3 argumentos )9+&: utilizando la sintaxis de huecos: se da valor jo al primer y ltimo argumento, y el segundo se deja pendiente con un placeholder 15 LPP 2012-2013 Funciones como objetos de primera clase Funciones como valores devueltos Las funciones pueden crearse en la invocacin de otras funciones y ser devueltas como resultado. Por ejemplo, la siguiente funcin construye y devuelve un sumador que suma el nmero que pasamos como parmetro. La funcin devuelta es una clausura. def makeSumador(k: Int) = { (x: Int) => x + k } val f: (Int) => Int = makeSumador(10) val g: (Int) => Int = makeSumador(100) f(4) g(4) La funcin +&;%<9+&'0= devuelve una funcin, por lo que podramos haber declarado su tipo retorno como >61(? AB 61(C hemos dejado que el compilador lo inera def makeSumador(k: Int): (Int) => Int = { (x: Int) => x + k } El tipo de las variables (funcin que recibe un Int y devuelve un Int) tambin podra haberlo inferido el compilador, pero lo indicamos por claridad 16 LPP 2012-2013 mbitos: funcionamiento igual que en Scheme Una invocacin a una funcin crea un nuevo mbito en el que se evala el cuerpo de la funcin. Lo llamamos mbito de evaluacin de la funcin El mbito de evaluacin se crea dentro del mbito en el que se cre la funcin (deep binding) Los argumentos de la funcin son variables locales de este nuevo mbito que quedan ligadas a los parmetros que se utilizan en la llamada En el nuevo mbito se pueden denir otras variables locales En el nuevo mbito se pueden acceder al valor de las variables del mbito padre 17 LPP 2012-2013 mbitos en Scala 1. En el mbito principal del intrprete se dene el valor de las variables x (10) e y (20), as como la funcin prueba. 2. La ejecucin de prueba(3) crea un nuevo mbito de evaluacin, dentro del mbito principal, en el que se ejecuta el cuerpo de la funcin. 3. En el mbito de evaluacin se liga el valor de z (argumento de prueba) con el valor 3 (parmetro con el que se realiza la llamada). 4. En este nuevo mbito se ejecuta el cuerpo de la funcin: se crea la variable local x con el valor 0 y se evala la expresin x+y+z. El valor de x y z estn denidos en el propio mbito local (0 y 3). El valor de y se obtiene del entorno padre: 20. El resultado es 23. val x = 10 val y = 20 def prueba(z: Int): Int = { val x = 0 x+y+z } prueba(3) 23 x: 10 y: 20 z: 3 x: 0 x + y + z --> 23 mbito evaluacin ,=9%4& 18 LPP 2012-2013 mbitos en Scala En primer lugar se realizan las invocaciones a g. Cada una crea un mbito local en el que se evala la funcin. Las invocaciones devuelven 13 y 15 respectivamente. Despus se realiza la invocacin a f con esos valores 13 y 15. Esta invocacin vuelve a crear un mbito local en el que se evala la expresin + +y+z, devolviendo 33. 19 def f(x: Int, y: Int): Int = { val z = 5 x+y+z } def g(z: Int): Int = { val x = 10 z+x } f(g(3),g(5)) LPP 2012-2013 mbitos en Scala Ejemplo con variables locales y globales En el mbito principal del intrprete se dene el valor de las variables x (10) e y (20), as como la funcin prueba. La ejecucin de prueba(3) crea un nuevo mbito de evaluacin, dentro del mbito principal, en el que se ejecuta el cuerpo de la funcin. En el mbito de evaluacin se liga el valor de z (argumento de prueba) con el valor 3 (parmetro con el que se realiza la llamada). En este nuevo mbito se ejecuta el cuerpo de la funcin: se crea la variable local x con el valor 0 y se evala la expresin x+y+z. El valor de x y z estn denidos en el propio mbito local (0 y 3). El valor de y se obtiene del entorno padre: 20. El resultado es 23. 20 val x = 10 val y = 20 def g(y: Int): Int = { x+y } def prueba(z: Int): Int = { val x = 0 g(x+y+z) } prueba(3) LPP 2012-2013 Clausuras en Scala Ejemplos Cambiamos la denicin de ,=9%4& haciendo que tome como parmetro la variable - y que devuelva una funci n anni ma construi da en tiempo de ejecucin: la funcin >D.61(? AB EFG-GDH que tiene como parmetro D y como variables libres F e -. La funcin devuelta es una clausura que se crear en al mbito de evaluacin de ,=9%4& y quedar asociada a ese mbito en el momento en que se ejecute ,=9%4&. val x = 10 val y = 20 def prueba(y: Int): (Int)=>Int = { val x = 0 (z:Int) => {x+y+z} } val f = prueba(2) f(3) x: 10 y: 20 y: 2 x: 0 mbito evaluacin ,=9%4& arg: z body: x+y+z f: mbito evaluacin 7 z: 3 x+y+z --> 5 5 21 LPP 2012-2013 Clausuras en Scala Ejemplos def makeSumador(k: Int): (Int)=>Int = (x: Int) => x + k val f = makeSumador(10) val g = makeSumador(100) f(4) g(4) k: 10 mbito evaluacin +&;%<9+&'0= arg: x body: x+k f: mbito evaluacin 7 x: 4 x+k --> 14 k: 100 mbito evaluacin +&;%<9+&'0= arg: x body: x+k g: mbito evaluacin I x: 4 x+k --> 104 22 LPP 2012-2013 Clausuras en Scala Ejemplos La llamada a 791/J hace que se evalue su cuerpo. En l se crea la variable F con el valor 10 y la expresin f=func1 () asocia f con la clausura devuelta por func1. Esta clausura es ()=>{x+1} creada en el mbito de evaluacin de func1. En ese mismo mbito se ha creado tambin la variable x con el valor 1. La llamada a f en el cdigo equivalente hace que la funcin se evalue. Es en este momento cuando el resultado cambia en funcin de si el lenguaje funciona con deep binding o shallow binding: Si el lenguaje funciona con deep binding el mbito de evaluacin de f se crea en el mbito de evaluacin de func1, en el que x vale 1. El resultado de la llamada sera 2. Si el lenguaje funciona con shallow binding el mbito de evaluacin de f se crea en el propio mbito en el que se realiza la llamada, en el que x vale 10. El resultado de la llamada sera 11. Si ejectuamos el cdigo veremos que devuelve 2. Por lo que estamos en el segundo caso. Scala usa deep binding. val x = 100 def func1(): () => Int = { val x=1 () => {x+1} } def func2(): Int= { val x=10 val f=func1() f() } func2() (define x 100) (define (func1) (let ((x 1)) (lambda () (+ x 1)))) (define (func2) (let ((x 10)) ((func1)))) (func2) En Scala y Scheme, que usan deep binding, se devolver 2. Los lenguajes que usen shallow binding se devolver 11. 23 LPP 2012-2013 Funciones de orden superior La denicin de funciones como un tipo de dato primitivo permite denir funciones de orden superior que toman como parmetro otras funciones y permiten hacer mucho ms conciso y general el cdigo escrito en Scala. Ejemplos de mtodos de orden superior de la clase K")(: /091( LLB cuenta el nmero de elementos de la lista que satisfacen un predicado %F")() LLB comprueba si algn elemento de la lista satisface un predicado 7"#(%= --> devuelve una nueva lista con los elementos de la lista original que cumplen el predicado +&, --> aplica una funcin a todos los elementos de la lista, devolviendo una nueva lista con el resultado Al ser mtodos y no funciones s que se pueden denir como genricos y aplicar a listas de distintos tipos. El tipo de la funcin que se pasa como argumento tiene que corresponder con el tipo de los elementos de la lista 24 LPP 2012-2013 Funciones de orden superior /091(C 7"#(%=C +&, El tipo de la funcin que se pasa como argumento a las funciones de orden superior es genrico y tiene que corresponder al tipo de los elementos de la lista. Distintas formas (equivalentes) de escribir la funcin que pasamos como argumento: val lista = List(1,2,3,4,5,6,7,8,9) lista.count((x:Int) => {x % 2 == 0}) lista.count((x) => {x % 2 == 0}) lista.count(x => x % 2 == 0) lista.count(_ % 2 == 0) def par(x:Int): Boolean = {x % 2 == 0} lista.count(par _) 4 lista.filter(par _) def cuadrado(x:Int) = x*x lista.map(cuadrado _) List("No","es","elegante","escribir","con","mayusculas").map(s => s.toUpperCase) List(2, 4, 6, 8) List(1, 4, 9, 16, 25, 36, 49, 64, 81) List(NO, ES, ELEGANTE, ESCRIBIR, CON, MAYUSCULAS) 25 LPP 2012-2013 Funciones de orden superior Implementacin recursiva Funcin +"M091(, que cuenta el nmero de elementos de una lista de tipo 61( que cumplen un predicado: Funcin +"N&,, que aplica una funcin a todos los elementos de una lista: def miCount(lista:List[Int], p:(Int)=>Boolean): Int = { if (lista == Nil) 0 else if (p(lista.head)) 1 + miCount(lista.tail,p) else miCount(lista.tail,p) } def miMap(lista:List[Int], f:(Int)=>Int): List[Int] = { if (lista == Nil) Nil else f(lista.head) :: miMap(lista.tail,f) } 26 LPP 2012-2013 Funciones genricas En Scala tambin es posible denir funciones con tipos de datos genricos. Por ejemplo, la funcin 7"#(%= utiliza el tipo genrico A como el tipo de los elementos de la lista y el tipo del predicado que se aplica a los elementos. El compilador determina el tipo A cuando se invoca a la funcin con una lista y un predicado concreto. 27 def filter[A](lista: List[A], pred: (A)=>Boolean): List[A] = { if (lista.isEmpty) Nil else if (pred (lista.head)) lista.head :: filter(lista.tail, pred) else filter(lista.tail, pred) } def par(x: Int) = x % 2 == 0 filter(List(1,2,3,4), par _) // List[Int] = List(2, 4, 6) filter(List("hola","amigo","adios"),(s: String)=>{s.head=='a'}) // List[java.lang.String] = List(amigo, adios) LPP 2012-2013 Funciones genricas 19+*#%+): =%8%=)%: "1)%=(: )0=(: 28 def numElems[A](lista:List[A]):Int = if (lista.isEmpty) 0 else 1 + numElems(lista.tail) def reverse[A](lista: List[A]) : List[A] = if (lista.isEmpty) Nil else reverse(lista.tail) ::: List(lista.head) def insert[A](x: A, lista: List[A], menor: (A,A) => Boolean) : List[A] = if (lista == Nil) x :: Nil else if (menor(x,lista.head)) x :: lista else lista.head :: insert(x, lista.tail, menor) def sort[A](lista: List[A], menor: (A,A) => Boolean): List[A] = if (lista.isEmpty) Nil else insert(lista.head, sort(lista.tail, menor), menor) LPP 2012-2013 Denicin por comprensin de colecciones 70=OOO-"%#' Mat emt i cament e, una deni ci n por comprensin de un conjunto se realiza con una denicin lgica de una propiedad que deben satisfacer sus elementos. Muchos lenguajes de programacin funcional, como Scala, permiten denir colecciones utilizando estas propiedades. Las colecciones tienen que tener un nmero nito de elementos. Denicin: { x! | x ! N : x ! {1..5} } conjunto los cinco primeros nmeros naturales elevados al cuadrado propiedad for (x <- (1 to 5)) yield x*x devuelve un rango de nmeros del 1 al 5 (una coleccin) aplica la funcin denida tras el -"%#' a todos los elementos del rango y devuelve una coleccin del mismo tipo que el dominio de entrada transformada con la expresin for (x <- List.range(1,6)) yield x*x devuelve una lista con los 5 primeros nmeros elevados al cuadrado el 6 no est incluido for (i <- List.range(1, 101) if i % 2 != 0) yield i*i devuelve el cuadrado de los nmeros impares del 1 al 100 condicin que deben cumplir los nmeros: tienen que ser impares for (i <- List.range(1, 101) if (i % 2 != 0 && i % 3 == 0)) yield i*i devuelve el cuadrado de los nmeros impares divisibles por 3 for(x <- (1 to 3 ); y <- (1 to x)) yield (x,y) dos generadores: El primero (1 to 3) dene los primeros nmeros de las parejas y el segundo (1 to x) utiliza la variable del primer generador como tope para obtener los valores de los segundos nmeros (1,1), (2,1), (2,2), (3,1), (3,2), (3,3) 29 LPP 2012-2013 Currying Currying es el proceso de convertir una funcin que toma dos argumentos en una funcin que toma un argumento y devuelve una funcin que recibe el segundo argumento. En Scala existe una forma abreviada de denir currying: def mulUnoCadaVez(x: Int) = (y: Int) => x * y funcin toma un argumento, devolviendo una funcin que toma un argumento mulUnoCadaVez(6)(7) llamada para multiplicar dos nmeros el resultado de mulUnoCadaVez(6) es la clausura (y: Int)=>x*y con el valor x asociado a 6. Esta funcin se aplica a 7, devolviendo 42. def mulUnoCadaVez(x: Int)(y: Int) = x * y; la funcin mulUnoCadaVez toma un argumento y devuelve una funcin que toma un argumento 30