Valeurs fonctionnelles
fun F/A, fun M:F/A, funs anonymes avec variables capturées (fermetures,
closures), funs nommées (fun Name(...) -> ... end), appels de valeurs
fonctionnelles (F(Args)) et appels dynamiques (M:F(Args), apply/2,3). Les
faits proviennent des sources épinglées de maint-29
(erts/emulator/beam/utils.c erts_cmp, erl_printf_term.c, erl_lint) et
de sondages sur OTP 29.1.1.
Valeurs
| Source | Valeur | Les appels entrent dans |
|---|---|---|
fun f/1 | Fun locale de ce module ; chaque fun f/1 d'un module est la même valeur | f/1 de ce module, exportée ou non |
fun m:f/1 | Fun externe désignant m:f/1, y compris pour le module courant | m:f/1 quand un module du programme l'exporte |
fun(X) -> ... end | Fun locale capturant les variables qu'elle lit chez son créateur ; chaque évaluation construit une nouvelle valeur | La fonction générée propre à la fun |
fun Name(X) -> ... end | Idem, avec Name lié à la fun à l'intérieur de ses clauses | La fonction générée propre à la fun |
fun M:F/A avec des variables | Fun externe construite à l'évaluation ; égale à la fun littérale fun m:f/1 qu'elle désigne | m:f/1 quand un module du programme l'exporte |
fun F/Adoit désigner une fonction du module (function F/A undefined) ou un builtin auto-importé :fun is_atom/1est la fun externeerlang:is_atom/1. Les funs de builtins les appellent via le pont des builtins ; unefun erlang:F/Alittérale d'une fonction hors du catalogue (fun erlang:apply/2) signale la capacité indisponibledynamic calls.F(Args)évalueF, puis les arguments de gauche à droite, puis vérifie la valeur : une non-fonction lève{badfun, F}, une autre arité{badarity, {F, Args}}(vérifiée avant le module), une fun externe dont aucun module du programme n'exporte la fonctionundef. Un appel en position terminale est un appel terminal, comme pour les fonctions nommées.is_function/1,2sont vraies pour les funs dans les corps et les guards.
Appels dynamiques
M:F(Args)avec une variable (ou toute expression) comme module ou fonction évalue le module, puis la fonction, puis les arguments de gauche à droite. Un module ou une fonction qui n'est pas un atome lèvebadarg; une fonction qu'aucun module du programme n'exporte avec cette arité lèveundef, avec{M, F, Arity, []}en tête de sa trace de pile (OTP montre les arguments ;apply/3et les appels de funs externes les montrent aussi). Un appel en position terminale est un appel terminal.apply(Fun, Args)etapply(M, F, Args)(auto-importées sauf si le module définitapply/2,3ou supprime l'import ; égalementerlang:apply/2,3) évaluent leurs arguments, puis exigent queArgssoit une liste propre (badarg).apply/2appelle ensuiteFuncomme le faitF(Args)({badfun, Fun},{badarity, {Fun, Args}}) ;apply/3rechercheM:F/length(Args)comme ci-dessus, donc plus de 255 arguments lèventundef. Les deux sont des appels terminaux en position terminale et ne sont jamais légaux dans les guards.erlang:apply/2,3atteintes par leur nom à l'exécution (spawn(erlang, apply, [F, Args]),M:apply(...)avecMlié àerlang, l'action d'un minuteur) sont des builtins (builtins/erlang/apply.cpp) qui poursuivent dans la fonction appelée avec les mêmes erreurs.fun M:F/Aavec des variables lèvebadargsauf siMetFsont des atomes etAun entier dans 0..255 ; la fonction n'a pas besoin d'exister avant l'appel de la fun.- La recherche utilise les index par hachage des exports du programme tenus
par le serveur de code (runtime), qui
restent enregistrés pendant toute la durée de vie du programme, de sorte
qu'une fonction trouvée ne peut pas disparaître pendant l'appel. Un nom
qu'aucun module n'exporte est ensuite recherché parmi les
builtins, de sorte que
M:F(...),apply/3etfun M:F/Aà l'exécution atteignent les builtins du catalogue du pont. - Services.
CLAUSE_call_v1(context, module, function, arity)vérifie les noms et renvoie leFrameDescriptorde l'export (tiré de sonExportDescriptor) ; les arguments sont déjà dans les registres.CLAUSE_apply_list_v1(context, fun, list, registers)etCLAUSE_call_list_v1(context, module, function, list, registers)copient d'abord la liste dans les registres. Les trois renvoient null après avoir consigné l'erreur, et le code généré effectue alors un transfert via le marqueurclause.applycomme pourF(Args).CLAUSE_make_external_fun_v1construit la fun d'uneFunDefinitionexterne que le serveur de code crée une seule fois parM:F/A(CodeServer::external_fun).
Fermetures
- Chaque clause part de la portée au niveau de la fun. Les variables de tête sont de nouveaux noms qui masquent les noms extérieurs (OTP avertit) ; un nom répété dans une même tête doit correspondre. Les guards lisent les noms de la tête. Rien de ce qu'une fun lie n'est visible après elle, et les noms des clauses de case de la fonction englobante n'y pénètrent pas.
- Une fun capture toute définition extérieure que ses têtes, guards ou corps lisent (funs imbriquées comprises), dans l'ordre de définition, comme OTP ordonne les variables libres d'une fonction. Les valeurs capturées sont copiées dans la cellule de la fun lors de l'évaluation de l'expression fun, de sorte qu'elles survivent au retour du créateur et aux ramassages, et sont copiées avec la fun.
- Le code est une fonction privée
-f/A-fun-N-(N compte les funs def/Adans l'ordre du source) qui prend les arguments de la fun, puis ses valeurs capturées ; les traces de pile montrent ce nom avec l'arité combinée, comme celles d'OTP. L'absence de clause correspondante lèvefunction_clause. - Les arguments plus les valeurs capturées sont limités à 255.
- Les clauses d'une fun nommée voient
Namecomme la fun elle-même : un nouveau nom qui masque un nom extérieur (OTP avertit), jamais capturé et invisible après la fun. Une variable de tête du même nom le masque à son tour. Quand une clause litName, le code de la fun construit la valeur à l'entrée à partir de ses valeurs capturées, de sorte qu'elle est égale (=:=) à la fun appelée, etName(...)est un appel de fun ordinaire : en position terminale, c'est un appel terminal qui s'exécute en pile constante. - Une fun anonyme dans la valeur par défaut d'un champ de record est une seule fun pour toutes les constructions qui utilisent la valeur par défaut (OTP développe une copie par site).
Funs développées en ligne
À -O2 (codegen/lowering_fun_inlining.cpp, lowering_list_loops.cpp,
lowering_map_loops.cpp), un appel de fun anonyme connue au point d'appel
développe les clauses de la fun à la place de l'appel, comme le fait l'option
inline_list_funcs d'OTP :
- Funs connues. Une fun écrite dans l'appel (
(fun(X) -> ... end)(Y), ou le premier argument d'une boucle de liste ci-dessous), ou une variable liée une seule fois à une fun anonyme sans nom (F = fun ... end) qui est lue une seule fois ou contient au plus 40 expressions. Dans le code d'une autre fun, la fun d'une variable n'est développée en ligne que si toutes ses captures y sont aussi liées. Les corps développés s'imbriquent sur quatre niveaux au plus ; les boucles sur binary et flottants ne développent rien en ligne. - Clauses sur place. Les arguments sont confrontés aux clauses et aux
guards de la fun, et le corps de la clause correspondante s'exécute dans la
trame de l'appelant. Si aucune clause ne correspond, la fun est appelée,
donc
function_clauseprovient de son propre code comme auparavant. Une fun écrite dans l'appel n'est construite que sur ce chemin et sur les chemins ci-dessous qui ont besoin de sa valeur (construction différée de fermeture) ; la fun d'une variable est construite à sa liaison, comme auparavant. - Boucles de liste.
lists:map/2,filter/2,foreach/2,all/2,any/2,flatmap/2,foldl/3,foldr/3,mapfoldl/3etmapfoldr/3du modulelistspropre à Clause, avec une fun connue de la bonne arité, deviennent une boucle sur la liste dans la fonction appelante. L'appel de fun de chaque élément est développé en ligne ; les listes sont construites à l'envers et inversées une fois à la fin ;foldr/3etmapfoldr/3inversent d'abord leur entrée, donc leur fun voit toujours les éléments à partir du dernier, après l'échec sur une queue impropre. Comme la récursion non terminale qu'elles remplacent, les boucles de liste ne font de ramassage qu'aux points de cession (tous les 4 000 éléments sans autres appels) et dans les appels de leur fun. - Boucles de pliage de map.
maps:fold/3du modulemapspropre à Clause, avec une fun connue d'arité 3, devient une boucle sur les entrées de la map (la cellule de map classique, ou les entrées d'un arbre copiées une fois dans une cellule en forme de tuple, comme les litmaps:fold/3), dans l'ordre des clés commemaps:fold/3. Un itérateur de map ou une valeur autre qu'une map déclenche le véritable appelmaps, qui le traite ou lève sa propre erreur. - Accumulateurs possédés. Quand chaque clause d'une fun de pliage
(
maps:fold/3,lists:foldl/3,lists:foldr/3) renvoie son paramètre accumulateur mis à jour,fun(K, V, Acc) -> Acc#{K := V + 1} endavecAcclu nulle part ailleurs et capturé par aucune fun, la boucle possède le résultat de chaque étape : à partir de la deuxième étape, la mise à jour est possédée et modifie la map sur place (arbres de maps). La première étape copie, car l'accumulateur initial peut être détenu ailleurs. - Mêmes résultats et raisons. Les valeurs, l'ordre d'exécution de la fun
et les raisons d'erreur sont ceux de
lists: une entrée autre qu'une liste pourmap/2,foldl/3,all/2,any/2et les queues impropres defilter/2,foreach/2,foldr/3,mapfoldl/3,mapfoldr/3déclenchent le véritable appellists, qui lève sa propre erreur ; une queue impropre demap/2,foldl/3,all/2,any/2,flatmap/2lèvefunction_clausedans l'appelant. Un prédicat non booléen lève{case_clause, V}(all/2,any/2) ou{bad_filter, V}(filter/2), un résultat autre qu'une paire d'une funmapfoldlève{badmatch, V}. - Les traces de pile des exceptions dans les funs développées en ligne
omettent les trames de la fun et de l'auxiliaire
listsoumaps(différences).
Représentation
- Descripteur. Chaque valeur distincte qu'un module crée se compile en un
abi::v1::FunDescriptordans la table privée<prefix>.funsdu module (funs.hpp) : descripteur de module, emplacements d'atomes du module et de la fonction, arité Erlang, index, indicateur externe et leFrameDescriptordans lequel entre un appel (null pour une fun externe hors du programme). L'enregistrement les lie à desFunDefinition(ModuleAtoms::funs,CodeServer::fun_definition) ; le nombre de valeurs capturées d'une fun locale est l'arité de son code moins celle de la fun. - Cellule. Type boxé
fun_closure: en-tête (nombre1 + n), unconst FunDefinition *non tracé, puisnvaleurs capturées. Le parcours, le ramassage, la copie et la vérification sautent le mot de définition, comme pour les native records. - Services.
CLAUSE_make_fun_v1(context, descriptor, captures, count, output)construit une fun.CLAUSE_apply_v1(context, fun, arity, arguments)vérifie une valeur appelée, consigne les erreurs ci-dessus dans le canal d'échec (ErrorReason22-24) et sinon ajoute les valeurs capturées après les arguments et renvoie leFrameDescriptordans lequel entrer. - Fermetures inline. Le runtime qui revendique le cache d'atomes d'un
module remplit aussi
ModuleDescriptor::fun_cacheavec l'adresse de la définition de chaque fun ; tant que la revendication est la sienne, le code généré alloue lui-même la cellule de la fun (en-tête, définition, valeurs capturées) par incrément de pointeur et n'appelleCLAUSE_make_fun_v1qu'en l'absence de place sur le tas ou dans un autre runtime.CLAUSE_apply_v1etCLAUSE_apply_list_v1lisent une fun de la bonne arité (et la liste d'arguments) directement dans ses cellules avant le chemin vérifié qui signale les erreurs. - Appels. La forme native passe les arguments dans un tableau de mots et
appelle le marqueur
clause.applyavec le descripteur ;lower_framesfait du tableau les registres du processus et du marqueur une entrée ou, en position terminale, un transfert terminal (modèle d'exécution).
Comparaison et affichage
- Les funs se rangent après les références et avant les ports (ordre des termes).
- Les funs locales se rangent avant les funs externes. Les funs locales se
comparent par module, puis index, puis valeurs capturées dans l'ordre (
==les compare avec==) ; les funs externes par module, fonction et arité. Des descripteurs égaux donnent des valeurs égales, doncfun f/1 =:= fun f/1. erlang:display/1,~wet les rapports d'exception non capturée affichent une fun externe sous la formefun m:f/1(atomes entre guillemets comme le fait l'émulateur) et une fun locale sous la forme#Fun<m.Index.0>.
Différences
La numérotation, l'affichage et l'ordre des funs, l'ordre de capture dans les
comprehensions, les traces de pile undef et les appels de modules hors du
programme diffèrent d'OTP comme consigné dans différences.
Informations sur les funs
runtime/src/builtins/erlang/fun_info.cpp. erlang:fun_info/1 liste les
éléments d'OTP dans l'ordre d'OTP : pour une fun locale pid, module,
new_index, new_uniq, index, uniq, name (-F/A-fun-N- pour une fun
anonyme), arity, env (les valeurs capturées) et type ; pour
fun M:F/A module, name, arity, env ([]) et type. fun_info/2
renvoie un seul {Item, Value} ; un élément inconnu donne badarg. pid
vaut undefined, new_uniq est le MD5 du nom du module et uniq ses 24
premiers bits, de sorte qu'ils diffèrent de ceux d'OTP. fun_to_list/1 est le
texte ~w. is_builtin/3 est vrai pour les builtins du runtime et
erlang:apply/2,3.
Clause