Lendo a Introdução Prática à Programação Funcional de Mary Rose Cook , ela oferece um exemplo de anti-padrão
def format_bands(bands):
for band in bands:
band['country'] = 'Canada'
band['name'] = band['name'].replace('.', '')
band['name'] = band['name'].title()
Desde a
- a função faz mais de uma coisa
- o nome não é descritivo
- tem efeitos colaterais
Como solução proposta, ela sugere pipelining de funções anônimas
pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])
No entanto, isso me parece ter a desvantagem de ser ainda menos testável; pelo menos format_bands poderia ter um teste de unidade para verificar se faz o que deveria, mas como testar o pipeline? Ou é a ideia de que as funções anônimas são tão auto-explicativas que não precisam ser testadas?
Meu aplicativo do mundo real para isso é tentar tornar meu pandas
código mais funcional. Muitas vezes terei algum tipo de pipeline dentro de uma função "munging"
def munge_data(df)
df['name'] = df['name'].str.lower()
df = df.drop_duplicates()
return df
Ou reescrevendo no estilo de pipeline:
def munge_data(df)
munged = (df.assign(lambda x: x['name'].str.lower()
.drop_duplicates())
return munged
Alguma sugestão para as melhores práticas nesse tipo de situação?
python
unit-testing
Max Flander
fonte
fonte
Respostas:
Acho que você perdeu a parte mais importante do exemplo corrigido do livro. A mudança mais fundamental no código é do método que opera em todos os valores em uma lista para operar em um elemento.
Já existem funções como
iter
(neste caso, nomeadaspipeline_foreach
) que executam uma determinada operação em todos os elementos em uma lista. Não havia necessidade de duplicar isso com umfor
loop. O uso de uma operação de lista conhecida também torna clara sua intenção. Commap
você está transformando os valores. Comiter
você está realizando um efeito colateral com cada elemento. Comfor
loop você é ... bem, você realmente não sabe até olhar através dele.O código corrigido de exemplo ainda não é muito funcional, porque (até onde eu sei) modifica os valores na lista sem retorná-los, impedindo a composição adicional de tubulação ou função. O método funcionalmente preferido
map
criaria uma nova lista de bandas com o atualizadocountry
ename
. Em seguida, você pode canalizar essa saída para a próxima função ou compormap
com outra função que tenha uma lista de bandas. Comiter
, é como um beco sem saída.Eu acho que o código do resultado final tem pequenas funções que são muito triviais para incomodar os testes aqui. Afinal, você não precisa escrever testes de unidade contra
replace
outitle
. Agora, talvez você queira compor esses itens em sua própria função e teste de unidade, para que a combinação desejada seja alcançada em um único item. Eu mesmo, provavelmente teria mudadoformat_bands
paraformat_band
singular, eliminado o loop for e chamadopipeline_each(bands, format_band)
. Em seguida, você pode testar format_band para garantir que não se esqueceu de algo.De qualquer forma, no seu código. Seu segundo exemplo de código parece mais pipeline-y. Mas isso por si só não fornece os benefícios da programação funcional. Na prática, programação funcional significa garantir a compatibilidade de funções com outras funções, definindo sua compatibilidade apenas em termos de entradas e saídas. Se houver efeitos colaterais ocultos dentro da função, apesar da entrada / saída alinhada com outra função, não será possível saber se eles são compatíveis até o tempo de execução. Se, no entanto, duas funções não tiverem efeitos colaterais e corresponderem de saída para entrada, você poderá fazer o pipeline ou compor com pouca preocupação com resultados inesperados.
fonte