Функціональні значення
План 11, steps 32–35 (2026-10-07): fun F/A, fun M:F/A, анонімні fun із
захопленими змінними (замикання), іменовані fun (fun Name(...) -> ... end),
виклики функціональних значень (F(Args)) і динамічні виклики (M:F(Args),
apply/2,3). Факти взято із зафіксованих вихідних кодів maint-29
(erts/emulator/beam/utils.c erts_cmp, erl_printf_term.c, erl_lint) і
перевірок на OTP 29.1.1.
Значення
| Вихідний код | Значення | Виклики входять у |
|---|---|---|
fun f/1 | Локальний fun цього модуля; кожен fun f/1 модуля — те саме значення | f/1 цього модуля, експортовану чи ні |
fun m:f/1 | Зовнішній fun, що називає m:f/1, також для поточного модуля | m:f/1, якщо її експортує модуль програми |
fun(X) -> ... end | Локальний fun, що захоплює змінні, які він читає зі свого творця; кожне обчислення будує нове значення | Власну згенеровану функцію fun |
fun Name(X) -> ... end | Те саме, з Name, зв'язаним із fun усередині його клауз | Власну згенеровану функцію fun |
fun M:F/A зі змінними | Зовнішній fun, побудований під час обчислення; дорівнює буквальному fun m:f/1, який він називає | m:f/1, якщо її експортує модуль програми |
fun F/Aмає називати функцію модуля (function F/A undefined) або автоімпортовану вбудовану функцію:fun is_atom/1— це зовнішній funerlang:is_atom/1. Fun вбудованих функцій викликають їх через міст вбудованих функцій; вбудована функція поза його каталогом (fun self/0,fun erlang:apply/2) повідомляє про недоступну можливістьdynamic calls.F(Args)обчислюєF, потім аргументи зліва направо, потім перевіряє значення: не-функція викликає{badfun, F}, інша арність —{badarity, {F, Args}}(перевіряється раніше за модуль), зовнішній fun, функцію якого ніщо в програмі не експортує, —undef. Виклик у хвостовій позиції є хвостовим викликом, як і для іменованих функцій.is_function/1,2істинні для fun у тілах і guard.
Динамічні виклики
M:F(Args)зі змінною (або будь-яким виразом) як модулем чи функцією обчислює модуль, потім функцію, потім аргументи зліва направо. Модуль або функція, що не є атомом, викликаєbadarg; функція, яку жоден модуль програми не експортує з такою арністю, викликаєundef. Виклик у хвостовій позиції є хвостовим викликом.apply(Fun, Args)іapply(M, F, Args)(автоімпортовані, якщо модуль не визначаєapply/2,3і не приглушує імпорт; такожerlang:apply/2,3) обчислюють свої аргументи, потім вимагають, щобArgsбув правильним списком (badarg). Даліapply/2викликаєFunтак само, якF(Args)({badfun, Fun},{badarity, {Fun, Args}});apply/3шукаєM:F/length(Args), як описано вище, тож понад 255 аргументів даютьundef. Обидва є хвостовими викликами у хвостовій позиції й ніколи не допустимі в guard.fun M:F/Aзі змінними викликаєbadarg, якщоMіFне є атомами абоAне є цілим числом у діапазоні 0..255; функція може не існувати, доки fun не викликано.- Пошук використовує таблиці експорту модулів програми, які лишаються
зареєстрованими весь час існування програми, тож знайдена функція не може
зникнути під час виклику. Він переглядає модулі та їхні експорти, порівнюючи
слова атомів; індекси на хеш-таблицях заплановано в step 62A. Назву, яку
жоден модуль не експортує, потім шукають серед
вбудованих функцій, тож
M:F(...),apply/3іfun M:F/Aпід час виконання досягають вбудованих функційerlangз каталогу мосту. - Сервіси.
CLAUSE_call_v1(context, module, function, arity)перевіряє назви й повертаєFrameDescriptorекспорту (ABI revision 8 додає його доExportDescriptor); аргументи вже перебувають у регістрах.CLAUSE_apply_list_v1(context, fun, list, registers)іCLAUSE_call_list_v1(context, module, function, list, registers)спершу копіюють список у регістри. Усі три повертають null після запису помилки, і згенерований код тоді виконує перехід через маркерclause.apply, як і дляF(Args).CLAUSE_make_external_fun_v1будує fun зовнішньогоFunDefinition, який сервер коду створює один раз для кожногоM:F/A(CodeServer::external_fun).
Замикання
- Кожна клауза починається з області видимості в місці fun. Змінні голови — це нові імена, що затінюють зовнішні (OTP попереджає); ім'я, повторене в межах однієї голови, має збігатися. Guard читають імена голови. Ніщо, що зв'язує fun, не видно після нього, а імена з клауз case охопної функції не проникають у нього.
- Fun захоплює кожне зовнішнє визначення, яке читають його голови, guard або тіла (включно з вкладеними fun), у порядку визначення, як OTP упорядковує вільні змінні функції. Захоплені значення копіюються в комірку fun під час обчислення виразу fun, тож вони переживають повернення з творця і збирання сміття та копіюються разом із fun.
- Код — це приватна функція
-f/A-fun-N-(N лічить fun функціїf/Aу порядку вихідного коду), що приймає аргументи fun, а потім його захоплені значення; трасування стека показують це ім'я із сукупною арністю, як і в OTP. Якщо жодна клауза не збігається, викликаєтьсяfunction_clause. - Аргументи разом із захопленими значеннями обмежено числом 255.
- Клаузи іменованого fun бачать
Nameяк сам fun: нове ім'я, що затінює зовнішнє (OTP попереджає), ніколи не захоплюється і не видно після fun. Змінна голови з тим самим ім'ям своєю чергою затінює його. Коли клауза читаєName, код fun будує значення під час входу зі своїх захоплених значень, тож воно рівне (=:=) викликаному fun, аName(...)— звичайний виклик fun: у хвостовій позиції це хвостовий виклик, що виконується в сталому стеку. - Анонімний fun у значенні за замовчуванням поля record — це один fun для кожного конструювання, що використовує значення за замовчуванням (OTP розгортає копію для кожного місця).
Подання
- Дескриптор. Кожне окреме значення, яке створює модуль, компілюється в
один
abi::v1::FunDescriptorу приватній таблиці модуля<prefix>.funs(funs.hpp): дескриптор модуля, слоти атомів модуля і функції, арність Erlang, індекс, прапорець зовнішності таFrameDescriptor, у який входить виклик (null для зовнішнього fun поза програмою). Реєстрація прив'язує їх доFunDefinition(ModuleAtoms::funs,CodeServer::fun_definition); кількість захоплених значень локального fun дорівнює арності його коду мінус арність fun. - Комірка. Упакований вид
fun_closure: заголовок (лічильник1 + n), невідстежуванийconst FunDefinition *, потімnзахоплених значень. Обхід, збирання сміття, копіювання і перевірка пропускають слово визначення, як і для native records. - Сервіси.
CLAUSE_make_fun_v1(context, descriptor, captures, count, output)будує fun.CLAUSE_apply_v1(context, fun, arity, arguments)перевіряє викликане значення, записує описані вище помилки в канал невдач (ErrorReason22-24), а інакше дописує захоплені значення після аргументів і повертаєFrameDescriptorдля входу. - Виклики. Нативна форма передає аргументи в масиві слів і викликає маркер
clause.applyз дескриптором;lower_framesперетворює масив на регістри процесу, а маркер — на перехід черезCLAUSE_enter_v1або, у хвостовій позиції, черезCLAUSE_tail_v1.
Порівняння і друк
- Порядок термів: число < атом < fun < кортеж < native record < map < nil < список < bitstring (посилань, портів і pid, які в OTP розташовані між атомами та кортежами, ще не існує).
- Локальні fun упорядковуються перед зовнішніми. Локальні fun порівнюються за
модулем, потім індексом, потім захопленими значеннями по порядку (
==порівнює їх через==); зовнішні fun — за модулем, функцією та арністю. Однакові дескриптори дають рівні значення, тожfun f/1 =:= fun f/1. erlang:display/1,~wі звіти про неперехоплені винятки друкують зовнішній fun якfun m:f/1(атоми в лапках, як в емуляторі), а локальний fun — як#Fun<m.Index.0>.
Відмінності
Зафіксовано у відмінностях:
- Локальний fun друкується як
#Fun<m.Index.0>: індекс OTP відповідає нумерації лямбд його компілятора, а третя частина — хеш коду модуля. Clause нумерує локальні fun у порядку вихідного коду, тож порядок двох локальних fun різних функцій модуля також може відрізнятися. - Виклик зовнішнього fun модуля поза програмою викликає
undef; OTP спершу спробував би завантажити модуль зі шляху коду. Те саме стосуєтьсяM:F(Args)іapply/3. - Трасування стека
undefпочинається з фрейму викликача; в OTP — з{M, F, Args, []}відсутньої функції. - Імена анонімних fun (
-f/1-fun-0-, видимі в трасуваннях стека) лічать fun у порядку вихідного коду; компілятор OTP нумерує їх у власному порядку. Fun, створені всередині comprehension, також можуть захоплювати свої значення в іншому порядку, ніж в OTP, що помітно лише при порівнянні двох таких fun.
Clause