ðīó §ó Ēó Ĩó Ūó §ó ŋ English version of this text here!
Um tempo atrÃĄs, eu tentei criar uma funçÃĢo recursiva e anÃīnima em Elixir porque eu estava dentro do iex e estava com preguiça de criar um mÃģdulo dentro do repl. Minha primeira tentativa foi algo assim:
# Fatorial ingÊnuo, anÃīnimo e recursivo
fact = fn
0 -> 1
n -> n * fact.(n-1)
end
OBS: Sim, ÃĐ possÃvel definir mÚltiplas clÃĄusulas para funçÃĩes anÃīnimas!
à claro que isso nÃĢo funcionou, porque fact/1 ainda nÃĢo estÃĄ definida no momento em que ÃĐ chamada dentro do corpo da funçÃĢo.
Eu realmente nÃĢo lembro por que eu precisava de uma funçÃĢo anÃīnima e recursiva, mas no começo dessa semana eu percebi o que que eu tinha que fazer para que funcionasse. Tudo o que temos que fazer ÃĐ assegurar-nos que a funçÃĢo existe no momento em que ÃĐ chamada, certo?
E se a funçÃĢo anÃīnima recebesse, entÃĢo, outra funçÃĢo como argumento, e essa funçÃĢo recebida fosse a que faz a chamada recursiva?
Agora o Elixir nÃĢo poderia reclamar da funçÃĢo nÃĢo estar definida, porque ela foi fornecida via um argumento, que poderia ser qualquer coisa:
fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1)
end
Bom, mas o que ÃĐ essa funçÃĢo recur?
Ela poderia muito bem ser a prÃģpria fact! Somente temos que passÃĄ-la quanto a estivermos chamando:
fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1)
end
# Passe fact para si mesma, para que ela seja "recur" dentro do corpo da funçÃĢo
fact.(4, fact)
à claro que isso vai lançar uma exceçÃĢo, pois agora fact tem uma aridade de 2, e recur estÃĄ sendo chamada com apenas um argumento. Lembre-se que recur ÃĐ de "facto" fact! Sim, tentei fazer um trocadilho.
Para resolver isso, tudo que temos que fazer ÃĐ chamar recur passando recur para si mesmo, assim:
fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1, recur)
end
fact.(4, fact)
#=> 24
TambÃĐm funciona com recursÃĢo de cauda, ÃĐ claro. Tudo que terÃamos que fazer ÃĐ gerenciar os parÃĒmetros e retornos corretamente.
Legal, posso usar isso num Enum.map ou alguma outra funçÃĢo de ordem superior?
Bom, somente se tomarmos cuidado.
Se escrevermos isso:
fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1, recur)
end
Enum.map([1,2,3,4], fact)
EntÃĢo Enum.map/2 tentarÃĄ invocar fact com um argumento apenas, lançando uma exceçÃĢo.
Uma maneira de resolver isso seria encapsulando-a dentro de uma outra funçÃĢo:
fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1, recur)
end
Enum.map([1,2,3,4], &(fact.(&1, fact)))
#=> [1, 2, 6, 24]
Outra maneira seria definir fact com uma interface mais legal.
A funçÃĢo fact poderia simplesmente receber um argumento e daà definir a funçÃĢo anÃīnima e recursiva dentro de si mesma, e daà chamar a funçÃĢo anÃīnima recÃĐm definida passando a mesma como argumento.
Seria algo assim:
fact = fn n ->
do_fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1, recur)
end
do_fact.(n, do_fact)
end
Enum.map([1,2,3,4], fact)
#=> [1, 2, 6, 24]
Agora nÃģs temos uma funçÃĢo recursiva e anÃīnima que funciona como o esperado. Se eu quiser saber o fatorial de um nÚmero, apenas tenho que passar o nÚmero para a funçÃĢo, que ÃĐ exatamente o que Enum.map tentarÃĄ fazer.
AtÃĐ definir na mesma linha essa funçÃĢo anÃīnima funcionarÃĄ:
Enum.map([1,2,3,4], fn n ->
do_fact = fn
0, _ -> 1
n, recur -> n * recur.(n-1, recur)
end
do_fact.(n, do_fact)
end)
#=> [1, 2, 6, 24]
E recursiva de cauda:
Enum.map([1,2,3,4], fn n ->
do_fact = fn
0, acc, _ -> acc
n, acc, recur -> recur.(n-1, n*acc, recur)
end
# Lembre-se de definir corretamente o valor inicial do acumulador. Aqui ele ÃĐ 1
do_fact.(n, 1, do_fact)
end)
#=> [1, 2, 6, 24]
Ter uma interface mais legal nÃĢo ÃĐ bom somente para usÃĄ-la como argumentos de uma funçÃĢo de order superior, tambÃĐm ÃĐ mais legal para nÃģs invocarmos essa funçÃĢo, ÃĐ claro:
fact = fn n ->
do_fact = fn
0, acc, _ -> 1
n, acc, recur -> recur.(n-1, n*acc, recur)
end
do_fact.(n, 1, do_fact)
end
# NÃĢo hÃĄ necessidade de passar nem o acumulador nem a prÃģpria funçÃĢo
fact.(4)
#=> 24
Agora se eu quiser saber o fatorial de 4, tenho que apenas chamar a funçÃĢo fact/1 com 4 como argumento, sem ter essas coisas estranhas de passar a prÃģpria funçÃĢo por aà manualmente na chamada.
Legal, mas isso ÃĐ Ãštil?
Para ser sincero, eu nÃĢo sei.
Para começo de conversa, nÃĢo ÃĐ ilegal definir um mÃģdulo dentro do repl, entÃĢo o cenÃĄrio de "Estou com preguiça dentro do iex" nÃĢo ÃĐ tÃĢo importante assim. Quero dizer, eu tive que escrever tanta coisa a mais sÃģ para criar essa funçÃĢo recursiva e anÃīnima que talvez simplesmente escrever defmodule M do ... end seja mais fÃĄcil e mais rÃĄpido.
Outra situaçÃĢo em que poderÃamos usar isso seria em arquivos elixir normais onde temos algums Enums ou outras funçÃĩes de ordem superior trabalhando, mas daà a gente provavelmente jÃĄ vai estar dentro de um mÃģdulo, onde poderÃamos definir uma funçÃĢo privada "com nome" (nÃĢo anÃīnima) para fazer esse trabalho e jÃĄ dando um nome significativo a ela, aproveitando que vamos estar ali. Isso vai culminar num cÃģdigo melhor e mais legÃvel do que a "soluçÃĢo" com as funçÃĩes recursivas e anÃīnimas malucas.
Mas ei, mesmo que isso nÃĢo seja assim tÃĢo Útil, pelo menos ÃĐ sempre legal pensar sobre funçÃĩes e recursÃĢo, certo?
E tambÃĐm foi bem divertido escrever esse post! ð
à isso por hoje. Obrigado por ler e tenha um bom dia! (:
Top comments (0)