ajuda você a escrever código C++ conciso e legível.
Um ótimo código deve ser autodocumentado, mas ao usar C++ na realidade, você pode acabar lidando com coisas de baixo nível, como iteradores ou loops escritos à mão, que desviam a atenção da essência real do seu código.
FunctionalPlus é uma pequena biblioteca somente de cabeçalho que ajuda você a reduzir o ruído do código e a lidar com apenas um único nível de abstração por vez. Ao aumentar a brevidade e a capacidade de manutenção do seu código, ele pode melhorar a produtividade (e a diversão!) No longo prazo. Ele persegue esses objetivos fornecendo funções puras e fáceis de usar que o liberam da implementação repetida de fluxos de controle comumente usados.
Digamos que você tenha uma lista de números e esteja interessado apenas nos ímpares.
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 ...
}
Existem diferentes possibilidades para atingir seu objetivo. Alguns deles são:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
do STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
do FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
Se você acha que a versão 3 pode ser a mais agradável de se trabalhar, você pode gostar do FunctionalPlus. E se você ainda acha que o loop for escrito à mão é mais fácil de entender, considere também o que aconteceria se o corpo do loop (ou seja, uma função lambda correspondente na chamada para fplus::keep_if
) fosse muito mais longo. Ao ler keep_if
você saberia imediatamente que odds
só podem conter elementos que vieram de values
e foram selecionados por algum predicado, possivelmente complicado. No caso do loop for, você não tem ideia do que está acontecendo até ler todo o corpo do loop. A versão do loop provavelmente precisaria de um comentário no topo informando o que o uso de keep_if
diria à primeira vista.
Abaixo estão alguns pequenos exemplos que mostram coisas interessantes que você pode fazer com funções e contêineres usando o FunctionalPlus.
Você pode testar o conteúdo de um contêiner para diversas propriedades, por exemplo
# 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
Existem também algumas funções convenientes para recuperar propriedades de contêineres. Por exemplo, você pode contar as ocorrências de um caractere em uma string.
# 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;
}
Saída:
There actually are this many 'I's in team: 2
Encontrar o elemento com classificação mais alta em um contêiner é muito simples em comparação com uma versão escrita à mão(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;
}
Saída:
Muffin is happy and sleepy. *purr* *purr* *purr*
Digamos que você tenha a seguinte função fornecida.
std::list< int > collatz_seq ( int x);
E você deseja criar um std::map
contendo representações de string das sequências Collatz para todos os números abaixo de 30. Você também pode implementar isso de maneira 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;
}
Saída:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
As funções mostradas não funcionam apenas com contêineres STL padrão, como std::vector
, std::list
, std::deque
, std::string
etc., mas também com contêineres personalizados que fornecem uma interface semelhante.
FunctionalPlus deduz tipos para você sempre que possível. Vamos pegar uma linha de código do exemplo Collatz:
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
é uma função que pega um uint64_t
e retorna um list
. show_ints
pega uma list
e retorna uma string
. Fazendo uso de function_traits
, escrito por kennyim, é possível deduzir automaticamente a expressão fplus::compose(collatz_seq, show_ints)
como sendo uma função que recebe um uint64_t
e retorna uma string
, então você não precisa fornecer dicas de tipo manualmente para o compilador.
Se duas funções cujos "tipos de conexão" não correspondem forem passadas, será gerada uma mensagem de erro inequívoca descrevendo o problema. FunctionalPlus usa asserções de tempo de compilação para evitar mensagens de erro confusamente longas que os compiladores geram quando confrontados com erros de tipo em modelos de função.
Mudar a maneira como você programa de "escrever seus próprios loops e ifs aninhados" para "compor e usar pequenas funções" resultará em mais erros em tempo de compilação, mas será recompensado por ter menos erros em tempo de execução. Além disso, erros de tempo de compilação mais precisos reduzirão o tempo gasto na depuração.
O artigo "Programação funcional em C++ com a biblioteca FunctionalPlus; hoje: HackerRank desafia Gemstones" fornece uma introdução suave à biblioteca, mostrando como alguém poderia desenvolver uma solução elegante para um problema usando a abordagem FunctionalPlus.
Também na Udemy existe o curso "Programação Funcional usando C++" que faz uso intenso do FunctionalPlus para explicar conceitos funcionais gerais.
O tutorial "Pedras Preciosas" acima explica como se pode aplicar o pensamento funcional para chegar à solução abaixo para o seguinte problema:
Encontre o número de caracteres presentes em cada linha de um 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));
}
Ao usar a funcionalidade do namespace fwd
, você pode conviver sem variáveis temporárias e deixar claro que todo o processo consiste simplesmente em enviar a entrada através de uma cadeia de funções, semelhante ao conceito de pipe na linha de comando do 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 ()
);
}
Em fplus::fwd::
você encontra muitas funções fplus::
novamente, mas em uma versão parcialmente curry, ou seja, fplus::foo : (a, b, c) -> d
tem sua contraparte com fplus::foo : (a, b) -> (c -> d)
. Isso torna o estilo acima possível.
Alternativamente à versão direta do aplicativo, você também pode escrever sem pontos e definir sua função por composição:
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()
);
A propósito, caso você precise dos parâmetros de uma função binária na ordem inversa, namespace fplus::fwd::flip
também existe. fplus::bar : (a, b) -> c
não só tem seu análogo em fplus::fwd::bar : a -> b -> c
mas também em fplus::fwd::flip::bar : b -> a -> c
.
Se você estiver procurando por uma função FunctionalPlus específica cujo nome ainda não sabe, é claro que você pode usar o recurso de preenchimento automático do seu IDE para navegar pelo conteúdo do namespace fplus
. Mas a forma recomendada é usar o site de busca da API FunctionalPlus . Você pode pesquisar rapidamente por palavras-chave ou assinaturas de tipo de função com ele. Se preferir, você também pode navegar pelo código-fonte usando o Sourcegraph.
As funções básicas são rápidas, graças ao conceito de abstração sem sobrecarga do C++. Aqui estão algumas medições do primeiro exemplo, feitas em um PC desktop padrão, compiladas com GCC e o sinalizador 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 |
Portanto, o compilador parece fazer um trabalho muito bom, otimizando e incorporando tudo para basicamente igualar o desempenho do código de máquina.
As funções mais complexas, porém, às vezes podem ser escritas de uma forma mais otimizada. Se você usar o FunctionalPlus em um cenário de desempenho crítico e a criação de perfil mostrar que você precisa de uma versão mais rápida de uma função, informe-me ou até mesmo ajude a melhorar o FunctionalPlus.
O FunctionalPlus internamente muitas vezes pode operar no local se um determinado contêiner for um valor r (por exemplo, em chamadas encadeadas) e, assim, evita muitas alocações e cópias desnecessárias. Mas este não é o caso em todas as situações. No entanto, graças ao trabalho com uma linguagem multiparadigma, é possível combinar facilmente código imperativo otimizado manualmente com funções fplus
. Felizmente, a experiência (também conhecida como criação de perfil) mostra que, na maioria dos casos, a grande maioria do código em um aplicativo não é relevante para o desempenho geral e o consumo de memória. Portanto, focar inicialmente na produtividade do desenvolvedor e na legibilidade do código é uma boa ideia.
FunctionalPlus e range-v3 (base para ranges
em C++-20) têm coisas em comum, como mostra o trecho de código a seguir.
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);
Existem algumas diferenças. Os intervalos Range-v3 são preguiçosos, o que significa que nenhuma memória intermediária é alocada durante as etapas únicas de uma cadeia de processamento como a acima. Por outro lado, ao usar o FunctionalPlus, você trabalha com contêineres STL normais. Além disso, implementar uma nova função é mais simples do que escrever um novo adaptador de intervalo. Além disso, o FunctionalPlus oferece muito mais funções prontas para uso e possui o site de pesquisa da API. Portanto a escolha entre as duas bibliotecas depende das suas preferências e das necessidades do projeto.
É necessário um compilador compatível com C++14 . Compiladores destas versões em diante estão bem:
Guias para diferentes maneiras de instalar o FunctionalPlus podem ser encontrados em INSTALL.md.
A funcionalidade desta biblioteca cresceu inicialmente devido à minha necessidade pessoal dela ao usar C++ regularmente. Eu tento o meu melhor para torná-lo livre de erros e tão confortável de usar quanto possível. A API ainda pode mudar no futuro. Se você tiver alguma sugestão, encontrar erros, perder algumas funções ou quiser dar feedback/crítica geral, adoraria ouvir sua opinião. Claro, contribuições também são muito bem-vindas.
Distribuído sob a Licença de Software Boost, Versão 1.0. (Veja o arquivo LICENSE
que acompanha ou copie em http://www.boost.org/LICENSE_1_0.txt)