DEV Community

Cover image for FunçÃĩes recursivas e anÃīnimas em Elixir
Lucas Perez
Lucas Perez

Posted on Edited on

FunçÃĩes recursivas e anÃīnimas em Elixir

ðŸī󠁧ó Ēó Ĩó Ū󠁧ó ŋ 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
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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)
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

É 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
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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)
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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]
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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]
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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]
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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]
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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
ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: lucassperez/funcoes recursivas e anonimas em elixir i - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

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)