Skip to content
FUNCIONES

Programación funcional

Funciones anónimas

Funciónes anónimas o lambdas

  • Función o subrutina definida y (posiblemente) llamada sin necesidad de asociarla a un identificador o nombre
  • Se suelen pasar como argumento a funciones de orden superior
  • Son funciones anidadas que permiten acceder a variables definidas en el ámbito de la contenedora (variables no locales a la función anónima)
  • Muchos lenguajes las introducen a través de la palabra reservada lambda

Expresión lambda

Una expresión lambda es una función anónima (con o sin parámetros) que es llamada sin necesidad de asociarle un nombre explícito. Sirven para pasarlas como argumento a funciones de orden superior, momento en el cual los parámetros de la función anónima toman un valor en el contexto de ejecución de la función contenedora que la recibe y ejecuta. Por tanto, las funciones anónimas permiten acceder a variables (no locales) definidas en el ámbito de la contenedora.

Lambdas en los lenguajes

Mecanismos de los lenguajes para implementar funciones anónimas:

Java

  ( argumentos ) -> expresión
  (int x, int y) -> x + y
  () -> 42
  (String s) -> { System.out.println(s); }

Ruby

  { | argumentos | expresión }
  ->(argumentos) { expresión }
  [1, 2, 3].each { |num| puts num * 2 }
  # 2 4 6

  times_two = ->(x) { x * 2 }
  times_two.call(10)
  # 20

C++

[capture](parameters) -> return_type { function_body }
Capture Descripción
[] Sin variables externas definidas. Intentar utilizar cualquier variable externa es un error
[x, &y] x se captura por valor, y por referencia
[&] Todas las variables externas que sean utilizadas son capturadas implícitamente por referencia
[=] Todas las variable externas que sean utilizadas son capturadas implícitamente por valor
[&, x] x se captura explícitamente por valor; el resto, por referencia
[=, &z] z se captura explícitamente por referencia; el resto, por valor

Cierres de funciones o closures

  • Cierre: Función o referencia a función junto con un entorno de referencia
    • La diferencia entre una función normal y un cierre es que un cierre depende de una o varias variables libres.
    • Un cierre permite acceder a las variables libres incluso cuando se invoca desde fuera de su ámbito léxico
  • Entorno de referencia: tabla que guarda una referencia a cada una de las variables no locales (libres) de la función
    • Variable libre (free): notación lógica matemática que especifica los lugares de una expresión donde tiene lugar una sustitución
    • Variable asignada (bound): variable que era libre previamente pero a la que le ha sido asignado un valor o conjunto de valores

Closures en Scala

Lambdas y cierres en C++

std::vector<int> some_list; // assume that contains something
int total = 0;
for (int i=0;i<5;++i) some_list.push_back(i);
std::for_each(
  begin(some_list),
  end(some_list),
  [&total](int x) { total += x; }
);
// Computes the total of all elements in the list.
/* Variable total is stored as a part of the lambda function's closure.
   Since it is a reference to the stack variable total, it can change
   its value. */
  • Un cierre en C++ se expresa mediante la parte [capture]
  • El entorno de referencia se expresa por el conjunto de variables externas indicadas dentro del cierre
  • Las variables del entorno de referencia en C++ pueden ser capturadas por valor ([=]) o por referencia ([&])
  • Las variables externas capturadas son inmutables por defecto; si no, usar mutable después de los (parameters)

Lectura: Lambdas en C++

Lecturas recomendadas

Funciones anónimas en Java

public class ComparatorTest { 
  public static void main(String[] args) {

    List<Person> personList = Person.createShortList();

    Collections.sort(personList, new Comparator<Person>(){
      public int compare(Person p1, Person p2){
        return p1.getLastname().compareTo(p2.getLastname());
      }
    });

    System.out.println("=== Sorted Asc Lastname ===");
    for(Person p:personList){
      p.printName();
    }

    Collections.sort(personList, new Comparator<Person>(){
      public int compare(Person p1, Person p2){
        return p2.getLastname().compareTo(p1.getLastname());
      }
    });

    System.out.println("=== Sorted Desc Lastname ===");
    for(Person p:personList){
      p.printName();
    }


  }
}
public class ComparatorTest { 
  public static void main(String[] args) {

    List<Person> personList = Person.createShortList();

    // Print Asc
    System.out.println("=== Sorted Asc Lastname ===");
    Collections.sort(personList, (Person p1, Person p2) ->
      p1.getLastname().compareTo(p2.getLastname()));

    for(Person p:personList){
      p.printName();
    }

    // Print Desc
    System.out.println("=== Sorted Desc Lastname ===");
    Collections.sort(personList, (p1,  p2) ->
      p2.getLastname().compareTo(p1.getLastname()));

    for(Person p:personList){
      p.printName();
    }

  }
}

Captura de variables en lambdas

Una expresión lambda en Java puede capturar (o no) variables de instancia no locales (atributos de la clase contenedora) y variables locales (declaradas o no final, pero cuyo valor no es modificado) del ámbito contenedor.

  • Ejemplo: Lambda que captura variables locales final:

    final BigDecimal bd = new BigDecimal(1);
    final BigDecimal x = new BigDecimal(2);
    Function<BigDecimal, BigDecimal> func = (a) -> bd.multiply(a).add(x);
    
    for (int j = 0; j < 999999999; j++) {
          func.apply(new BigDecimal(j));
    }
    

  • Ejemplo: Lambda que captura variables locales no declaradas final pero cuyo valor no es modificado:

    BigDecimal bd = new BigDecimal(1);
    BigDecimal x = new BigDecimal(2);
     // Se puede consultar x pero no se podría cambiar el valor de x:
    Function<BigDecimal, BigDecimal> func = (a) -> { bd.multiply(a).add(x); /* x = new BigDecimal(0); */ };
    
    for (int j = 0; j < 999999999; j++) {
          func.apply(new BigDecimal(j));
    }
    

  • Ejemplo: Lambda que captura variables de instancia de la clase contenedora:

    public class LambdaInstanceCapturing implements Runnable {
        private BigDecimal bd = new BigDecimal(1);
    
        @Override
        public void run() {
            Function<BigDecimal, BigDecimal> func = (a) -> bd.multiply(a);
    
            for (int j = 0; j < 999999999; j++) {
                func.apply(new BigDecimal(j));
            }
        }
    }
    

Lambdas y clases anónimas internas

En Java, una expresión lambda y una clase anónima interna (inner class) tienen un propósito similar, pero son diferentes en un aspecto: el ámbito (scope) de la definición de las variables locales.

Cuando se usa una inner class, se crea un nuevo ámbito para dicha clase. Se pueden ocultar las variables locales para el ámbito contenedor instanciando nuevas variables con el mismo nombre. También se puede usar la palabra reservada this dentro de una clase anónima para hacer referencia a su instancia.

Sin embargo, las expresiones lambda trabajan con el ámbito contenedor. No se pueden ocultar las variables del ámbito contenedor dentro del cuerpo de la expresión lambda. En tal caso, la palabra reservada this hace referencia a una instancia de la clase contenedora.

En el ejemplo siguiente, ¿qué valor devuelve scopeExperiment()?:

@FunctionalInterface
public interface ClaseFuncional{
  String method(String string);
}

private String variable = "Valor de la contenedora";

//...

public String scopeExperiment() {

  ClaseFuncional unaInnerClass = new ClaseFuncional() {
      String variable = "Valor de la inner class";
      @Override
      public String method(String string) {
          return this.variable;
            /*  Con o sin this, no hay lambdas ni variables libres */
      }
  };
  String resultadoInnerClass = unaInnerClass.method("");

  ClaseFuncional unaLambda = parametro -> {
      String variable= "Valor de la lambda";
      return this.variable; 
        /* Con this, el cierre de la variable libre se produce
           con el valor de ClaseFuncional::variable */
  };
  String resultadoLambda = unaLambda.method("");

  return "resultadoInnerClass = " + resultadoInnerClass +
    ",\nresultadoLambda = " + resultadoLambda;
}

El valor será:

resultadoInnerClass  = Valor de la inner class,
resultadoLambda = Valor de la contenedora

Funciones anónimas en Ruby

Bloques (blocks)

some_list = [ 10, 20, 30 ]
some_list.map do |i|
    i += 1
end
some_list = [ 10, 20, 30 ]
some_list.map { |i| i += 1 }

El método map itera y aplica un bloque repetitivamente a cada elemento de una colección (representado por el parámetro i)

Ejemplo: búsqueda en una lista
class SongList
  def with_title(title)
    for i in 0...@songs.length
      return @songs[i] if title == @songs[i].name
    end
    return nil
  end
end
class SongList
  def with_title(title)
    @songs.find do |song|
      title == song.name
    end
  end
end
class SongList
  def with_title(title)
    @songs.find { |song| title == song.name }
  end
end

El método find itera y aplica el test del bloque a cada elemento song de la colección.

Ejecución de bloques
  • El bloque debe aparecer al lado de una llamada a método
  • No se ejecuta el bloque, sino que se recuerda el contexto (variables locales, objeto actual, etc.) en que aparece
  • Cuando se ejecuta el método, el bloque es invocado donde aparezca yield
  • El control vuelve al método después del yield
  • Al bloque se le pueden pasar parámetros

Ejemplo: fibonacci

def fib_up_to(max)
  i1, i2 = 1, 1
  while i1 <= max
    yield i1
    i1, i2 = i2, i1+i2
  end
end
fib_up_to(1000) {|f| print f, " " }

#Salida => 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Ejemplo de yield:

def three_times
  yield
  yield
  yield
end
three_times { puts "Hello" }

Ejemplo: implementación de Array.find

class Array
  def find
    for i in 0...size
      value = self[i]
      return value if yield(value)
    end
    return nil
  end
end

Ejemplos: iterar con bloques

  • Iterar sobre un array con each

Array#each: recibe un array y aplica el bloque a cada item, sin modificar el array ni crear un nuevo objeto; devuelve el mismo array.

[ 1, 3, 5, 7, 9 ].each {|i| printf i, " " }
#Salida => 1 3 5 7 9
Array a = [ 1, 2, 3, 4 ]
a.each {|i| puts i*2 }
#Salida => 2 4 6 8
#Devuelve => [1, 2, 3, 4]
a
#Devuelve => [1, 2, 3, 4]
  • Iterar sobre un fichero con each

File#each: recibe el contenido de un fichero de texto y aplica el bloque a cada línea.

f = File.open("testfile")
f.each do |line|
  puts line
end
f.close
f = File.open("testfile")
f.each {|line| puts line}
f.close
  • Iterar sobre un array con collect

Array#collect: aplica el bloque a todos los items y devuelve el nuevo array modificado; hace lo mismo que Array#map

["H", "A", "L"].collect {|x| x.succ }
# Salida => [''I'', ''B'', ''M'']
Array a = [ 1, 2, 3, 4 ]
a.collect {|i| puts i*2}
#Salida => 2 4 6 8
#Devuelve => [nil, nil, nil, nil]
a.collect {|i| i.succ}
#Devuelve => [2, 3, 4, 5]
a
#Devuelve => [1, 2, 3, 4]

Procs y lambdas

  • En Ruby, una función anónima o lambda es simplemente un tipo especial de objeto Proc
  • Definición de procs/lambdas:
# sin argumentos:
say_something = -> { puts "This is a lambda" }
say_something = lambda { puts "This is a lambda" }
say_otherwise = Proc.new { puts "This is a proc" }
# con argumentos:
times_two = ->(x) { x * 2 }
  • Varias formas de llamar a la lambda (es preferible call)
say_something = -> { puts "This is a lambda" }
say_something.call
say_something.()
say_something[]

say_otherwise = Proc.new { puts "This is a proc" }
say_otherwise.call

times_two = ->(x) { x * 2 }
times_two.call(10)
  • Los proc no se preocupan de los argumentos:
t = Proc.new { |x,y| puts "I don't care about args!" }
t.call #Salida: I don't care about args!
t.call(10) #Salida: I don't care about args!
t.call(10,10) #Salida: I don't care about args!
t.call(10,10) #Salida: I don't care about args!

s = ->(x,y) { puts "I care about args" }
s.call # ArgumentError: wrong number of arguments (given 0, expected 2)
s.call(10) # ArgumentError: wrong number of arguments (given 1, expected 2)
s.call(10,10) # Salida: I care about args
  • Los proc retornan del método actual; los lambda retornan de la función anónima:
# funciona:
my_lambda = -> { return 1 }
puts "Lambda result: #{my_lambda.call}"

# eleva una exceción:
my_proc = Proc.new { return 1 }
puts "Proc result: #{my_proc.call}"
  • Si el proc está dentro de un método, la llamada a return es equivalente a retornar de ese método:
def call_proc
  puts "Before proc"
  my_proc = Proc.new { return 2 }
  my_proc.call
  puts "After proc"
end

puts call_proc
# Prints "Before proc" but not "After proc"

def call_lambda
  puts "Before lambda"
  my_lambda = lambda { return 2 }
  my_lambda.call
  puts "After lambda"
end

puts call_lambda
# Prints "Before lambda" and "After lambda"

Diferencias entre Proc y lambda:

  • Las lambdas se definen con -> {} y los procs con Proc.new {}
  • Los Proc retornan del método actual, las lambdas retornan de la propia función lambda
  • Los Proc no se preocupan del número correcto de argumentos, las lambdas elevan una excepción

Paso de bloques como parámetros

  • Simplemente, se añade al final de la llamada a un método
  • ¿Dónde se llama al bloque? Donde el método indique con yield
  • El bloque (realmente un objeto Proc) se pasa como una especie de parámetro no declarado

Ejemplos de paso de bloques:

  • Llamada a un bloque sin parámetros
def run_it
  puts("Before the yield")
  yield
  puts("After the yield")
end
run_it do
  puts('Hello')
  puts('Coming to you from inside the block')
end

# Salida =>
#  Before the yield
#  Hello
#  Coming to you from inside the block
#  After the yield
  • Cualquier método puede recibir un bloque como parámetro implícito, pero no lo ejecuta si no hace yield:
def run_it
end

run_it do
  puts('Hello')
end

# => No genera salida
  • Con yield:
def run_it
  yield if block_given?
end

run_it do
  puts('Hello')
end
# Salida =>
#   Hello
  • Llamada a un bloque con parámetros:
def run_it_with_parameter
  puts('Before the yield')
  yield(24)
  puts('After the yield')
end

run_it_with_parameter do |x|
  puts('Hello from inside the proc')
  puts("The value of x is #{x}")
end

# Salida =>
#  Before the yield
#  Hello from inside the proc
#  The value of x is 24
#  After the yield
  • Hacer explícito el bloque pasado como parámetro usando ampersand: explicitamos que se espera que el método reciba un parámetro de tipo bloque
def run_it_with_parameter(&block)
  puts('Before the call')
  block.call(24)
  puts('After the call')
end
  • Convertir un Proc o un lambda en un bloque pasado como parámetro:
my_proc = Proc.new {|x| puts("The value of x is #{x}")}
run_it_with_parameter(&my_proc)
my_lambda = lambda {|x| puts("The value of x is #{x}")}
run_it_with_parameter(&my_lambda)

# Salida (en ambos casos) =>
#  Before the call
#  The value of x is 24
#  After the call

Lecturas recomendadas

Ejemplo de interfaces funcionales: Formateo de informes

Versión en ruby

class Report
  attr_reader :title, :text
  attr_accessor :formatter
  def initialize(formatter)
    @title = 'Informe mensual'
    @text = ['Todo marcha', 'muy bien.']
    @formatter = formatter
  end
  def output_report()
    @formatter.output_report(self)
  end
end
class HTMLFormatter
  def output_report(context)
    puts('<html>')
    puts(' <head>')
    # Output The rest of the report ...
    puts(" <title>#{context.title}</title>")
    puts(' </head>')
    puts(' <body>')
    context.text.each do |line|
      puts(" <p>#{line}</p>")
    end
    puts(' </body>')
    puts('</html>')
  end
end

class PlainTextFormatter
  def output_report(context)
    puts("***** #{context.title} *****")
    context.text.each do |line|

Versión con interfaces funcionales (Ruby procs + blocks)

class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(&formatter)
    @title = 'Monthly Report'
    @text = [ 'Things are going', 'really, really well.' ]
    @formatter = formatter
  end

  def output_report
    @formatter.call( self )
  end
end

Formateo HTML

HTML_FORMATTER = lambda do |context|
  puts('<html>')
  puts(' <head>')
  puts("   <title>#{context.title}</title>")
  puts(' </head>')
  puts(' <body>')
  context.text.each do |line|
    puts("  <p>#{line}</p>" )
  end
  puts(' </body>')
  puts

report = Report.new &HTML_FORMATTER
report.output_report

Formateo de texto

report = Report.new do |context|
  puts("***** #{context.title} *****")
  context.text.each do |line|
    puts(line)
  end
end