Programación funcional en Scala (3)

Table of Contents

Referencias

Funciones con listas

  • Con las operaciones previas se pueden definir funciones similares a las que vimos en Scheme





  • Número de elementos de una lista:
    def numElems(l:List[Int]):Int = 
          if (l == Nil) 0 else 1 + numElems(l.tail)
    






  • 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)
    






  • Ordenación de una lista:
    def sort(lista: List[Int]): List[Int] =
     if (lista.isEmpty) Nil else insert(lista.head, sort(lista.tail))
    






  • Reverse-list
    def reverse(l: List[Int]) : List[Int] =   
          if (l == Nil) l else reverse(l.tail) ::: List(l.head)
    








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 +
    "hola" + "adios" --> "holaadios"
    
  • Funciones head, tail, charAt
    "hola".head --> h
    "hola".tail -->"ola"
    "hola".charAt(0) --> 'h'
    







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 trio = (99, "Hola", true)
    println(trio._1)
    println(trio._2)
    println(trio._3)
    
  • Muy útil 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)
    








Ámbito de variables

  • Una vez definida una variable en un ámbito no podemos volver a definirla
  • Sería un error el siguiente script:
    val a = 1
    val a = 2 // error
    println(a)
    
  • Sí es posible hacerlo en el intérprete, porque cada evaluación de una expresión crea una nuevo ámbito
  • Podemos crear un ámbito interior utilizando llaves:
    val a = 1
    {
      val a = 2
      println(a)
    }
    println(a)
    
  • Podemos crear funciones locales que definen su propio ámbito:
    def function1(x : Int) = {
        def function2(y: Int) =  {
            x*y
        }
        function2(3)
    }
    function1(2)
    







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
  • Las funciones de orden superior son funciones que utilizan otras funciones como parámetros








Ejemplo sumatorio

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)
  • 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







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:
    (x: Int) => x * x
    
  • 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:
    sum(x => x * x, a, b)
    
  • Una notación más concisa utiliza subrayados como huecos (placeholders) de los parámetros de la función
    sum(_+2, a, b)
    

es equivalente a:

sum(x => x+2, a, b)








Variables de tipo función

  • Al igual que en Scheme podemos asignar funciones a variables:
    val f = (s: String) => s + "adios"
    f("hola")
    
  • 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 + c
    val f = suma3 _ 
    f(1,2,3) -> 6
    
  • 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.head
    f(1,2,3)
    
  • 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+5
    def por8(x: Int) = x*8
    val l = List(mas5 _, por8 _)
    aplicaLista(l, 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 función de un argumento a partir de una función anterior
    val g = suma3(1, _: Int, 10) 
    








Devolviendo funciones anónimas

  • Las funciones pueden crearse y ser devueltas por otras funciones
    def makeSumador(k: Int) = (x: Int) => x + k
    val f = makeSumador(10)
    val g = makeSumador(100)
    f(4)
    g(4)
    
  • ¿Cómo declararías el tipo devuelto por la función makeSumador(Int)?








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
    
  • ¿Qué pasa si definimos x como var? ¿Podríamos modificarla? ¿Afectaría a la función devuelta? Lo veremos cuando hablemos de estado local.









Lenguajes y Paradigmas de Programación
Curso 2010-2011
Departamento de Ciencia de la Computación e Inteligencia Artificial
Universidad de Alicante

Sitio web realizado con org-mode y el estilo CSS del proyecto Worg
Validate XHTML 1.0