Valores de função
fun F/A, fun M:F/A, funs anónimas com variáveis capturadas (closures), funs
com nome (fun Name(...) -> ... end), chamadas de valores de função
(F(Args)) e chamadas dinâmicas (M:F(Args), apply/2,3). Os factos vêm dos
códigos-fonte fixados maint-29 (erts/emulator/beam/utils.c erts_cmp,
erl_printf_term.c, erl_lint) e de sondagens no OTP 29.1.1.
Valores
| Origem | Valor | As chamadas entram em |
|---|---|---|
fun f/1 | Fun local deste módulo; todos os fun f/1 de um módulo são o mesmo valor | f/1 deste módulo, exportada ou não |
fun m:f/1 | Fun externa que designa m:f/1, também para o módulo atual | m:f/1 quando um módulo do programa a exporta |
fun(X) -> ... end | Fun local que captura as variáveis que lê do seu criador; cada avaliação constrói um novo valor | A função gerada própria da fun |
fun Name(X) -> ... end | O mesmo, com Name associado à fun dentro das suas cláusulas | A função gerada própria da fun |
fun M:F/A com variáveis | Fun externa construída ao ser avaliada; igual à fun literal fun m:f/1 que designa | m:f/1 quando um módulo do programa a exporta |
fun F/Atem de designar uma função do módulo (function F/A undefined) ou um builtin auto-importado:fun is_atom/1é a fun externaerlang:is_atom/1. As funs de builtins chamam-nos através da ponte de builtins; umfun erlang:F/Aliteral de uma função fora do catálogo (fun erlang:apply/2) indica a capacidadedynamic callsindisponível.F(Args)avaliaF, depois os argumentos da esquerda para a direita, e depois verifica o valor: algo que não seja uma função lança{badfun, F}, outra aridade{badarity, {F, Args}}(verificado antes do módulo), uma fun externa cuja função nada no programa exportaundef. Uma chamada em posição de cauda é uma chamada de cauda, como para as funções com nome.is_function/1,2são verdadeiros para funs em corpos e em guards.
Chamadas dinâmicas
M:F(Args)com uma variável (ou qualquer expressão) como módulo ou função avalia o módulo, depois a função, depois os argumentos da esquerda para a direita. Um módulo ou função que não seja um átomo lançabadarg; uma função que nenhum módulo do programa exporte com essa aridade lançaundef, com{M, F, Arity, []}no topo do seu rasto de pilha (o OTP mostra os argumentos;apply/3e chamadas de funs externas também os mostram). Uma chamada em posição de cauda é uma chamada de cauda.apply(Fun, Args)eapply(M, F, Args)(auto-importados, a não ser que o módulo definaapply/2,3ou suprima a importação; tambémerlang:apply/2,3) avaliam os seus argumentos e depois exigem queArgsseja uma lista própria (badarg).apply/2chama entãoFuncomo fazF(Args)({badfun, Fun},{badarity, {Fun, Args}});apply/3procuraM:F/length(Args)como acima, pelo que mais de 255 argumentos lançamundef. Ambos são chamadas de cauda em posição de cauda e nunca são legais em guards.erlang:apply/2,3alcançados por nome em tempo de execução (spawn(erlang, apply, [F, Args]),M:apply(...)comMassociado aerlang, uma ação de temporizador) são builtins (builtins/erlang/apply.cpp) que continuam na função chamada com os mesmos erros.fun M:F/Acom variáveis lançabadarg, a não ser queMeFsejam átomos eAum inteiro em 0..255; a função não precisa de existir até a fun ser chamada.- A pesquisa usa os índices de hash do code server para os exports do programa
(runtime), que se mantêm registados
durante toda a vida do programa, pelo que uma função encontrada não pode
desaparecer durante a chamada. Um nome que nenhum módulo exporta é depois
procurado entre os builtins, pelo que
M:F(...),apply/3efun M:F/Aem tempo de execução chegam aos builtins do catálogo da ponte. - Serviços.
CLAUSE_call_v1(context, module, function, arity)verifica os nomes e devolve oFrameDescriptordo export (a partir do seuExportDescriptor); os argumentos já estão nos registos.CLAUSE_apply_list_v1(context, fun, list, registers)eCLAUSE_call_list_v1(context, module, function, list, registers)copiam primeiro a lista para os registos. Os três devolvem null depois de registarem o erro, e o código gerado transfere então o controlo através do marcadorclause.apply, como paraF(Args).CLAUSE_make_external_fun_v1constrói a fun de umaFunDefinitionexterna que o code server cria uma vez porM:F/A(CodeServer::external_fun).
Closures
- Cada cláusula parte do âmbito no ponto da fun. As variáveis da cabeça são nomes novos que ocultam os exteriores (o OTP avisa); um nome repetido numa mesma cabeça tem de corresponder. As guards leem os nomes da cabeça. Nada do que uma fun associa é visível depois dela, e os nomes das cláusulas case da função envolvente não chegam ao seu interior.
- Uma fun captura todas as definições exteriores que as suas cabeças, guards ou corpos leem (incluindo funs aninhadas), pela ordem de definição, tal como o OTP ordena as variáveis livres de uma função. Os valores capturados são copiados para a célula da fun quando a expressão fun é avaliada, pelo que sobrevivem ao regresso do criador e às recolhas, e são copiados com a fun.
- O código é uma função privada
-f/A-fun-N-(N conta as funs def/Apela ordem do código-fonte) que recebe os argumentos da fun e depois os seus valores capturados; os stack traces mostram esse nome com a aridade combinada, como os do OTP. Se nenhuma cláusula corresponder, é lançadofunction_clause. - Os argumentos mais os valores capturados estão limitados a 255.
- As cláusulas de uma fun com nome veem
Namecomo a própria fun: um nome novo que oculta um exterior (o OTP avisa), nunca capturado e não visível depois da fun. Uma variável da cabeça com o mesmo nome oculta-o por sua vez. Quando uma cláusula lêName, o código da fun constrói o valor à entrada a partir dos seus valores capturados, pelo que é igual (=:=) à fun que está a ser chamada, eName(...)é uma chamada de fun normal: em posição de cauda é uma chamada de cauda e corre em pilha constante. - Uma fun anónima no valor por omissão de um campo de record é uma única fun para todas as construções que usam esse valor (o OTP expande uma cópia por local).
Funs expandidas inline
Em -O2 (codegen/lowering_fun_inlining.cpp, lowering_list_loops.cpp,
lowering_map_loops.cpp), uma chamada de uma fun anónima conhecida no local
onde é chamada rebaixa as cláusulas da fun no lugar da chamada, de forma
semelhante à opção inline_list_funcs do OTP:
- Funs conhecidas. Uma fun escrita na chamada (
(fun(X) -> ... end)(Y), ou o primeiro argumento de um ciclo de lista abaixo), ou uma variável associada uma vez a uma fun anónima sem nome (F = fun ... end) que seja lida uma vez ou contenha no máximo 40 expressões. Dentro do código de outra fun, a fun de uma variável só é expandida inline quando tudo o que captura também está associado aí. Os corpos expandidos inline aninham-se até quatro níveis; os ciclos de binaries e de números de vírgula flutuante não expandem nada inline. - Cláusulas no lugar da chamada. Os argumentos são comparados com as
cláusulas e guards da fun, e o corpo da cláusula correspondente executa no
frame do chamador. Quando nenhuma cláusula corresponde, a fun é chamada,
pelo que
function_clausevem do código da própria fun como antes. Uma fun escrita na chamada só é construída nesse caminho e nos caminhos abaixo que precisam do seu valor (adiamento da construção da closure); a fun de uma variável é construída onde é associada, como antes. - Ciclos de listas.
lists:map/2,filter/2,foreach/2,all/2,any/2,flatmap/2,foldl/3,foldr/3,mapfoldl/3emapfoldr/3do próprio módulolistsdo Clause com uma fun conhecida da aridade certa tornam-se um ciclo sobre a lista na função chamadora. A chamada da fun de cada elemento é expandida inline; as listas são construídas ao contrário e invertidas uma vez no fim;foldr/3emapfoldr/3invertem primeiro a entrada, pelo que a fun continua a ver os elementos a partir do último, depois de uma cauda imprópria ter falhado. Como a recursão no corpo que substituem, os ciclos de listas só fazem recolha onde cedem a execução (a cada 4,000 elementos sem outras chamadas) e nas chamadas feitas pela fun. - Ciclos de redução de maps.
maps:fold/3do próprio módulomapsdo Clause com uma fun conhecida de aridade 3 torna-se um ciclo sobre as entradas do map (a célula clássica de map, ou as entradas de uma árvore copiadas uma vez para uma célula com forma de tuplo, comomaps:fold/3as lê), por ordem de chave comomaps:fold/3. Um iterador de map ou um valor que não seja map faz a chamada real amaps, que o trata ou lança o seu próprio erro. - Acumuladores com posse exclusiva. Quando todas as cláusulas de uma fun
de redução (
maps:fold/3,lists:foldl/3,lists:foldr/3) devolvem o seu parâmetro acumulador atualizado,fun(K, V, Acc) -> Acc#{K := V + 1} endcomAccnão lido em mais nenhum sítio e não capturado por nenhuma fun, o ciclo tem a posse exclusiva do resultado de cada passo: a partir do segundo passo, a atualização tem posse exclusiva e altera o map diretamente (árvores de maps). O primeiro passo copia, pois o acumulador inicial pode estar guardado noutro sítio. - Mesmos resultados e motivos. Os valores, a ordem em que a fun executa e
os motivos de erro são os de
lists: uma entrada que não seja lista demap/2,foldl/3,all/2,any/2e as caudas impróprias defilter/2,foreach/2,foldr/3,mapfoldl/3,mapfoldr/3fazem a chamada real alists, que lança o seu próprio erro; uma cauda imprópria demap/2,foldl/3,all/2,any/2,flatmap/2lançafunction_clauseno chamador. Um predicado não booleano lança{case_clause, V}(all/2,any/2) ou{bad_filter, V}(filter/2), um resultado que não seja par de uma fun demapfoldlança{badmatch, V}. - Rastos de pilha de exceções dentro de funs expandidas inline não incluem
os frames da fun nem do auxiliar de
listsoumaps(diferenças).
Representação
- Descritor. Cada valor distinto que um módulo cria compila para um
abi::v1::FunDescriptorna tabela privada<prefix>.funsdo módulo (funs.hpp): descritor do módulo, slots dos átomos do módulo e da função, aridade Erlang, índice, flag de externa e oFrameDescriptorem que uma chamada entra (null para uma fun externa fora do programa). O registo associa-os aFunDefinitions (ModuleAtoms::funs,CodeServer::fun_definition); o número de valores capturados de uma fun local é a aridade do seu código menos a da fun. - Célula. Tipo boxed
fun_closure: cabeçalho (contagem1 + n), umconst FunDefinition *não rastreado e depoisnvalores capturados. O percurso, a recolha, a cópia e a verificação saltam a palavra da definição, como para os native records. - Serviços.
CLAUSE_make_fun_v1(context, descriptor, captures, count, output)constrói uma fun.CLAUSE_apply_v1(context, fun, arity, arguments)verifica um valor chamado, regista os erros acima no canal de falhas (ErrorReason22-24) e, caso contrário, acrescenta os valores capturados depois dos argumentos e devolve oFrameDescriptorem que entrar. - Closures inline. O runtime que reivindica a cache de átomos de um módulo
também preenche
ModuleDescriptor::fun_cachecom o endereço da definição de cada fun; enquanto a reivindicação for sua, o código gerado aloca ele próprio a célula da fun por incremento de ponteiro (cabeçalho, definição, valores capturados) e chamaCLAUSE_make_fun_v1apenas quando não há espaço no heap ou noutro runtime.CLAUSE_apply_v1eCLAUSE_apply_list_v1leem uma fun com a aridade certa (e a lista de argumentos) diretamente das suas células antes do caminho verificado que comunica os erros. - Chamadas. A forma nativa passa os argumentos num array de palavras e chama
o marcador
clause.applycom o descritor;lower_framesfaz do array os registos do processo e do marcador uma entrada ou, em posição de cauda, uma transferência de cauda (modelo de execução).
Comparação e impressão
- As funs ordenam-se depois das referências e antes dos ports (ordem dos termos).
- As funs locais ordenam-se antes das funs externas. As funs locais comparam-se
por módulo, depois por índice, depois pelos valores capturados por ordem
(
==compara-os com==); as funs externas por módulo, função e aridade. Descritores iguais dão valores iguais, pelo quefun f/1 =:= fun f/1. erlang:display/1,~we os relatórios de exceções não apanhadas imprimem uma fun externa comofun m:f/1(átomos entre aspas como faz o emulador) e uma fun local como#Fun<m.Index.0>.
Diferenças
A numeração, a impressão e a ordenação das funs, a ordem de captura em
comprehensions, os stack traces de undef e as chamadas de módulos fora do
programa diferem do OTP tal como registado em diferenças.
Informação de funs
runtime/src/builtins/erlang/fun_info.cpp. erlang:fun_info/1 lista os itens
do OTP pela ordem do OTP: para uma fun local pid, module, new_index,
new_uniq, index, uniq, name (-F/A-fun-N- para uma fun anónima),
arity, env (os valores capturados) e type; para fun M:F/A module,
name, arity, env ([]) e type. fun_info/2 devolve um
{Item, Value}; um item desconhecido é badarg. pid é undefined,
new_uniq o MD5 do nome do módulo e uniq os seus primeiros 24 bits, pelo que
diferem dos do OTP. fun_to_list/1 é o texto ~w. is_builtin/3 é verdadeiro
para os builtins do runtime e para erlang:apply/2,3.
Clause