Apenas para esclarecer o nome, ambas são funções. Uma é uma função nomeada e a outra é anônima. Mas você está certo, eles funcionam de maneira um pouco diferente e vou ilustrar por que eles funcionam assim.
Vamos começar com o segundo fn
. fn
é um fechamento, semelhante ao lambda
do Ruby. Podemos criá-lo da seguinte maneira:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Uma função também pode ter várias cláusulas:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Agora, vamos tentar algo diferente. Vamos tentar definir cláusulas diferentes, esperando um número diferente de argumentos:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
Ah não! Temos um erro! Não podemos misturar cláusulas que esperam um número diferente de argumentos. Uma função sempre tem uma aridade fixa.
Agora, vamos falar sobre as funções nomeadas:
def hello(x, y) do
x + y
end
Como esperado, eles têm um nome e também podem receber alguns argumentos. No entanto, eles não são encerramentos:
x = 1
def hello(y) do
x + y
end
Esse código não será compilado porque toda vez que você vê um def
, você obtém um escopo de variável vazio. Essa é uma diferença importante entre eles. Eu particularmente gosto do fato de que cada função nomeada começa com uma lista limpa e você não mescla as variáveis de diferentes escopos. Você tem um limite claro.
Poderíamos recuperar a função hello nomeada acima como uma função anônima. Você mesmo mencionou:
other_function(&hello(&1))
E então você perguntou: por que não posso simplesmente passar isso hello
como em outros idiomas? Isso ocorre porque as funções no Elixir são identificadas por nome e aridade. Portanto, uma função que espera dois argumentos é uma função diferente daquela que espera três, mesmo se eles tivessem o mesmo nome. Portanto, se simplesmente passássemos hello
, não teríamos ideia do que hello
você realmente quis dizer. Aquele com dois, três ou quatro argumentos? Esta é exatamente a mesma razão pela qual não podemos criar uma função anônima com cláusulas com diferentes áreas.
Desde o Elixir v0.10.1, temos uma sintaxe para capturar funções nomeadas:
&hello/1
Isso capturará a função nomeada local hello com arity 1. Em todo o idioma e sua documentação, é muito comum identificar funções nessa hello/1
sintaxe.
É também por isso que o Elixir usa um ponto para chamar funções anônimas. Como você não pode simplesmente transmitir hello
como uma função, é necessário capturá-la explicitamente, existe uma distinção natural entre funções nomeadas e anônimas e uma sintaxe distinta para chamar cada uma delas torna tudo um pouco mais explícito (Lispers estaria familiarizado com isso devido à discussão Lisp 1 vs. Lisp 2).
No geral, essas são as razões pelas quais temos duas funções e por que elas se comportam de maneira diferente.
f()
(sem o ponto).is_function/2
um guarda.is_function(f, 2)
cheques tem arity de 2. :)SomeFun()
esome_fun()
. No Elixir, se removêssemos o ponto, eles seriam os mesmossome_fun()
esome_fun()
porque as variáveis usam o mesmo identificador como nomes de funções. Daí o ponto.Não sei o quanto isso será útil para mais ninguém, mas a maneira como finalmente entendi o conceito foi perceber que as funções do elixir não são funções.
Tudo no elixir é uma expressão. assim
não é uma função, mas a expressão retornada executando o código em
my_function
. Na verdade, existe apenas uma maneira de obter uma "Função" que você pode transmitir como argumento e usar a notação de função anônima.É tentador referir-se à notação fn ou & como um ponteiro de função, mas na verdade é muito mais. É um fechamento do ambiente circundante.
Se você se perguntar:
Preciso de um ambiente de execução ou de um valor de dados neste local?
E se você precisar de execução, use fn, então a maioria das dificuldades fica muito mais clara.
fonte
MyModule.my_function(foo)
é uma expressão, masMyModule.my_function
"poderia" ter sido uma expressão que retorna uma função "objeto". Mas já que você precisa contar à aridade, você precisaria de algo parecidoMyModule.my_function/1
. E acho que eles decidiram que era melhor usar uma&MyModule.my_function(&1)
sintaxe que permita expressar a aridade (e serve a outros propósitos também). Ainda não está claro porque é que há um()
operador para funções nomeadas e um.()
operador para funções sem nome&MyModule.my_function/1
é assim que você pode distribuí-lo como uma função.Eu nunca entendi por que as explicações disso são tão complicadas.
É realmente apenas uma distinção excepcionalmente pequena combinada com as realidades da "execução de funções sem parênteses" no estilo Ruby.
Comparar:
Para:
Embora ambos sejam apenas identificadores ...
fun1
é um identificador que descreve uma função nomeada definida comdef
.fun2
é um identificador que descreve uma variável (que contém uma referência à função).Considere o que isso significa quando você vê
fun1
oufun2
em alguma outra expressão? Ao avaliar essa expressão, você chama a função referenciada ou apenas faz referência a um valor sem memória?Não há uma boa maneira de saber em tempo de compilação. Ruby tem o luxo de inspecionar o espaço para nome da variável para descobrir se uma ligação de variável sombreava uma função em algum momento. Elixir, sendo compilado, não pode realmente fazer isso. É isso que a notação de ponto faz, diz ao Elixir que deve conter uma referência de função e que deve ser chamada.
E isso é realmente difícil. Imagine que não havia uma notação de ponto. Considere este código:
Dado o código acima, acho bem claro por que você deve dar a dica ao Elixir. Imagine se todas as des-referências de variáveis tivessem que verificar uma função? Como alternativa, imagine que heroísmo seria necessário para sempre inferir que a desreferência variável estava usando uma função?
fonte
Posso estar errado, já que ninguém o mencionou, mas também fiquei com a impressão de que a razão disso também é a herança ruby de poder chamar funções sem colchetes.
Arity está obviamente envolvido, mas vamos deixar isso de lado por um tempo e usar funções sem argumentos. Em uma linguagem como o javascript, onde os colchetes são obrigatórios, é fácil fazer a diferença entre passar uma função como argumento e chamar a função. Você chama apenas quando usa os colchetes.
Como você pode ver, nomeá-lo ou não não faz uma grande diferença. Mas elixir e ruby permitem chamar funções sem os colchetes. Essa é uma opção de design que eu pessoalmente gosto, mas tem esse efeito colateral que você não pode usar apenas o nome sem os colchetes, pois pode significar que você deseja chamar a função. É para isso que
&
serve. Se você deixar o arity appart por um segundo, acrescentar o nome da sua função&
significa que deseja explicitamente usar essa função como argumento, e não o que essa função retorna.Agora, a função anônima é um pouco diferente, pois é usada principalmente como argumento. Novamente, essa é uma opção de design, mas o racional por trás disso é que ela é usada principalmente por iteradores, tipo de funções que aceitam funções como argumentos. Então, obviamente, você não precisa usá-lo
&
porque eles já são considerados argumentos por padrão. É o propósito deles.Agora, o último problema é que às vezes você precisa chamá-los em seu código, porque eles nem sempre são usados com um tipo de função de iterador ou você pode estar codificando um iterador. Para a pequena história, como o ruby é orientado a objetos, a principal maneira de fazer isso era usar o
call
método no objeto. Dessa forma, você pode manter o comportamento dos colchetes não obrigatórios consistente.Agora, alguém criou um atalho que basicamente se parece com um método sem nome.
Novamente, essa é uma escolha de design. Agora, o elixir não é orientado a objetos e, portanto, não deve usar o primeiro formulário com certeza. Não posso falar por José, mas parece que a segunda forma foi usada no elixir porque ainda parece uma chamada de função com um caractere extra. É perto o suficiente de uma chamada de função.
Eu não pensei em todos os prós e contras, mas parece que nos dois idiomas você pode se dar bem com os colchetes desde que os torne obrigatórios para funções anônimas. Parece que é:
Parênteses obrigatórios VS Notação ligeiramente diferente
Nos dois casos, você abre uma exceção porque os dois se comportam de maneira diferente. Como existe uma diferença, é melhor deixar óbvio e seguir a notação diferente. Os colchetes obrigatórios pareceriam naturais na maioria dos casos, mas muito confusos quando as coisas não saem conforme o planejado.
Aqui está. Agora, essa pode não ser a melhor explicação do mundo, porque simplifiquei a maioria dos detalhes. Também a maioria são escolhas de design e tentei dar uma razão para elas sem julgá-las. Eu amo elixir, eu amo rubi, gosto das chamadas de função sem colchetes, mas, como você, acho as consequências bastante errôneas de vez em quando.
E no elixir, é apenas esse ponto extra, enquanto no rubi você tem blocos em cima disso. Os blocos são incríveis e estou surpreso com o quanto você pode fazer com apenas blocos, mas eles só funcionam quando você precisa de apenas uma função anônima que é o último argumento. Então, como você deve lidar com outros cenários, aqui vem todo o método / lambda / proc / block confusion.
Enfim ... isso está fora de escopo.
fonte
Há uma excelente postagem no blog sobre esse comportamento: link
Dois tipos de funções
Ponto na chamada
fn
fonte
O Elixir possui chaves opcionais para funções, incluindo funções com zero aridade. Vamos ver um exemplo de por que ela torna importante uma sintaxe de chamada separada:
Sem fazer a diferença entre dois tipos de funções, não podemos dizer o que
Insanity.dive
significa: obter uma função em si, chamá-la ou também chamar a função anônima resultante.fonte
fn ->
sintaxe é para usar funções anônimas. Fazer var. () Está apenas dizendo ao elixir que eu quero que você pegue esse var com uma função e execute-o, em vez de se referir ao var como algo apenas mantendo essa função.O Elixir possui um padrão comum em que, em vez de ter lógica dentro de uma função para ver como algo deve ser executado, combinamos diferentes funções com base no tipo de entrada que temos. Suponho que é por isso que nos referimos às coisas por aridade no
function_name/1
sentido.É meio estranho se acostumar a fazer definições de funções abreviadas (func (& 1), etc.), mas útil quando você está tentando canalizar ou manter seu código conciso.
fonte
Você pode fazer
otherfunction(&myfunction/2)
Como o elixir pode executar funções sem os colchetes (como
myfunction
), usá-otherfunction(myfunction))
lo tentará executarmyfunction/0
.Portanto, você precisa usar o operador de captura e especificar a função, incluindo arity, pois você pode ter funções diferentes com o mesmo nome. Assim
&myfunction/2
,.fonte