Solo para aclarar el nombre, ambas son funciones. Una es una función con nombre y la otra es anónima. Pero tienes razón, funcionan de manera algo diferente y voy a ilustrar por qué funcionan así.
Comencemos con el segundo fn
,. fn
es un cierre, similar a un lambda
en Ruby. Podemos crearlo de la siguiente manera:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Una función también puede tener múltiples cláusulas:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Ahora, intentemos algo diferente. Tratemos de definir diferentes cláusulas esperando un número diferente de argumentos:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
¡Oh no! ¡Tenemos un error! No podemos mezclar cláusulas que esperen un número diferente de argumentos. Una función siempre tiene una aridad fija.
Ahora, hablemos de las funciones nombradas:
def hello(x, y) do
x + y
end
Como se esperaba, tienen un nombre y también pueden recibir algunos argumentos. Sin embargo, no son cierres:
x = 1
def hello(y) do
x + y
end
Este código no se compilará porque cada vez que ve un def
, obtiene un alcance variable vacío. Esa es una diferencia importante entre ellos. Particularmente me gusta el hecho de que cada función nombrada comienza con una pizarra limpia y no se mezclan las variables de diferentes ámbitos. Tienes un límite claro.
Podríamos recuperar la función de saludo mencionada anteriormente como una función anónima. Lo mencionaste tú mismo:
other_function(&hello(&1))
Y luego preguntaste, ¿por qué no puedo simplemente pasarlo hello
como en otros idiomas? Esto se debe a que las funciones en Elixir se identifican por nombre y aridad. Entonces, una función que espera dos argumentos es una función diferente de una que espera tres, incluso si tenían el mismo nombre. Entonces, si simplemente aprobamos hello
, no tendríamos idea de lo hello
que realmente quisiste decir. ¿El que tiene dos, tres o cuatro argumentos? Esta es exactamente la misma razón por la que no podemos crear una función anónima con cláusulas con aridades diferentes.
Desde Elixir v0.10.1, tenemos una sintaxis para capturar funciones con nombre:
&hello/1
Eso capturará la función local nombrada hola con arity 1. En todo el lenguaje y su documentación, es muy común identificar funciones en esta hello/1
sintaxis.
Esta es también la razón por la cual Elixir usa un punto para llamar a funciones anónimas. Dado que no puede simplemente pasar hello
como una función, en su lugar necesita capturarla explícitamente, existe una distinción natural entre funciones con nombre y anónimas y una sintaxis distinta para llamar a cada una de ellas hace que todo sea un poco más explícito (Lispers estaría familiarizado con esto debido a la discusión Lisp 1 vs. Lisp 2).
En general, esas son las razones por las que tenemos dos funciones y por qué se comportan de manera diferente.
f()
(sin el punto).is_function/2
de un guardia.is_function(f, 2)
comprueba que tiene arity de 2. :)SomeFun()
ysome_fun()
. En Elixir, si eliminamos el punto, serían los mismossome_fun()
ysome_fun()
porque las variables usan el mismo identificador que los nombres de las funciones. De ahí el punto.No sé lo útil que será para alguien más, pero la forma en que finalmente comprendí el concepto fue darme cuenta de que las funciones de elixir no son funciones.
Todo en elixir es una expresión. Entonces
no es una función sino la expresión devuelta al ejecutar el código en
my_function
. En realidad, solo hay una forma de obtener una "Función" que puede pasar como argumento y es usar la notación de función anónima.Es tentador referirse a la notación fn o & como puntero de función, pero en realidad es mucho más. Es un cierre del entorno circundante.
Si te preguntas a ti mismo:
¿Necesito un entorno de ejecución o un valor de datos en este lugar?
Y si necesita ejecución, use fn, entonces la mayoría de las dificultades se vuelven mucho más claras.
fuente
MyModule.my_function(foo)
es una expresión, peroMyModule.my_function
"podría" haber sido una expresión que devuelve una función "objeto". Pero como necesitas contarle al arity, necesitarías algo asíMyModule.my_function/1
. Y supongo que decidieron que era mejor usar una&MyModule.my_function(&1)
sintaxis que permitiera expresar la aridad (y también sirve para otros propósitos). Todavía no está claro por qué hay un()
operador para funciones con nombre y un.()
operador para funciones sin nombre&MyModule.my_function/1
es como puedes pasarlo como una función.Nunca he entendido por qué las explicaciones de esto son tan complicadas.
Es realmente una distinción excepcionalmente pequeña combinada con las realidades de la "ejecución de funciones sin parentesco" al estilo Ruby.
Comparar:
A:
Si bien ambos son solo identificadores ...
fun1
es un identificador que describe una función con nombre definida condef
.fun2
es un identificador que describe una variable (que contiene una referencia a la función).¿Considera lo que eso significa cuando ves
fun1
ofun2
en alguna otra expresión? Al evaluar esa expresión, ¿llama a la función referenciada o simplemente hace referencia a un valor sin memoria?No hay una buena manera de saber en tiempo de compilación. Ruby tiene el lujo de introspectar el espacio de nombres de las variables para averiguar si un enlace variable ha sombreado una función en algún momento. Elixir, siendo compilado, realmente no puede hacer esto. Eso es lo que hace la notación de puntos, le dice a Elixir que debe contener una referencia de función y que debe llamarse.
Y esto es realmente difícil. Imagine que no hubiera una notación de puntos. Considera este código:
Dado el código anterior, creo que está bastante claro por qué tienes que darle una pista a Elixir. ¿Imagina si cada desreferencia de variables tuviera que verificar una función? Alternativamente, ¿imagina qué heroicidad sería necesaria para inferir siempre que la desreferencia variable estaba usando una función?
fuente
Puedo estar equivocado ya que nadie lo mencionó, pero también tenía la impresión de que la razón de esto también es la herencia rubí de poder llamar funciones sin corchetes.
Obviamente, Arity está involucrada, pero dejemos de lado por un tiempo y usemos funciones sin argumentos. En un lenguaje como javascript, donde los corchetes son obligatorios, es fácil hacer la diferencia entre pasar una función como argumento y llamar a la función. Lo llamas solo cuando usas los corchetes.
Como puede ver, nombrarlo o no no hace una gran diferencia. Pero el elixir y el rubí le permiten llamar a funciones sin los corchetes. Esta es una opción de diseño que personalmente me gusta, pero tiene este efecto secundario que no puede usar solo el nombre sin los corchetes porque podría significar que desea llamar a la función. Esto es para lo que
&
sirve. Si deja Arity aparte por un segundo, anteponer el nombre de su función&
significa que quiere explícitamente usar esta función como argumento, no lo que devuelve esta función.Ahora la función anónima es un poco diferente, ya que se usa principalmente como argumento. Nuevamente, esta es una opción de diseño, pero la razón detrás de esto es que es utilizada principalmente por el tipo de funciones iteradoras que toman funciones como argumentos. Obviamente, no es necesario usarlo
&
porque ya se consideran argumentos por defecto. Es su proposito.Ahora, el último problema es que a veces tiene que llamarlos en su código, porque no siempre se usan con un tipo de función de iterador, o podría estar codificando un iterador usted mismo. Para la pequeña historia, dado que el rubí está orientado a objetos, la forma principal de hacerlo era usar el
call
método en el objeto. De esa manera, podría mantener el comportamiento de los corchetes no obligatorios consistente.Ahora a alguien se le ocurrió un atajo que básicamente parece un método sin nombre.
Nuevamente, esta es una opción de diseño. Ahora el elixir no está orientado a objetos y, por lo tanto, no use la primera forma con seguridad. No puedo hablar por José, pero parece que la segunda forma se usó en elixir porque todavía parece una llamada de función con un carácter adicional. Está lo suficientemente cerca de una llamada de función.
No pensé en todos los pros y los contras, pero parece que en ambos idiomas podrías salirte con los corchetes siempre que los corchetes sean obligatorios para las funciones anónimas. Parece que es:
Paréntesis obligatorios VS notación ligeramente diferente
En ambos casos, hace una excepción porque hace que ambos se comporten de manera diferente. Como hay una diferencia, es mejor que lo hagas obvio e ir por la notación diferente. Los corchetes obligatorios se verían naturales en la mayoría de los casos, pero muy confusos cuando las cosas no salen según lo planeado.
Aqui tienes. Ahora, esta podría no ser la mejor explicación del mundo porque simplifiqué la mayoría de los detalles. Además, la mayoría son opciones de diseño y traté de darles una razón sin juzgarlas. Me encanta el elixir, me encanta el rubí, me gustan las llamadas de función sin paréntesis, pero como tú, las consecuencias me parecen bastante equivocadas de vez en cuando.
Y en el elixir, es solo este punto extra, mientras que en el rubí tienes bloques encima. Los bloques son increíbles y me sorprende lo mucho que puedes hacer con solo bloques, pero solo funcionan cuando necesitas una sola función anónima, que es el último argumento. Entonces, dado que debería poder lidiar con otros escenarios, aquí viene todo el método / lambda / proc / block confusion.
De todos modos ... esto está fuera de alcance.
fuente
Hay una excelente publicación de blog sobre este comportamiento: enlace
Dos tipos de funciones.
Punto al llamar
fn
fuente
Elixir tiene llaves opcionales para funciones, incluidas las funciones con 0 arity. Veamos un ejemplo de por qué hace importante una sintaxis de llamada separada:
Sin hacer una diferencia entre 2 tipos de funciones, no podemos decir qué
Insanity.dive
significa: obtener una función en sí misma, llamarla o también llamar a la función anónima resultante.fuente
fn ->
La sintaxis es para usar funciones anónimas. Hacer var. () Es solo decirle al elixir que quiero que tomes esa var con un func y la ejecutes en lugar de referirte a la var como algo que solo tiene esa función.Elixir tiene un patrón común en el que, en lugar de tener la lógica dentro de una función para ver cómo se debe ejecutar algo, el patrón coincide con diferentes funciones en función de qué tipo de entrada tenemos. Supongo que es por eso que nos referimos a las cosas por aridad en el
function_name/1
sentido.Es un poco extraño acostumbrarse a hacer definiciones de funciones abreviadas (func (& 1), etc.), pero útil cuando intenta canalizar o mantener su código conciso.
fuente
Tu puedes hacer
otherfunction(&myfunction/2)
Dado que elixir puede ejecutar funciones sin los corchetes (como
myfunction
), usarlootherfunction(myfunction))
intentará ejecutarsemyfunction/0
.Por lo tanto, debe usar el operador de captura y especificar la función, incluida la aridad, ya que puede tener diferentes funciones con el mismo nombre. Por lo tanto,
&myfunction/2
.fuente