Valores de función
Plan 11 steps 32–35 (2026-10-07): fun F/A, fun M:F/A, funs anónimos con
variables capturadas (clausuras, closures), funs con nombre
(fun Name(...) -> ... end), llamadas a valores de función (F(Args)) y
llamadas dinámicas (M:F(Args), apply/2,3). Los datos provienen de los
fuentes fijados de maint-29 (erts/emulator/beam/utils.c erts_cmp,
erl_printf_term.c, erl_lint) y de pruebas sobre OTP 29.1.1.
Valores
| Código fuente | Valor | Las llamadas entran en |
|---|---|---|
fun f/1 | Fun local de este módulo; todo fun f/1 de un módulo es el mismo valor | f/1 de este módulo, exportada o no |
fun m:f/1 | Fun externo que designa m:f/1, también para el módulo actual | m:f/1 cuando un módulo del programa la exporta |
fun(X) -> ... end | Fun local que captura las variables que lee de su creador; cada evaluación construye un valor nuevo | La función generada propia del fun |
fun Name(X) -> ... end | Lo mismo, con Name vinculado al fun dentro de sus cláusulas | La función generada propia del fun |
fun M:F/A con variables | Fun externo construido al evaluarse; igual al fun m:f/1 literal que designa | m:f/1 cuando un módulo del programa la exporta |
fun F/Adebe designar una función del módulo (function F/A undefined) o un builtin autoimportado:fun is_atom/1es el fun externoerlang:is_atom/1. Los funs de builtins las llaman a través del puente de builtins; un builtin fuera de su catálogo (fun self/0,fun erlang:apply/2) informa de que la capacidaddynamic callsno está disponible.F(Args)evalúaF, después los argumentos de izquierda a derecha, y luego comprueba el valor: algo que no es una función lanza{badfun, F}, otra aridad{badarity, {F, Args}}(comprobado antes que el módulo), un fun externo cuya función no exporta nada del programaundef. Una llamada en posición de cola es una llamada de cola, como en las funciones con nombre.is_function/1,2son verdaderas para los funs en cuerpos y guards.
Llamadas dinámicas
M:F(Args)con una variable (o cualquier expresión) como módulo o función evalúa el módulo, después la función y luego los argumentos de izquierda a derecha. Un módulo o una función que no son átomos lanzanbadarg; una función que ningún módulo del programa exporta con esa aridad lanzaundef. Una llamada en posición de cola es una llamada de cola.apply(Fun, Args)yapply(M, F, Args)(autoimportadas salvo que el módulo definaapply/2,3o suprima la importación; tambiénerlang:apply/2,3) evalúan sus argumentos y después exigen queArgssea una lista propia (badarg). A continuaciónapply/2llama aFuncomo lo haceF(Args)({badfun, Fun},{badarity, {Fun, Args}});apply/3buscaM:F/length(Args)como se ha descrito, de modo que más de 255 argumentos lanzanundef. Ambas son llamadas de cola en posición de cola y nunca son legales en guards.fun M:F/Acon variables lanzabadargsalvo queMyFsean átomos yAun entero en 0..255; la función no necesita existir hasta que se llama al fun.- La búsqueda usa las tablas de exportación de los módulos del programa, que
permanecen registradas durante toda la vida del programa, de modo que una
función encontrada no puede desaparecer durante la llamada. Recorre los
módulos y sus exportaciones comparando palabras de átomo; los índices de tabla
hash son el plan step 62A. Un nombre que ningún módulo exporta se busca
después entre las builtins, de modo que
M:F(...),apply/3yfun M:F/Aen tiempo de ejecución alcanzan las builtins deerlangdel catálogo del puente. - Servicios.
CLAUSE_call_v1(context, module, function, arity)comprueba los nombres y devuelve elFrameDescriptorde la exportación (la revisión 8 de la ABI lo añade aExportDescriptor); los argumentos ya están en los registros.CLAUSE_apply_list_v1(context, fun, list, registers)yCLAUSE_call_list_v1(context, module, function, list, registers)copian primero la lista en los registros. Las tres devuelven null tras registrar el error, y el código generado transfiere entonces el control a través del marcadorclause.applycomo paraF(Args).CLAUSE_make_external_fun_v1construye el fun de unaFunDefinitionexterna que el servidor de código crea una vez por cadaM:F/A(CodeServer::external_fun).
Clausuras
- Cada cláusula parte del ámbito en el punto del fun. Las variables de la cabecera son nombres nuevos que ocultan a los exteriores (OTP advierte); un nombre repetido dentro de una misma cabecera debe encajar. Los guards leen los nombres de la cabecera. Nada de lo que vincula un fun es visible después de él, y los nombres de las cláusulas case de la función contenedora no llegan a su interior.
- Un fun captura toda definición exterior que leen sus cabeceras, guards o cuerpos (incluidos los funs anidados), en orden de definición, igual que OTP ordena las variables libres de una función. Los valores capturados se copian en la celda del fun cuando se evalúa la expresión fun, de modo que sobreviven al retorno del creador y a las recolecciones, y se copian con el fun.
- El código es una función privada
-f/A-fun-N-(N cuenta los funs def/Aen orden de código fuente) que toma los argumentos del fun y después sus valores capturados; las trazas de pila muestran ese nombre con la aridad combinada, como las de OTP. Si ninguna cláusula encaja, se lanzafunction_clause. - Los argumentos más los valores capturados están limitados a 255.
- Las cláusulas de un fun con nombre ven
Namecomo el propio fun: un nombre nuevo que oculta a uno exterior (OTP advierte), nunca capturado y no visible después del fun. Una variable de cabecera con el mismo nombre lo oculta a su vez. Cuando una cláusula leeName, el código del fun construye el valor a la entrada a partir de sus valores capturados, de modo que es igual (=:=) al fun que se está llamando, yName(...)es una llamada a fun ordinaria: en posición de cola es una llamada de cola y se ejecuta con pila constante. - Un fun anónimo en el valor por defecto de un campo de record es un único fun para todas las construcciones que usan ese valor por defecto (OTP expande una copia por punto de uso).
Representación
- Descriptor. Cada valor distinto que crea un módulo se compila a un
abi::v1::FunDescriptoren la tabla privada<prefix>.funsdel módulo (funs.hpp): descriptor del módulo, slots de átomo del módulo y de la función, aridad Erlang, índice, indicador de externo y elFrameDescriptoren el que entra una llamada (null para un fun externo ajeno al programa). El registro los vincula aFunDefinition(ModuleAtoms::funs,CodeServer::fun_definition); el número de valores capturados de un fun local es la aridad de su código menos la del fun. - Celda. Tipo encapsulado
fun_closure: cabecera (recuento1 + n), unconst FunDefinition *no rastreado y despuésnvalores capturados. El recorrido, la recolección, la copia y la verificación omiten la palabra de definición, como en los records nativos. - Servicios.
CLAUSE_make_fun_v1(context, descriptor, captures, count, output)construye un fun.CLAUSE_apply_v1(context, fun, arity, arguments)comprueba un valor llamado, registra los errores anteriores en el canal de fallos (ErrorReason22-24) y, en otro caso, añade los valores capturados tras los argumentos y devuelve elFrameDescriptoren el que entrar. - Llamadas. La forma nativa pasa los argumentos en un array de palabras y
llama al marcador
clause.applycon el descriptor;lower_framesconvierte el array en los registros del proceso y el marcador en una transferencia a través deCLAUSE_enter_v1o, en posición de cola,CLAUSE_tail_v1.
Comparación e impresión
- Orden de términos: número < átomo < fun < tupla < record nativo < map < nil < lista < bitstring (las referencias, los puertos y los pids, que en OTP se sitúan entre los átomos y las tuplas, aún no existen).
- Los funs locales se ordenan antes que los externos. Los funs locales se
comparan por módulo, después por índice y luego por los valores capturados en
orden (
==los compara con==); los externos por módulo, función y aridad. Descriptores iguales dan valores iguales, de modo quefun f/1 =:= fun f/1. erlang:display/1,~wy los informes de excepciones no capturadas imprimen un fun externo comofun m:f/1(con los átomos entre comillas como hace el emulador) y un fun local como#Fun<m.Index.0>.
Diferencias
Registradas en diferencias:
- Un fun local se imprime
#Fun<m.Index.0>: el índice de OTP sigue la numeración de lambdas de su compilador y su tercera parte es un hash del código del módulo. Clause numera los funs locales en orden de código fuente, por lo que el orden de dos funs locales de funciones distintas de un módulo también puede diferir. - Llamar a un fun externo de un módulo ajeno al programa lanza
undef; OTP intentaría antes cargar el módulo desde la ruta de código. Lo mismo vale paraM:F(Args)yapply/3. - Una traza de pila de
undefempieza con el marco del llamador; la de OTP empieza con{M, F, Args, []}de la función inexistente. - Los nombres de los funs anónimos (
-f/1-fun-0-, visibles en las trazas de pila) cuentan los funs en orden de código fuente; el compilador de OTP los numera en su propio orden. Los funs creados dentro de comprehensions también pueden capturar sus valores en un orden distinto al de OTP, lo que solo se aprecia al comparar dos de esos funs.
Clause