Вкладена функція (вкладена процедура чи вкладена підпрограма, ) — у комп'ютерному програмуванні функція, яка визначена всередині іншої функції — охоплювальної (). Завдяки рекурсивним правилам області видимості вкладену функцію не видно зовні своєї безпосередньої охоплювальної функції, але вона може бачити (мати доступ) до всіх локальних об'єктів (даних, функцій, типів, тощо) своєї прямої охоплювальної функції, а також до всіх функцій, які, також містяться в ній. Теоретично можлива будь-яка глибина вкладення функцій, однак на практиці використовують, як правило, лише декілька рівнів.
Вкладені функції використовуються в багатьох парадигмах програмування. В багатьох сучасних динамічних і функціональних мовах вкладені функції надзвичайно корисні та вживані. У структурному програмуванні також часто застосовується в «поважних» мовах, таких як Алгол, Симула, Паскаль та Фортран.
В C++ вкладені функції можна реалізувати за допомогою лямбда-виразів.
Ефекти Вкладені функції передбачають наявність області видимості функції, або блокової видимості. Областю видимості вкладеної функції є вміст охоплювальної функції, або, точніше, вміст одного зі складових блоків цієї функції, що означає, що її не видно зовні цього блоку, а також за межами охоплювальної функції. Вкладена функція може мати доступ до інших локальних функцій, змінних, констант, типів, класів тощо, які містяться в тій самій області видимості або в будь-якій зовнішній області видимості, без явного передавання параметрів, що спрощує передавання даних у вкладену функцію і з неї.
У певних ситуаціях (і мовах) вкладені функції можуть привести до створення замикання. Якщо для вкладеної функції існує можливість із зовнішньої функції, наприклад, якщо вкладена функція є об'єктом першого класу і вона передається іншій функції або повертається із зовнішньої функції, то в таких випадках створюється замикання, і виклики цієї функції можуть отримати доступ до середовища початкової функції. Фрейм безпосередньої зовнішньої функції повинен залишатися живим доти, поки не «помре» останнє замикання, що посилається на нього, і нелокальні автоматичні змінні, посилання на які є в замиканні, не можуть таким чином виділити стек. Ця ситуація відома як і є ключовою причиною того, чому вкладені функції не було реалізовано в деяких простіших мовах, оскільки це значно ускладнює генерування коду та його аналіз, особливо у випадках, коли функції вкладено в різні рівні і вони поділяють різні частини своїх оточень.
Приклади Приклад з використанням синтаксису мови Pascal (Алгол, , Оберон, Ада та інших схожих): fun e (x : real) = let fun f y = x+y in f 3 + f 4 end; Функцію F вкладено у функцію E. Зауважимо, що параметр x функції E видимий також у функції F (оскільки функція F є частиною функції E), тоді як обидва параметри x і y невидимі зовні функції E і функції F відповідно.
Схожий приклад мовою Standard ML: e :: Float -> Float e x = f 3 + f 4 where f y = x + y Один зі способів реалізації цього прикладу з використанням синтаксису мови програмування Haskell: float E(float x) { float F(float y)
return x + y;
return F(3) + F(4); } Цей самий приклад із використанням синтаксису GNU C (C, розширена використанням вкладених функцій):function E(x: real): real; function F(y: real): real; begin F := x + y end; begin E := F(3) + F(4) end;
Швидке сортування Більш практичний приклад — реалізація швидкого сортування:Re: Nesting functions- Why? , baavgai , 14 січня 2012 void sort(int *items, int size) { void quickSort(int first, int last) { void swap(int p, int q) { int tmp = items[p]; items[p] = items[q]; items[q] = tmp;
int partition() { int pivot = items[first], index = first; swap(index, last); for (int i = first; i < last; i++) if (items[i] < pivot) swap(index++, i); swap(index, last); return index;
if (first < last) { int pivotIndex = partition(); quickSort(first, pivotIndex - 1); quickSort(pivotIndex + 1, last);
quickSort(0, size - 1);
}Інший приклад — ще одна реалізація швидкого сортування Гоара з використанням синтаксису лямбда-виразів на C++11: template<typename RandomAccessIterator> auto Sort(RandomAccessIterator Begin, RandomAccessIterator End)->void { auto Partition = [&]() { //Hoare partition scheme auto &Pivot = *Begin; auto ForwardCursor = Begin; auto BackwardCursor = End - 1; auto PartitionPositionFound = false; auto LocatePartitionPosition = [&]() { while (*ForwardCursor < Pivot) ++ForwardCursor; while (Pivot < *BackwardCursor) --BackwardCursor; if (ForwardCursor >= BackwardCursor) PartitionPositionFound = true; else Swap(*ForwardCursor, *BackwardCursor);
//Trivial helper function auto MoveOnAndTryAgain = [&]() { ++ForwardCursor; --BackwardCursor;
//Brief outline of the actual partition process while (true) { LocatePartitionPosition(); if (PartitionPositionFound) return BackwardCursor + 1; else MoveOnAndTryAgain();
//Brief outline of the quicksort algorithm if (Begin < End - 1) { auto PartitionPosition = Partition(); Sort(Begin, PartitionPosition); Sort(PartitionPosition, End);
Призначення Лексично визначення вкладених функцій є формою та корисні для поділу процедурних задач на підзадачі. Це допомагає уникнути засмічення інших частин програми функціями та змінними, які не мають до них стосунку.
Зазвичай вкладені функції використовують як допоміжні функції або рекурсивні функції всередині інших функцій (як у прикладі швидкого сортування вище). Це корисно для структурної організації коду, запобігання забруднення області видимості, а також дає змогу функціям легко ділитися своїм станом. Оскільки вкладена функція має доступ до локальних змінних зовнішньої функції, спільне використання стану, можливе без передавання параметрів у вкладену функцію або використання глобальних змінних, спрощує код.
У мовах зі вкладеними функціями ці функції можуть загалом також містити локальні константи і типи даних (на додаток до локальних змінних, параметрів і функцій), так само інкапсульовані й приховані з будь-яким рівнем вкладеності. Це розширює можливості структурування коду.
Інші види використання
Порядок виконання Вкладені функції можуть бути також використані для неструктурованого потоку керування, з використанням інструкції повернення для загального неструктурованого потоку керування. Це може використовуватися для детальнішого керування, ніж це можливо за допомогою інших вбудованих функцій мови — наприклад, це дає змогу достроково припинити цикл for, якщо недоступна інструкція break, або достроково припинити вкладений цикл for, якщо недоступний багаторівневий break або винятки.
Функції вищого порядку У деяких мовах програмування можна створити вкладену функцію, яка отримує доступ до набору параметрів із зовнішньої функції, тобто замикання, і зробити цю функцію значенням, яке повертає зовнішня функція. Отже, можна повернути функцію, призначену для виконання певної задачі, без параметрів або з невеликим набором параметрів, переданих до неї, що може значно підвищити продуктивність.
Альтернативи Головною альтернативою вкладеним функціям у мовах програмування, де відсутня їх підтримка, є перенесення всіх релевантних функцій та змінних в окремий модуль (файл) та надання доступу лише до функції-обгортки верхнього рівня. У мові програмування C це можна зробити з використанням статичних функцій для інкапсуляції та статичних змінних для зв'язку."Question 20.24: Why doesn't C have nested functions? , comp.lang.c FAQ Це дає змогу приховувати, інкапсулювати та декомпонувати дані, але на іншому рівні деталізації, ніж у випадку вкладених функцій. Ця модульність не підтримує більше одного рівня вкладеності.
Інша альтернатива спільного використання стану між функціями можлива з використанням параметрів функції, в більшості випадків передаючи посилання як аргументи, щоб уникнути витрат на копіювання. Загалом у C це реалізують з використанням вказівника на структуру, яка містить контекст. Це значно ускладнює виклики функції.
У PHP та інших мовах єдиною альтернативою є анонімна функція: вкладена функція оголошується не як звичайна функція, а як посилання локальну змінну. Для використання локальних змінних в анонімних функціях використовують замикання.
Мови програмування Серед мов програмування, які лексично підтримують вкладені функції, такі:
Засновані на Алголі, такі як , Симула, Паскаль, , , Оберон, та Ада. Сучасні версії мови програмування Lisp (з лексичною областю видимості) такі як Scheme і Common Lisp. ECMAScript (JavaScript та ActionScript). Dart. Kotlin (локальні функції). Scala (вкладені функції). Різні рівні підтримки у скриптових мовах, таких як Ruby, Python, Lua, PHP та Perl. GCC підтримує вкладені функції С, як розширення мови. C#, починаючи з C# 7.0. Мова D, C-подібна мова зі вкладеними функціями. Фортран, починаючи з Fortran-90, підтримує одиничний рівень вкладених підпрограм та функцій. MATLAB (повна підтримка). .
Функційні мови У більшості мов функційного програмування, таких як Scheme, вкладені функції із циклами є звичайним способом реалізації алгоритмів. Проста (хвостова) рекурсивна внутрішня функція створюється і використовується як головний цикл алгоритму, тоді як зовнішня функція виконує дії, пов'язані із запуском, які слід виконати лише один раз. У більшості складних випадків як внутрішні функції може бути створено низку взаємно рекурсивних функцій.
Деякі мови без прямої підтримки Деякі мови не мають прямої синтаксичної та семантичної підтримки для реалізації вкладених функцій. Попри це, для деяких з них аналог вкладених функцій можна змоделювати з використанням інших мовних конструкцій. Приклади мов, де можна реалізувати аналог вкладених функцій за допомогою відповідних стратегій:
C++ перед версією C++11: дає змогу визначити класи всередині класів, а отже використовувати методи класу подібно до вкладених функцій одного рівня (див. Функтор у C++). починаючи з версії C++11: використовуючи лямбда-вирази, наприклад, наведене вище швидке сортування. Eiffel явно не дозволяє використання вкладених функцій, щоб зберегти простоту мови, а також використовує домовленість про використання спеціальної змінної Result для позначення результату функції, яка повертає значення. Visual Basic — з використанням анонімних методів або лямбда-виразів. Java — з використанням лямбда-виразів (див. Анонімні функції в Java) (починаючи з Java 8) або з використанням обхідного шляху, який полягає у використанні анонімного класу, що містить єдиний метод. Можливо також використати іменований клас, оголошений локально у методі.
Реалізація Реалізація вкладених функцій може вимагати більше уваги, ніж здається, оскільки посилання на внутрішню функцію, яка посилається на нелокальну змінну створює замикання. З цієї причини вкладені функції не підтримуються в деяких мовах програмування, таких як C, C++ або Java, оскільки це створює складнощі під час компіляції таких програм.answer by Dave Vandervies, Aug 28 '09 at 17:45, to «Why are nested functions not supported by the C standard? » Попри це, деякі компілятори підтримують їх у вигляді спеціального розширення компілятора. Добре відомий приклад — реалізація GNU C мови програмування C.
Примітки
Джерела Examples of Lambda Expressions