Lambda-Sobre-Lambda en C++14
¿Cómo termina/termina la siguiente llamada lambda recursiva?
#include <cstdio>
auto terminal = [](auto term) // <---------+
{ // |
return [=] (auto func) // | ???
{ // |
return terminal(func(term)); // >---------+
};
};
auto main() -> int
{
auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
auto world =[](auto s){ fprintf(s,"World\n"); return s; };
terminal(stdout)
(hello)
(world) ;
return 0;
}
¿Qué me estoy perdiendo aquí ?
6 answers
No es una llamada a una función recursiva, mírala paso a paso:
-
terminal(stdout)
- esto simplemente devuelve una lambda que ha capturadostdout
- El resultado de 1. se llama con la lambda
hello
, que ejecuta la lambda (func(term)
), cuyo resultado se pasa aterminal()
, que simplemente devuelve una lambda como en 1. - El resultado de 2. se llama con la lambda
world
, que hace lo mismo que 2, esta vez se descarta el valor devuelto...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:37:22
La llamada en sí no es recursiva. Devuelve un objeto de función que, si se llama, llamará de nuevo a terminal
para generar otro objeto de función.
Así que terminal(stdout)
devuelve un funtor que captura stdout
y puede ser llamado con otro objeto de función. Llamándolo de nuevo, (hello)
, llama al funtor hello
con el término capturado stdout
, produciendo "Hello"
; el llama terminal
y devuelve otro funtor que esta vez captura el valor devuelto de hello
- que sigue siendo stdout
. Llamando a ese functor, (world)
, de la misma manera, de salida "World"
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:33:40
La clave aquí es entender que esto es válido:
world(hello(stdout));
E imprimirá "Hola Mundo". La serie recursiva de lambdas se puede desenrollar como
#include <cstdio>
auto terminal = [](auto term) // <---------+
{ // |
return [=] (auto func) // | ???
{ // |
return terminal(func(term)); // >---------+
};
};
/*
terminal(stdout) -returns> anonymous_lambda which captures stdout (functor)
anonymous_lambda(hello) is called, func(term) is hello(stdout) and prints "Hello" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
(the above 2 lines start again)
terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
anonymous_lambda(world) is called, func(term) is world(stdout) and prints "World" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
nobody uses that anonymous_lambda.. end.
*/
auto main() -> int
{
auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
auto world =[](auto s){ fprintf(s,"World\n"); return s; };
world(hello(stdout));
terminal(stdout)
(hello)
(world) ;
return 0;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:58:46
Se puede traducir internamente en algo que se ve de la siguiente manera:
#include <cstdio>
template <typename T>
struct unnamed_lambda
{
unnamed_lambda(T term) : captured_term(term) {}
template <typename A>
unnamed_lambda operator()(A func);
T captured_term;
};
struct terminal_lambda
{
template <typename A>
unnamed_lambda<A> operator()(A term)
{
return unnamed_lambda<A>{term};
}
};
terminal_lambda terminal;
template <typename T>
template <typename A>
unnamed_lambda<T> unnamed_lambda<T>::operator()(A func)
{
return terminal(func(captured_term));
}
struct Hello
{
FILE* operator()(FILE* s)
{
fprintf(s, "Hello\n");
return s;
}
};
struct World
{
FILE* operator()(FILE* s)
{
fprintf(s, "World\n");
return s;
}
};
int main()
{
Hello hello;
World world;
unnamed_lambda<FILE*> l1 = terminal(stdout);
unnamed_lambda<FILE*> l2 = l1(hello);
unnamed_lambda<FILE*> l3 = l2(world);
// same as:
terminal(stdout)(hello)(world);
}
En realidad esto es lo que hace el compilador detrás de la escena con lambdas (con cierta aproximación).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-08 13:32:33
Creo que la fuente de confusión proviene de leer una declaración lambda como una llamada lambda. De hecho aquí:
auto terminal = [](auto term) // <---------+
{ // |
return [=] (auto func) // | ???
{ // |
return terminal(func(term)); // >---------+
};
};
El autor acaba de declarar una lambda terminal
que toma un argumento arbitrario term
y devuelve una lambda sin nombre, ¡nada más! Echemos un vistazo a esta lambda sin nombre:
- acepta un objeto invocable
func
como argumento y lo llama en el parámetro copy-capturedterm
y - devuelve el resultado de la terminal llamada con el resultado de la llamada
func(term)
; por lo que devuelve otra lambda sin nombre que captura el resultado defunc(term)
, pero esta lambda no se llama por ahora, no hay recursión.
Ahora el truco en general debería ser más claro:
-
terminal(stdout)
devuelve un lambda sin nombre que ha capturado stdout. -
(hello)
llama a este lambda passing sin nombre como arg the hello callable. Esto se llama en el stdout previamente capturado.hello(stdout)
devuelve de nuevo stdout que se utiliza como argumento de una llamada a terminal, devolviendo otra lambda sin nombre que ha capturado stdout. -
(world)
igual que 2.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 14:58:20
-
Terminal(stdout) devuelve una función, llamémosla function
x
, con paramfunc
. Así que:terminal(stdout) ==> x(func) { return terminal(func(stdout)) };
-
Ahora terminal(stdout)(hola) llama a la función
x(hello)
:terminal(stdout)(hello) ==> x(hello) { return terminal(hello(stdout)) };
Esto resulta en
hello
la función se llama y devuelve la funciónx
de nuevo. -
Ahora terminal (std)(hola) (mundo) llama a la función
x(world)
:terminal(stdout)(hello) ==> x(world) { return terminal(world(stdout)) };
Esto resulta en
world
función get called y devuelve la funciónx
nuevo. Functionx
ahora ya no se llama ya que no hay más param.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-03 04:29:56