Juste pour clarifier la dénomination, ce sont deux fonctions. L'une est une fonction nommée et l'autre est anonyme. Mais vous avez raison, ils fonctionnent un peu différemment et je vais illustrer pourquoi ils fonctionnent comme ça.
Commençons par le second fn
,. fn
est une fermeture, semblable à un lambda
Ruby. Nous pouvons le créer comme suit:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Une fonction peut également avoir plusieurs clauses:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Maintenant, essayons quelque chose de différent. Essayons de définir différentes clauses en attendant un nombre d'arguments différent:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
Oh non! Nous obtenons une erreur! Nous ne pouvons pas mélanger des clauses qui attendent un nombre différent d'arguments. Une fonction a toujours une arité fixe.
Maintenant, parlons des fonctions nommées:
def hello(x, y) do
x + y
end
Comme prévu, ils ont un nom et peuvent également recevoir des arguments. Cependant, ce ne sont pas des fermetures:
x = 1
def hello(y) do
x + y
end
Ce code ne parviendra pas à compiler car chaque fois que vous voyez un def
, vous obtenez une portée de variable vide. C'est une différence importante entre eux. J'aime particulièrement le fait que chaque fonction nommée commence par une table rase et que vous ne mélangez pas les variables de différentes étendues. Vous avez une frontière claire.
Nous pourrions récupérer la fonction hello nommée ci-dessus en tant que fonction anonyme. Vous en avez parlé vous-même:
other_function(&hello(&1))
Et puis vous avez demandé, pourquoi je ne peux pas simplement le passer hello
comme dans d'autres langues? C'est parce que les fonctions dans Elixir sont identifiées par leur nom et leur arité. Ainsi, une fonction qui attend deux arguments est une fonction différente de celle qui attend trois, même si elles avaient le même nom. Donc, si nous passions simplement hello
, nous n'aurions aucune idée de ce que hello
vous vouliez réellement dire. Celui avec deux, trois ou quatre arguments? C'est exactement la même raison pour laquelle nous ne pouvons pas créer une fonction anonyme avec des clauses d'arités différentes.
Depuis Elixir v0.10.1, nous avons une syntaxe pour capturer les fonctions nommées:
&hello/1
Cela capturera la fonction nommée locale bonjour avec arity 1. Dans tout le langage et sa documentation, il est très courant d'identifier les fonctions dans cette hello/1
syntaxe.
C'est aussi pourquoi Elixir utilise un point pour appeler des fonctions anonymes. Comme vous ne pouvez pas simplement passer hello
comme une fonction, vous devez plutôt la capturer explicitement, il existe une distinction naturelle entre les fonctions nommées et anonymes et une syntaxe distincte pour appeler chacune rend tout un peu plus explicite (Lispers serait familier avec cela en raison de la discussion Lisp 1 vs Lisp 2).
Dans l'ensemble, ce sont les raisons pour lesquelles nous avons deux fonctions et pourquoi elles se comportent différemment.
f()
(sans le point).is_function/2
dans un garde.is_function(f, 2)
vérifie qu'il a une arité de 2. :)SomeFun()
etsome_fun()
. Dans Elixir, si nous supprimions le point, ils seraient les mêmessome_fun()
etsome_fun()
parce que les variables utilisent le même identifiant que les noms de fonction. D'où le point.Je ne sais pas à quel point cela sera utile à quelqu'un d'autre, mais la façon dont j'ai finalement pensé au concept était de réaliser que les fonctions d'élixir ne sont pas des fonctions.
Tout dans l'élixir est une expression. Alors
n'est pas une fonction mais l'expression renvoyée en exécutant le code dans
my_function
. Il n'y a en fait qu'une seule façon d'obtenir une "fonction" que vous pouvez passer comme argument et qui est d'utiliser la notation de fonction anonyme.Il est tentant de se référer à la notation fn ou & comme un pointeur de fonction, mais c'est en fait beaucoup plus. C'est une fermeture de l'environnement environnant.
Si vous vous demandez:
Ai-je besoin d'un environnement d'exécution ou d'une valeur de données à cet endroit?
Et si vous avez besoin d'une exécution, utilisez fn, alors la plupart des difficultés deviennent beaucoup plus claires.
la source
MyModule.my_function(foo)
c'est une expression maisMyModule.my_function
"pourrait" être une expression qui renvoie une fonction "objet". Mais puisque vous devez dire à l'arité, vous auriez besoin de quelque chose comme à laMyModule.my_function/1
place. Et je suppose qu'ils ont décidé qu'il valait mieux utiliser une&MyModule.my_function(&1)
syntaxe à la place qui permet d'exprimer l'arité (et sert également à d'autres fins). On ne sait toujours pas pourquoi il existe un()
opérateur pour les fonctions nommées et un.()
opérateur pour les fonctions non nommées&MyModule.my_function/1
c'est comment vous pouvez le faire passer en fonction.Je n'ai jamais compris pourquoi les explications sont si compliquées.
C'est vraiment juste une distinction exceptionnellement petite combinée avec les réalités de "l'exécution de fonctions sans parens" de style Ruby.
Comparer:
À:
Bien que ces deux éléments ne soient que des identifiants ...
fun1
est un identifiant qui décrit une fonction nommée définie avecdef
.fun2
est un identifiant qui décrit une variable (qui contient une référence à une fonction).Considérez ce que cela signifie lorsque vous voyez
fun1
oufun2
dans une autre expression? Lorsque vous évaluez cette expression, appelez-vous la fonction référencée ou faites-vous simplement référence à une valeur hors de la mémoire?Il n'y a aucun bon moyen de savoir au moment de la compilation. Ruby a le luxe d'introspectionner l'espace de noms de variable pour savoir si une liaison de variable a masqué une fonction à un moment donné. Elixir, en cours de compilation, ne peut pas vraiment faire ça. C'est ce que fait la notation par points, elle indique à Elixir qu'elle doit contenir une référence de fonction et qu'elle doit être appelée.
Et c'est vraiment difficile. Imaginez qu'il n'y ait pas de notation par points. Considérez ce code:
Compte tenu du code ci-dessus, je pense qu'il est assez clair pourquoi vous devez donner un indice à Elixir. Imaginez si chaque dé-référence variable devait vérifier une fonction? Alternativement, imaginez quelle héroïque serait nécessaire pour toujours déduire que la déréférence variable utilisait une fonction?
la source
Je peux me tromper car personne ne l'a mentionné, mais j'avais également l'impression que la raison en est également l'héritage rubis de pouvoir appeler des fonctions sans crochets.
Arity est évidemment impliqué mais laisse le mettre de côté pendant un moment et utilise des fonctions sans arguments. Dans un langage comme javascript où les crochets sont obligatoires, il est facile de faire la différence entre passer une fonction en argument et appeler la fonction. Vous l'appelez uniquement lorsque vous utilisez les crochets.
Comme vous pouvez le voir, le nommer ou non ne fait pas une grande différence. Mais l'élixir et le rubis vous permettent d'appeler des fonctions sans les crochets. C'est un choix de conception que j'aime personnellement, mais il a cet effet secondaire que vous ne pouvez pas utiliser uniquement le nom sans les crochets, car cela pourrait signifier que vous souhaitez appeler la fonction. C'est pour ça
&
. Si vous laissez arity appart pendant une seconde, le fait d'ajouter le nom de votre fonction&
signifie que vous souhaitez explicitement utiliser cette fonction comme argument, et non ce qu'elle renvoie.Maintenant, la fonction anonyme est un peu différente en ce qu'elle est principalement utilisée comme argument. Encore une fois, c'est un choix de conception, mais le raisonnement derrière cela est qu'il est principalement utilisé par les itérateurs du type de fonctions qui prennent des fonctions comme arguments. Vous n'avez donc évidemment pas besoin de les utiliser
&
car ils sont déjà considérés comme des arguments par défaut. C'est leur but.Maintenant, le dernier problème est que parfois vous devez les appeler dans votre code, car ils ne sont pas toujours utilisés avec une fonction de type itérateur, ou vous pouvez coder vous-même un itérateur. Pour la petite histoire, puisque ruby est orienté objet, la principale façon de le faire était d'utiliser la
call
méthode sur l'objet. De cette façon, vous pouvez garder le comportement des crochets non obligatoires cohérent.Maintenant, quelqu'un a trouvé un raccourci qui ressemble à une méthode sans nom.
Encore une fois, c'est un choix de conception. Maintenant, l'élixir n'est pas orienté objet et donc appelez à ne pas utiliser le premier formulaire à coup sûr. Je ne peux pas parler pour José mais il semble que la deuxième forme ait été utilisée dans l'élixir car elle ressemble toujours à un appel de fonction avec un caractère supplémentaire. C'est assez proche d'un appel de fonction.
Je n'ai pas pensé à tous les avantages et les inconvénients, mais il semble que dans les deux langues, vous pouvez vous en tirer avec les crochets tant que vous rendez les crochets obligatoires pour les fonctions anonymes. Il semble que ce soit:
Crochets obligatoires VS Notation légèrement différente
Dans les deux cas, vous faites une exception car vous faites en sorte que les deux se comportent différemment. Puisqu'il y a une différence, vous pourriez aussi bien la rendre évidente et opter pour la notation différente. Les parenthèses obligatoires sembleraient naturelles dans la plupart des cas, mais très déroutantes lorsque les choses ne se déroulent pas comme prévu.
Voici. Maintenant, ce n'est peut-être pas la meilleure explication au monde car j'ai simplifié la plupart des détails. De plus, la plupart sont des choix de conception et j'ai essayé de leur donner une raison sans les juger. J'aime l'élixir, j'aime le rubis, j'aime les appels de fonction sans crochets, mais comme vous, je trouve les conséquences assez erronées de temps en temps.
Et dans l'élixir, c'est juste ce point supplémentaire, alors qu'en rubis vous avez des blocs en plus. Les blocs sont incroyables et je suis surpris de voir tout ce que vous pouvez faire avec seulement des blocs, mais ils ne fonctionnent que lorsque vous avez besoin d'une seule fonction anonyme qui est le dernier argument. Ensuite, puisque vous devriez être capable de gérer d'autres scénarios, voici toute la confusion méthode / lambda / proc / block.
Quoi qu'il en soit ... cela est hors de portée.
la source
Il y a un excellent article de blog sur ce comportement: lien
Deux types de fonctions
Point dans l'appel
fn
la source
Elixir a des accolades optionnelles pour les fonctions, y compris les fonctions avec 0 arité. Voyons un exemple de la raison pour laquelle il est important d'avoir une syntaxe d'appel distincte:
Sans faire la différence entre 2 types de fonctions, nous ne pouvons pas dire ce que cela
Insanity.dive
signifie: obtenir une fonction elle-même, l'appeler ou également appeler la fonction anonyme résultante.la source
fn ->
la syntaxe permet d'utiliser des fonctions anonymes. Faire var. () Est juste dire à elixir que je veux que vous preniez ce var avec un func dedans et que vous le lanciez au lieu de faire référence au var comme quelque chose qui tient juste cette fonction.Elixir a ce modèle commun où, au lieu d'avoir une logique à l'intérieur d'une fonction pour voir comment quelque chose doit s'exécuter, nous adaptons différentes fonctions en fonction du type d'entrée que nous avons. Je suppose que c'est pourquoi nous nous référons aux choses par arité dans le
function_name/1
sens.C'est un peu bizarre de s'habituer à faire des définitions de fonctions abrégées (func (& 1), etc.), mais pratique lorsque vous essayez de diriger ou de garder votre code concis.
la source
Tu peux faire
otherfunction(&myfunction/2)
Comme elixir peut exécuter des fonctions sans les crochets (comme
myfunction
), son utilisationotherfunction(myfunction))
tentera de s'exécutermyfunction/0
.Vous devez donc utiliser l'opérateur de capture et spécifier la fonction, y compris l'arity, car vous pouvez avoir différentes fonctions avec le même nom. Ainsi,
&myfunction/2
.la source