Функціональні значення
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 erlang:F/Aдля функції поза каталогом (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, а її трасування стека починається з{M, F, Arity, []}(OTP показує аргументи;apply/3і виклики зовнішніх fun також їх показують). Виклик у хвостовій позиції є хвостовим викликом.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.erlang:apply/2,3, до яких звертаються за іменем під час виконання (spawn(erlang, apply, [F, Args]),M:apply(...), колиMзв’язано зerlang, дія таймера), є вбудованими функціями (builtins/erlang/apply.cpp), що продовжують виконання у викликаній функції з тими самими помилками.fun M:F/Aзі змінними викликаєbadarg, якщоMіFне є атомами абоAне є цілим числом у діапазоні 0..255; функція може не існувати, доки fun не викликано.- Пошук використовує хеш-індекси сервера коду для експортів програми
(runtime), які лишаються
зареєстрованими весь час існування програми, тож знайдена функція не може
зникнути під час виклику. Назву, яку жоден модуль не експортує, потім шукають
серед вбудованих функцій, тож
M:F(...),apply/3іfun M:F/Aпід час виконання досягають вбудованих функцій з каталогу мосту. - Сервіси.
CLAUSE_call_v1(context, module, function, arity)перевіряє назви й повертаєFrameDescriptorекспорту (з його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 розгортає копію для кожного місця).
Вбудовування fun
За -O2 (codegen/lowering_fun_inlining.cpp, lowering_list_loops.cpp,
lowering_map_loops.cpp) виклик анонімного fun, відомого в місці виклику,
понижує клаузи fun на місці виклику, подібно до параметра OTP
inline_list_funcs:
- Відомі fun. Fun, записаний у виклику (
(fun(X) -> ... end)(Y)або перший аргумент циклу списку нижче), чи змінна, один раз зв’язана з неіменованим анонімним fun (F = fun ... end), яку читають один раз або яка містить не більш ніж 40 виразів. Усередині коду іншого fun fun змінної вбудовується, лише якщо все захоплене ним також зв’язано там. Вкладеність вбудованих тіл обмежена чотирма рівнями; цикли binary і чисел з плаваючою комою нічого не вбудовують. - Клаузи на місці. Аргументи зіставляються з клаузами та guard fun, а
тіло відповідної клаузи виконується у фреймі викликача. Якщо жодна клауза
не відповідає, викликається fun, тож
function_clause, як і раніше, походить із власного коду fun. Fun, записаний у виклику, будується лише на цьому шляху та на описаних нижче шляхах, що потребують його значення (відкладене створення замикання); fun змінної будується в місці її зв’язування, як і раніше. - Цикли списків.
lists:map/2,filter/2,foreach/2,all/2,any/2,flatmap/2,foldl/3,foldr/3,mapfoldl/3іmapfoldr/3власного модуляlistsClause з відомим fun потрібної арності стають циклом по списку у функції викликача. Виклик fun для кожного елемента вбудовується; списки будуються у зворотному порядку й один раз обертаються наприкінці;foldr/3іmapfoldr/3спершу обертають вхідний список, тож їхній fun і далі бачить елементи з останнього, після помилки через неправильний хвіст. Як і рекурсія в тілі, яку вони замінюють, цикли списків збирають сміття лише там, де поступаються керуванням (кожні 4,000 елементів без інших викликів), і у викликах, які робить їхній fun. - Цикли згортання map.
maps:fold/3власного модуляmapsClause з відомим fun арності 3 стає циклом по записах map (класична комірка map або записи дерева, один раз скопійовані в комірку форми кортежу, як їх читаєmaps:fold/3), у порядку ключів, як уmaps:fold/3. Ітератор map або значення, що не є map, спричиняє справжній викликmaps, який обробляє його або генерує власну помилку. - Акумулятори у власності циклу. Коли кожна клауза fun згортання
(
maps:fold/3,lists:foldl/3,lists:foldr/3) повертає свій параметр акумулятора оновленим,fun(K, V, Acc) -> Acc#{K := V + 1} end, аAccбільше ніде не читається й не захоплюється жодним fun, цикл володіє результатом кожного кроку: з другого кроку оновлення має виключне володіння та змінює map на місці (дерева map). Перший крок копіює, оскільки початковий акумулятор може зберігатися деінде. - Ті самі результати й причини. Значення, порядок виконання fun і причини
помилок такі самі, як у
lists: вхідні дані, що не є списком, дляmap/2,foldl/3,all/2,any/2і неправильні хвости дляfilter/2,foreach/2,foldr/3,mapfoldl/3,mapfoldr/3спричиняють справжній викликlists, який генерує власну помилку; неправильний хвіст дляmap/2,foldl/3,all/2,any/2,flatmap/2генеруєfunction_clauseу викликачі. Небулевий предикат генерує{case_clause, V}(all/2,any/2) або{bad_filter, V}(filter/2), а результат funmapfold, що не є парою, —{badmatch, V}. - Трасування стека винятків усередині вбудованих fun не містять фреймів
fun і допоміжної функції
listsчиmaps(відмінності).
Подання
- Дескриптор. Кожне окреме значення, яке створює модуль, компілюється в
один
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для входу. - Вбудовані замикання. Runtime, що закріплює за собою кеш атомів модуля,
також заповнює
ModuleDescriptor::fun_cacheадресою визначення кожного fun; поки закріплення належить йому, згенерований код сам виділяє комірку fun (заголовок, визначення, захоплені значення) зсувом вказівника й викликаєCLAUSE_make_fun_v1лише за браку місця в купі або в іншому runtime.CLAUSE_apply_v1іCLAUSE_apply_list_v1читають fun потрібної арності (і список аргументів) безпосередньо з його комірок перед перевіреним шляхом, який повідомляє про помилки. - Виклики. Нативна форма передає аргументи в масиві слів і викликає маркер
clause.applyз дескриптором;lower_framesперетворює масив на регістри процесу, а маркер — на вхід або, у хвостовій позиції, на хвостову передачу (модель виконання).
Порівняння і друк
- Fun упорядковуються після посилань і перед портами (порядок термів).
- Локальні fun упорядковуються перед зовнішніми. Локальні fun порівнюються за
модулем, потім індексом, потім захопленими значеннями по порядку (
==порівнює їх через==); зовнішні fun — за модулем, функцією та арністю. Однакові дескриптори дають рівні значення, тожfun f/1 =:= fun f/1. erlang:display/1,~wі звіти про неперехоплені винятки друкують зовнішній fun якfun m:f/1(атоми в лапках, як в емуляторі), а локальний fun — як#Fun<m.Index.0>.
Відмінності
Нумерація, друк і впорядкування fun, порядок захоплення в comprehension,
трасування стека undef і виклики модулів поза програмою відрізняються від
OTP, як записано у відмінностях.
Інформація про fun
runtime/src/builtins/erlang/fun_info.cpp. erlang:fun_info/1 перелічує
елементи OTP у порядку OTP: для локального fun — pid, module, new_index,
new_uniq, index, uniq, name (-F/A-fun-N- для анонімного fun),
arity, env (захоплені значення) і type; для fun M:F/A — module,
name, arity, env ([]) і type. fun_info/2 повертає один
{Item, Value}; невідомий елемент дає badarg. pid дорівнює undefined,
new_uniq — MD5 імені модуля, а uniq — його перші 24 біти, тож вони
відрізняються від OTP. fun_to_list/1 — це текст ~w. is_builtin/3
істинна для вбудованих функцій runtime і erlang:apply/2,3.
Clause