le ayuda a escribir código C++ conciso y legible.
Un gran código debería ser autodocumentado en su mayoría, pero al usar C++ en realidad puedes encontrarte lidiando con cosas de bajo nivel como iteradores o bucles escritos a mano que distraen de la esencia real de tu código.
FunctionalPlus es una pequeña biblioteca de encabezados que le ayuda a reducir el ruido del código y a manejar un solo nivel de abstracción a la vez. Al aumentar la brevedad y la facilidad de mantenimiento de su código, puede mejorar la productividad (¡y la diversión!) a largo plazo. Persigue estos objetivos proporcionando funciones puras y fáciles de usar que le liberan de implementar flujos de control de uso común una y otra vez.
Supongamos que tiene una lista de números y solo le interesan los impares.
bool is_odd_int ( int x) { return x % 2 != 0 ; }
int main ()
{
typedef vector< int > Ints;
Ints values = { 24 , 11 , 65 , 44 , 80 , 18 , 73 , 90 , 69 , 18 };
// todo: get odd numbers from values ...
}
Hay diferentes posibilidades para lograr tu objetivo. Algunos de ellos son:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
del STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
de FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
Si cree que la versión 3 podría ser la más agradable para trabajar, es posible que le guste FunctionalPlus. Y si todavía cree que el bucle for escrito a mano es más fácil de entender, considere también lo que sucedería si el cuerpo del bucle (es decir, una función lambda correspondiente en la llamada a fplus::keep_if
) fuera mucho más largo. Al leer keep_if
sabrás inmediatamente que odds
solo pueden contener elementos que provienen de values
y fueron seleccionados por algún predicado posiblemente complicado. En el caso del bucle for, no tienes idea de lo que está sucediendo hasta que lees todo el cuerpo del bucle. La versión en bucle probablemente necesitaría un comentario en la parte superior que indique lo que el uso de keep_if
indicaría a primera vista.
A continuación se muestran algunos ejemplos breves que muestran cosas interesantes que puede hacer con funciones y contenedores utilizando FunctionalPlus.
Puede probar el contenido de un contenedor para varias propiedades, por ejemplo
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::list things = { " same old " , " same old " };
if ( fplus::all_the_same (things))
std::cout << " All things being equal. " << std::endl;
}
team
También hay algunas funciones convenientes para recuperar propiedades de contenedores. Por ejemplo, puedes contar las apariciones de un carácter en una cadena.
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::string team = " Our team is great. I love everybody I work with. " ;
std::cout << " There actually are this many 'I's in team: " <<
fplus::count ( " I " , fplus::split_words ( false , team)) << std::endl;
}
Producción:
There actually are this many 'I's in team: 2
Encontrar el elemento mejor valorado en un contenedor es muy sencillo en comparación con una versión escrita a mano(1, 2).
# include < fplus/fplus.hpp >
# include < iostream >
struct cat
{
double cuteness () const
{
return softness_ * temperature_ * roundness_ * fur_amount_ - size_;
}
std::string name_;
double softness_;
double temperature_;
double size_;
double roundness_;
double fur_amount_;
};
void main ()
{
std::vector cats = {
{ " Tigger " , 5 , 5 , 5 , 5 , 5 },
{ " Simba " , 2 , 9 , 9 , 2 , 7 },
{ " Muffin " , 9 , 4 , 2 , 8 , 6 },
{ " Garfield " , 6 , 5 , 7 , 9 , 5 }};
auto cutest_cat = fplus::maximum_on ( std::mem_fn (&cat::cuteness), cats);
std::cout << cutest_cat. name_ <<
" is happy and sleepy. *purr* *purr* *purr* " << std::endl;
}
Producción:
Muffin is happy and sleepy. *purr* *purr* *purr*
Digamos que tienes la siguiente función dada.
std::list< int > collatz_seq ( int x);
Y desea crear un std::map
que contenga representaciones de cadenas de las secuencias de Collatz para todos los números inferiores a 30. También puede implementar esto de manera funcional.
# include < fplus/fplus.hpp >
# include < iostream >
// std::list collatz_seq(std::uint64_t x) { ... }
int main ()
{
typedef std::list< int > Ints;
// [1, 2, 3 ... 29]
auto xs = fplus::numbers( 1 , 30 );
// A function that does [1, 2, 3, 4, 5] -> "[1 => 2 => 3 => 4 => 5]"
auto show_ints = fplus::bind_1st_of_2 (fplus::show_cont_with, " => " );
// A composed function that calculates a Collatz sequence and shows it.
auto show_collats_seq = fplus::compose (collatz_seq, show_ints);
// Associate the numbers with the string representation of their sequences.
auto collatz_dict = fplus::create_map_with (show_collats_seq, xs);
// Print some of the sequences.
std::cout << collatz_dict[ 13 ] << std::endl;
std::cout << collatz_dict[ 17 ] << std::endl;
}
Producción:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
Las funciones que se muestran no solo funcionan con contenedores STL predeterminados como std::vector
, std::list
, std::deque
, std::string
etc. sino también con contenedores personalizados que proporcionan una interfaz similar.
FunctionalPlus deduce tipos por usted siempre que sea posible. Tomemos una línea de código del ejemplo de Collatz:
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
es una función que toma uint64_t
y devuelve una list
. show_ints
toma una list
y devuelve una string
. Al utilizar function_traits
, escrito por kennyim, es posible deducir automáticamente la expresión fplus::compose(collatz_seq, show_ints)
como una función que toma un uint64_t
y devuelve una string
, por lo que no es necesario proporcionar manualmente sugerencias de tipo para el compilador.
Si se pasan dos funciones cuyos "tipos de conexión" no coinciden, se generará un mensaje de error inequívoco que describe el problema. FunctionalPlus utiliza aserciones en tiempo de compilación para evitar los mensajes de error confusamente largos que generan los compiladores cuando se enfrentan a errores de tipo en las plantillas de funciones.
Cambiar la forma de programar de "escribir sus propios bucles y if anidados" a "componer y usar funciones pequeñas" generará más errores en el tiempo de compilación, pero dará sus frutos al tener menos errores en el tiempo de ejecución. Además, errores más precisos en tiempo de compilación reducirán el tiempo dedicado a la depuración.
El artículo "Programación funcional en C++ con la biblioteca FunctionalPlus; hoy: HackerRank Challenge Gemstones" proporciona una introducción sencilla a la biblioteca al mostrar cómo se puede desarrollar una solución elegante a un problema utilizando el enfoque FunctionalPlus.
También en Udemy hay un curso "Programación funcional usando C++" que hace un uso intensivo de FunctionalPlus para explicar conceptos funcionales generales.
El tutorial "Piedras preciosas" anterior explica cómo se puede aplicar el pensamiento funcional para llegar a la siguiente solución para el siguiente problema:
Encuentre la cantidad de caracteres presentes en cada línea de un texto de entrada.
std::string gemstone_count ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
const auto lines = split_lines ( false , input); // false = no empty lines
const auto sets = transform (
convert_container,
lines);
// Build the intersection of all given character sets (one per line).
const auto gem_elements = fold_left_1 (
set_intersection, sets);
return show ( size_of_cont (gem_elements));
}
Al utilizar la funcionalidad del namespace fwd
, puede prescindir de variables temporales y dejar claro que todo el proceso consiste simplemente en enviar la entrada a través de una cadena de funciones, similar al concepto de canalización en la línea de comandos de Unix.
std::string gemstone_count_fwd_apply ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
return fwd::apply (
input
, fwd::split_lines ( false )
, fwd::transform (convert_container)
, fwd::fold_left_1 (set_intersection)
, fwd::size_of_cont ()
, fwd::show ()
);
}
En fplus::fwd::
encontrará muchas funciones fplus::
nuevamente, pero en una versión parcialmente curry, es decir, fplus::foo : (a, b, c) -> d
tiene su contraparte con fplus::foo : (a, b) -> (c -> d)
. Esto hace posible el estilo anterior.
Como alternativa a la versión avanzada de la aplicación, también puedes escribir sin puntos y definir tu función por composición:
using namespace fplus ;
typedef std::set characters;
const auto gemstone_count_fwd_compose = fwd::compose(
fwd::split_lines ( false ),
fwd::transform(convert_container),
fwd::fold_left_1(set_intersection),
fwd::size_of_cont(),
fwd::show()
);
Por cierto, en caso de que necesite los parámetros de una función binaria en orden inverso, namespace fplus::fwd::flip
también existe. fplus::bar : (a, b) -> c
no sólo tiene su análogo en fplus::fwd::bar : a -> b -> c
sino también en fplus::fwd::flip::bar : b -> a -> c
.
Si está buscando una función FunctionalPlus específica cuyo nombre aún no conoce, puede, por supuesto, utilizar la función de autocompletar de su IDE para explorar el contenido del namespace fplus
. Pero la forma recomendada es utilizar el sitio web de búsqueda de la API FunctionalPlus . Puede buscar rápidamente por palabras clave o firmas de tipos de funciones con él. Si lo prefieres, también puedes explorar el código fuente usando Sourcegraph.
Las funciones básicas son rápidas, gracias al concepto de abstracción sin gastos generales de C++. A continuación se muestran algunas medidas del primer ejemplo, tomadas en una PC de escritorio estándar, compiladas con GCC y el indicador O3
.
5000 random numbers, keep odd ones, 20000 consecutive runs accumulated
----------------------------------------------------------------------
| Hand-written for loop | std::copy_if | fplus::keep_if |
|-----------------------|--------------|----------------|
| 0.632 s | 0.641 s | 0.627 s |
Entonces, el compilador parece hacer un muy buen trabajo optimizando e integrando todo para básicamente igualar el rendimiento del código de máquina.
Sin embargo, las funciones más complejas a veces podrían escribirse de una manera más optimizada. Si utiliza FunctionalPlus en un escenario de rendimiento crítico y la generación de perfiles muestra que necesita una versión más rápida de una función, hágamelo saber o incluso ayúdeme a mejorar FunctionalPlus.
FunctionalPlus internamente a menudo puede operar in situ si un contenedor determinado tiene un valor r (por ejemplo, en llamadas encadenadas) y, por lo tanto, evita muchas asignaciones y copias innecesarias. Pero este no es el caso en todas las situaciones. Sin embargo, gracias a trabajar con un lenguaje multiparadigma, se puede combinar fácilmente código imperativo optimizado manualmente con funciones fplus
. Afortunadamente, la experiencia (también conocida como creación de perfiles) muestra que, en la mayoría de los casos, la gran mayoría del código de una aplicación no es relevante para el rendimiento general y el consumo de memoria. Por lo tanto, centrarse inicialmente en la productividad del desarrollador y la legibilidad del código es una buena idea.
FunctionalPlus y range-v3 (base para ranges
en C++-20) tienen cosas en común, como muestra el siguiente fragmento de código.
const auto times_3 = []( int i){ return 3 * i;};
const auto is_odd_int = []( int i){ return i % 2 != 0 ;};
const auto as_string_length = []( int i){ return std::to_string (i). size ();};
// FunctionalPlus
using namespace fplus ;
const auto result_fplus = fwd::apply(
numbers ( 0 , 15000000 )
, fwd::transform(times_3)
, fwd::drop_if(is_odd_int)
, fwd::transform(as_string_length)
, fwd::sum());
// range-v3
const auto result_range_v3 =
accumulate (
views::ints ( 0 , ranges::unreachable)
| views::take( 15000000 )
| views::transform(times_3)
| views::remove_if(is_odd_int)
| views::transform(as_string_length), 0);
Sin embargo, existen algunas diferencias. Los rangos Range-v3 son diferidos, lo que significa que no se asigna memoria intermedia durante los pasos individuales de una cadena de procesamiento como la anterior. Por otro lado, cuando se utiliza FunctionalPlus se trabaja con contenedores STL normales. Además, implementar una nueva función es más sencillo en comparación con escribir un nuevo adaptador de rango. Además, FunctionalPlus proporciona muchas más funciones listas para usar y tiene el sitio web de búsqueda API. Entonces, la elección entre las dos bibliotecas depende de sus preferencias y de las necesidades del proyecto.
Se necesita un compilador compatible con C++14 . Los compiladores a partir de estas versiones están bien:
Se pueden encontrar guías sobre diferentes formas de instalar FunctionalPlus en INSTALL.md.
La funcionalidad de esta biblioteca creció inicialmente debido a mi necesidad personal mientras usaba C++ con regularidad. Hago todo lo posible para que esté libre de errores y sea lo más cómodo posible de usar. La API aún podría cambiar en el futuro. Si tiene alguna sugerencia, encuentra errores, pierde algunas funciones o desea brindar comentarios o críticas generales, me encantaría saber de usted. Por supuesto, las contribuciones también son bienvenidas.
Distribuido bajo la licencia de software Boost, versión 1.0. (Ver el archivo adjunto LICENSE
o copiar en http://www.boost.org/LICENSE_1_0.txt)