Чи є рекурсія самою собою особливістю?


116

... чи це просто практика?

Я прошу це через суперечку з моїм професором: я втратив кредит за виклик функції рекурсивно, виходячи з того, що ми не покривали рекурсію в класі, і мій аргумент полягає в тому, що ми дізналися це неявно за допомогою навчання returnта методів.

Я прошу тут, бо підозрюю, що хтось має остаточну відповідь.

Наприклад, яка різниця між наступними двома методами:

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

Окрім " aпродовжується назавжди" (у фактичній програмі використовується правильно для повторного сповіщення користувача, якщо він надається недійсним вводом), чи є принципова різниця між aі b? Як неоптимізований компілятор, як вони по-різному обробляються?

В кінцевому рахунку, зводиться до того, навчившись return a()від bщо ми следоватьльни також навчилися return a()з a. Хіба ми?


24
Відмінна дискусія. Цікаво, чи ви пояснили це так своєму професору. Якщо ви це зробили, я думаю, що він повинен дати вам втрачену кредит.
Михайло Яворський

57
Рекурсія навіть не є концепцією, що є виключною для інформатики. Функція Фібоначчі, факторний оператор і багато інших речей з математики (і, можливо, інших полів) виражаються (або принаймні можуть бути) рекурсивно. Чи вимагає професор, щоб ви також не забували про ці речі?
Теодорос Чатзіґянакікіс

34
Професор повинен надати йому додаткові заслуги за те, що він придумав елегантний спосіб вирішити проблему, або за те, що говорити нестандартно.

11
Яке було завдання? Це проблема, про яку я часто замислювався, коли ви надсилаєте програмне завдання, що відзначається, ваша здатність вирішувати проблему або ваша здатність використовувати те, що ви дізналися. Ці два необов'язково однакові.
Леон

35
FWIW, запит на введення до тих пір, поки це не є правильним місцем для використання рекурсії, занадто легко переповнювати стек. Для цього конкретного випадку було б краще використовувати щось подібне a() { do { good = prompt(); } while (!good); }.
Кевін

Відповіді:


113

Щоб відповісти на ваше конкретне питання: Ні, з точки зору вивчення мови, рекурсія не є особливістю. Якщо ваш професор справді закрепив вам позначки за використання "функції", яку він ще не вчив, це було неправильно.

Читаючи між рядками, одна з можливостей полягає в тому, що, використовуючи рекурсію, ви уникали коли-небудь використовувати функцію, яка повинна була бути результатом навчання для його курсу. Наприклад, можливо, ви взагалі не використовували ітерацію, або, можливо, ви використовували лише forпетлі замість обох forі while. Загальним є те, що завдання має на меті перевірити вашу здатність робити певні речі, і якщо ви уникаєте їх робити, ваш професор просто не може надати вам знаки, відведені для цієї функції. Однак якщо це справді було причиною ваших втрачених оцінок, професор повинен сприймати це як власний досвід навчання - якщо демонстрація певних результатів навчання є одним із критеріїв завдання, що має бути чітко пояснено студентам .

Сказавши це, я погоджуюся з більшістю інших коментарів та відповідей, що ітерація - це кращий вибір, ніж рекурсія тут. Є кілька причин, і хоча інші люди певною мірою торкалися їх, я не впевнений, що вони повністю пояснили думку, що стоїть за ними.

Стек переповнення

Більш очевидним є те, що ви ризикуєте отримати помилку переповнення стека. Реально, написаний вами метод є малоймовірним, що він фактично призведе до цього, оскільки користувачеві доведеться багато разів вводити неправильний вклад, щоб насправді викликати переповнення стека.

Однак слід пам’ятати про те, що не лише сам метод, а й інші методи, що знаходяться вище або нижче в ланцюзі викликів, будуть знаходитись у стеку. З-за цього випадкове поглинання наявного простору стеку - це досить нечесна річ для будь-якого методу. Ніхто не хоче постійно турбуватися про вільний простір стеків кожного разу, коли вони пишуть код через ризик того, що інший код міг би без потреби використовувати його багато.

Це частина більш загального принципу в розробці програмного забезпечення, що називається абстракцією. По суті, коли ви телефонуєте DoThing(), все, що вам потрібно буде подбати, - це те, що робиться. Вам не доведеться турбуватися про деталі впровадження того, як це робиться. Але жадібне використання стека порушує цей принцип, тому що кожен біт коду повинен турбуватися про те, скільки стека можна сміливо припускати, що він залишив його за кодом в інших місцях ланцюжка викликів.

Читабельність

Інша причина - читабельність. Ідеал, до якого повинен прагнути код, - це читати людський документ, де кожен рядок просто описує, що він робить. Скористайтеся цими двома підходами:

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

проти

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

Так, це обоє працюють, і так, вони обоє досить легко зрозуміти. Але як можна описати два підходи англійською мовою? Я думаю, це було б щось на зразок:

Я запрошую ввести, поки введений не буде дійсним, а потім поверну його

проти

Я підкажу про введення, тоді, якщо введення дійсне, я поверну його, інакше я отримаю вклад і поверну результат замість цього

Можливо, ви можете придумати дещо менш незграбне формулювання для останнього, але я думаю, ви завжди знайдете, що перше - це більш точний опис, в принципі, того, що ви насправді намагаєтеся зробити. Це не означає, що рекурсія завжди менш читабельна. У ситуаціях, коли він світить, як, наприклад, обхід дерева, ви могли б робити такий самий вид паралельного аналізу між рекурсією та іншим підходом, і ви майже напевно знайдете, що рекурсія дає код, який чіткіше самоописується, рядок за рядком.

Окремо це обидві точки. Це дуже малоймовірно, що це дійсно призведе до переповнення стека, а посилення читабельності незначне. Але будь-яка програма буде сукупністю багатьох цих невеликих рішень, тому навіть якщо вони відокремлені, вони не мають великого значення, важливо вивчити принципи їх правильності.


8
Чи можете ви розширити своє твердження, що рекурсія не є особливістю? У своїй відповіді я стверджував, що так є, тому що не всі компілятори обов'язково її підтримують.
Гаррі Джонстон

5
Не всі мови обов'язково підтримують рекурсію, тож це не обов'язково лише питання про вибір правильного компілятора - але ви цілком вірно сказали, що "функція" є суттєво неоднозначним описом, настільки справедливим. Ваш другий пункт також справедливий з точки зору того, хто навчається програмувати (як це зараз прийнято), не маючи спочатку жодного досвіду програмування машинного коду. :-)
Гаррі Джонстон,

2
Зауважте, що проблема "читабельності" - це проблема з синтаксисом. У рекурсії немає нічого, що є «нечитабельним». Насправді, індукція - це найпростіший спосіб виразити індуктивні структури даних, такі як петлі, списки та послідовності тощо. І більшість структур даних є індуктивними.
іменник

6
Думаю, ви склали колоду зі своїм формулюванням. Ви описали ітераційну версію функціонально і навпаки. Я думаю, що справедливіший покроковий опис обох буде "Я запрошую на введення". Якщо введення недійсне, я продовжуватиму повторювати підказку, поки не отримаю дійсного введення. Тоді я поверну його ». vs “Я підкажу для введення Якщо введення дійсне, я поверну його. Інакше я поверну результат передоплати ». (Мої діти зрозуміли функціональну концепцію дозволу, коли вони були в дошкільній школі, тому я думаю, що це законний резюме англійської мови про рекурсивну концепцію тут.)
pjs

2
@HarryJohnston Відсутність підтримки для рекурсії буде скоріше винятком з існуючих функцій, а не відсутністю нової функції. Зокрема, в контексті цього питання "нова особливість" означає "корисна поведінка, якої ми ще не вчили, існує", що не відповідає дійсності рекурсії, оскільки це логічне розширення особливостей, яких викладали (а саме те, що процедури містять інструкції та виклики процедур - це інструкції). Це так, ніби професор навчав додавання студента, а потім лаяв його за те, що додав одне і те ж значення не один раз, тому що "ми не охопили множення".
nmclean

48

Щоб відповісти на буквальне питання, а не на мета-питання: рекурсія - це особливість, в тому сенсі, що не всі компілятори та / або мови обов'язково дозволяють це. На практиці очікується від усіх (звичайних) сучасних компіляторів - і, звичайно, усіх компіляторів Java! - але це не є загально правдою.

Як надуманий приклад того, чому може не підтримуватися рекурсія, розглянемо компілятор, який зберігає зворотну адресу для функції у статичному місці; це може бути, наприклад, для компілятора для мікропроцесора, у якого немає стека.

Для такого компілятора, коли ви викликаєте подібну функцію

a();

вона реалізована як

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

та визначення ((),

function a()
{
   var1 = 5;
   return;
}

реалізується як

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

Сподіваємось, проблема, коли ви намагаєтесь викликати a()рекурсивно в такому компіляторі, очевидна; компілятор більше не знає, як повернутися із зовнішнього виклику, оскільки повернення адреси було перезаписано.

Для компілятора, який я насправді використовував (я думаю, наприкінці 70-х або на початку 80-х), без підтримки рекурсії проблема була дещо тонкішою, ніж ця: зворотна адреса зберігалася б у стеці, як у сучасних компіляторах, але локальні змінні не були 'т. (Теоретично це повинно означати, що рекурсія була можливою для функцій без нестатичних локальних змінних, але я не пам'ятаю, чи компілятор явно це підтримував чи ні. Можливо, потрібні неявні локальні змінні чомусь.)

Дивлячись вперед, я можу уявити спеціалізовані сценарії - можливо, паралельні системи, - де не потрібно надавати стек для кожного потоку, і де рекурсія дозволена лише тоді, коли компілятор може перефактурувати його в цикл. (Звичайно, примітивні компілятори, про які я обговорював вище, не були здатні до складних завдань, таких як код рефакторингу.)


Наприклад, препроцесор C не підтримує рекурсію в макросах. Визначення макросів поводяться аналогічно функціям, але ви не можете їх називати рекурсивно.
sth

7
Ваш "надуманий приклад" - це не все, на що надумали: стандарт Fortran 77 не дозволяв функціям називати себе рекурсивно - причина в тому, що ви описуєте майже все. (Я вважаю, що адреса, до якої потрібно перейти, коли функція була виконана, зберігалася в кінці самого коду функції, або щось еквівалентне цій компоновці.) Подивіться тут трохи про це.
alexis

5
Шейдерні мови або мови GPGPU (скажімо, GLSL, Cg, OpenCL C) не підтримують рекурсію, як приклад. Наскільки аргумент "не всі мови підтримують це", безумовно, справедливий. Рекурсія передбачає еквівалент стека (він не обов'язково повинен бути стеком , але повинен бути засіб якось зберігати зворотні адреси та кадри функцій ).
Деймон

Компілятор Fortran, над яким я трохи працював на початку 1970-х, не мав стека викликів. Кожна підпрограма або функція мала статичні області пам'яті для зворотної адреси, параметрів та власних змінних.
Патрісія Шанахан

2
Навіть деякі версії Turbo Pascal відключили рекурсію за замовчуванням, і вам довелося встановити директиву компілятора, щоб її включити.
dan04

17

Вчитель хоче знати, ви навчалися чи ні. Мабуть, ви не вирішили проблему так, як він вас навчив ( хороший спосіб ; ітерація), і, таким чином, вважає, що ви цього не зробили. Я все для творчих рішень, але в цьому випадку мені доводиться погоджуватися з вашим викладачем з іншої причини:
Якщо користувач надає недійсне введення занадто багато разів (тобто, натискаючи клавішу Enter), у вас буде виняток переповнення стека і ваш рішення вийде з ладу. Крім того, ітераційне рішення є більш ефективним та легшим у обслуговуванні. Я думаю, що це причина, яку вам мав дати ваш вчитель.


2
Нам не сказали виконати це завдання яким-небудь конкретним чином; і ми дізналися про методи, а не лише про ітерацію. Крім того, я хотів би залишити, який з них простіше читати на особисті переваги: ​​я вибрав те, що мені добре виглядало. Помилка SO для мене нова, хоча думка про те, що рекурсія є самою особливістю, все ще не здається обґрунтованою.

3
"Я б залишив, який з них легше читати за особистими уподобаннями". Домовились. Рекурсія не є функцією Java. Це такі.
Майк

2
@Vality: Усунення хвостових викликів? Деякі JVM можуть це зробити, але майте на увазі, що він також повинен підтримувати стеження стека за винятками. Якщо це дозволяє усунути хвостові дзвінки, слід стека, сформований наївно, може стати недійсним, тому деякі JVM не роблять TCE з цієї причини.
icktoofay

5
У будь-якому випадку, покладаючись на оптимізацію, щоб зробити ваш зламаний код менш зламаним, це досить погана форма.
cHao

7
+1, дивіться, що нещодавно в Ubuntu екран входу був зламаний, коли користувач натискав кнопку Enter постійно, те ж саме відбувається з XBox
Себастьян

13

Відрахування балів через те, що "ми не охопили рекурсії в класі" - жахливо. Якщо ви навчилися викликати функцію A, яка викликає функцію B, яка викликає функцію C, яка повертається назад до B, яка повертається назад, A, яка повертається назад абоненту, і вчитель не сказав вам прямо, що це повинні бути різні функції (які наприклад, у старих версіях FORTRAN), немає причини, що A, B і C не можуть бути однаковими функціями.

З іншого боку, нам доведеться побачити фактичний код, щоб вирішити, чи є у вашому конкретному випадку використання рекурсії справді правильним. Деталей не так багато, але це звучить неправильно.


10

Є багато точок зору, на які слід звернути увагу щодо конкретного питання, яке ви задали, але те, що я можу сказати, - це те, що з точки зору вивчення мови, рекурсія не є особливістю самостійно. Якщо ваш професор дійсно закрепив вам бали за використання "функції", якої він ще не вчив, це було неправильно, але, як я вже сказав, є інші точки зору, які слід враховувати тут, що насправді робить професор правильним при відрахуванні балів.

З того, що я можу зробити з вашого запитання, використовуючи рекурсивну функцію, щоб запитати про введення даних у разі невдачі введення, - це не є хорошою практикою, оскільки виклик кожної рекурсивної функції пересувається на стек. Оскільки ця рекурсія керується введенням користувача, можливо нескінченна рекурсивна функція і, таким чином, призводить до StackOverflow.

Немає різниці між цими двома прикладами, які ви згадали у своєму запитанні, в сенсі того, що вони роблять (але вони відрізняються іншими способами). В обох випадках у стек завантажується зворотна адреса та вся інформація про метод. У випадку рекурсії зворотна адреса - це просто рядок відразу після виклику методу (звичайно, це не зовсім те, що ви бачите в самому коді, а скоріше в коді, створеному компілятором). У Java, C та Python рекурсія є досить дорогою порівняно з ітерацією (загалом), оскільки вимагає виділення нового кадру стека. Не кажучи вже про те, що ви можете отримати виняток переповнення стека, якщо введення не надто дійсне.

Я вважаю, що професор вирахував бали, оскільки рекурсія вважається предметом самостійно і малоймовірно, що хтось, хто не має досвіду програмування, не подумає про рекурсію. (Звичайно, це не означає, що вони цього не хочуть, але навряд чи).

ІМХО, я думаю, що професор має рацію, віднімаючи вам бали. Ви могли легко взяти частину перевірки на інший метод і використовувати її так:

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

Якщо те, що ви зробили, справді можна вирішити таким чином, то те, що ви зробили, було поганою практикою і цього слід уникати.


Мета самого методу - перевірити вхід, потім викликати та повернути результат іншого методу (саме тому він повертається сам). Щоб бути конкретним, він перевіряє, чи є переміщення в грі Tic-Tac-Toe дійсним, а потім повертається hasWon(x, y, piece)(щоб перевірити лише постраждалий рядок і стовпець).

ви можете просто взяти частину перевірки ТІЛЬКО і поставити її в інший метод, який називається "GetInput", наприклад, а потім використовувати її, як я написав у своїй відповіді. Я відредагував свою відповідь тим, як вона повинна виглядати. Звичайно, ви можете змусити GetInput повернути тип, який містить необхідну інформацію.
Йонатан Нір

1
Йонатан Нір: Коли рекурсія була поганою практикою? Можливо, JVM підірветься тому, що VM Hotspot не вдалося оптимізувати через безпеку байтового коду, і щось було б приємним аргументом. Чим ваш код відрізняється від іншого, ніж він використовує інший підхід?

1
Рекурсія не завжди є поганою практикою, але якщо її можна уникнути і зберегти код чистим та легким у обслуговуванні, цього слід уникати. У Java, C та Python рекурсія є досить дорогою порівняно з ітерацією (загалом), оскільки вимагає виділення нового кадру стека. У деяких компіляторах C можна використовувати прапор компілятора, щоб усунути цю накладну, яка перетворює певні типи рекурсії (власне, певні типи хвостових викликів) у стрибки замість викликів функцій.
Йонатан Нір

1
Незрозуміло, але якщо ви замінили цикл з невизначеною кількістю ітерацій рекурсією, то це погано. Java не гарантує оптимізацію хвостових дзвінків, тому у вас може бути легко не вистачати місця у стеку. У Java не використовуйте рекурсії, якщо тільки у вас гарантовано буде обмежена кількість ітерацій (зазвичай логарифмічних порівняно із загальним розміром даних).
гайда

6

Можливо, ваш професор цього ще не навчив, але це здається, що ви готові дізнатися переваги та недоліки рекурсії.

Основна перевага рекурсії полягає в тому, що рекурсивні алгоритми часто писати набагато простіше і швидше.

Основним недоліком рекурсії є те, що рекурсивні алгоритми можуть викликати переповнення стека, оскільки кожен рівень рекурсії вимагає додавання до стеку додаткового кадру стека.

Що стосується виробничого коду, коли масштабування може призвести до набагато більше рівнів рекурсії у виробництві, ніж в тестових програмістах, недолік зазвичай переважує перевагу, а рекурсивного коду часто уникають, коли це можливо.


1
Будь-який потенційно ризикований рекурсивний алгоритм завжди може бути тривіально переписаний для використання явного стека - стек викликів, зрештою, є лише стеком. У цьому випадку, якщо ви переписали рішення для використання стека, це виглядало б смішно - додаткові докази того, що рекурсивна відповідь не дуже хороша.
Aaronaught

1
Якщо переповнення стека є проблемою, вам слід використовувати мову / час виконання, який підтримує оптимізацію хвостових викликів, наприклад .NET 4.0 або будь-яку функціональну мову програмування
Себастьян

Не всі рекурсії - це хвостові дзвінки.
Уоррен Роса

6

Що стосується конкретного питання, чи є рекурсія особливістю, я схильний сказати «так», але після повторного тлумачення питання. Існують загальні варіанти дизайну мов та компіляторів, які роблять можливою рекурсію, і існують цілі Тюрінг мови, які взагалі не дозволяють рекурсії . Іншими словами, рекурсія - це здатність, яка забезпечується певними варіантами дизайну мови / компілятора.

  • Підтримка першокласних функцій робить рекурсію можливою за дуже мінімальних припущень; див. приклад циклів написання в Unlambda для прикладу, або цей тупий вираз Python, що не містить само посилань, циклів чи призначень:

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • Мови / компілятори, які використовують пізнє зв’язування або визначають прямі декларації , роблять можливою рекурсію. Наприклад, хоча Python дозволяє наведений нижче код, це вибір дизайну (пізнє прив'язування), а не вимога до системи, що завершує Тьюрінга . Взаємно рекурсивні функції часто залежать від підтримки декларацій вперед.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • Статично типізовані мови, які дозволяють рекурсивно визначені типи, сприяють включенню рекурсії. Дивіться цю реалізацію комбінатора Y у Go . Без рекурсивно визначених типів все одно можна було б використовувати рекурсію в Go, але я вважаю, що комбінатор Y спеціально був би неможливим.


1
Це змусило мою голову вибухнути, особливо Unlambda +1
Джон Пауелл

Комбінатори з фіксованою точкою важкі. Коли я вирішив вивчити функціональне програмування, я змусив себе вивчити комбінатор Y, поки не зрозумів це, а потім застосувати його для написання інших корисних функцій. Взяв мене деякий час, але добре того вартий.
wberry

5

З того, що я можу зробити з вашого запитання, використовуючи рекурсивну функцію, щоб запитати про введення даних у разі невдачі вводу, не є хорошою практикою. Чому?

Оскільки кожен рекурсивний виклик функцій висувається на стек. Оскільки ця рекурсія керується введенням користувача, можливо мати нескінченну рекурсивну функцію і, таким чином, утворюватися StackOverflow :-p

Наявність нерекурсивного циклу для цього - це шлях.


Основна частина методу, про який йдеться, та мета самого методу полягає у валідації даних за допомогою різних перевірок. Процес починається заново, якщо введення недійсне, поки введення не буде правильним (згідно інструкцій).

4
@fay Але якщо введення занадто багато разів недійсне, ви отримаєте StackOverflowError. Рекурсія є більш елегантною, але на моїх очах, як правило, більше проблеми, ніж звичайна петля (через штабелі).
Михайло Яворський

1
Тож цікавий і хороший момент. Я не вважав цю помилку. Хоча чи можна досягти того ж ефекту, використовуючи while(true)той самий метод? Якщо так, я б не сказав, що це підтримує різницю між рекурсією, добре знати, як це є.

1
@fay while(true)- нескінченна петля. Якщо у вас немає breakзаяви, я не бачу сенсу в цьому, якщо ви не намагаєтеся зламати програму хаха. Моя думка, якщо ви викликаєте той самий метод (це рекурсія), він іноді надасть вам StackOverflowError , але якщо ви використовуєте whileабо forцикл, він не буде. Проблема просто не існує при регулярному циклі. Можливо, я неправильно зрозумів тебе, але моя відповідь тобі - ні.
Михайло Яворський

4
Мені це чесно здається, мабуть, справжньою причиною, чому професор зняв бали =) Він, можливо, не дуже добре пояснив це, але це правдива скарга, якщо ви сказали, що ви використовували його таким чином, який би вважався дуже поганим, якщо ні відвертий недолік у більш серйозному коді.
Командир Коріандр Саламандер

3

Рекурсія - це концепція програмування , особливість (як ітерація) та практика . Як видно із посилання, існує велика область досліджень, присвячена цій темі. Можливо, нам не потрібно заглиблюватися в тему, щоб зрозуміти ці моменти.

Рекурсія як особливість

Простіше кажучи, Java підтримує це неявно, тому що дозволяє методу (який в основному є особливою функцією) мати "знання" про себе та інших методах, що складають клас, до якого він належить. Розглянемо мову, де це не так: ви зможете написати тіло цього методу a, але ви не зможете включити виклик до aнього. Єдиним рішенням буде використання ітерації для отримання того ж результату. У такій мові вам доведеться розрізняти функції, які усвідомлюють власне існування (використовуючи певний синтаксичний маркер), і ті, хто цього не робить! Насправді, ціла група мов робить це розрізнення (див. Лямбди Lisp та ML Наприклад, сім'ї ). Цікаво, що Perl навіть дозволяє анонімні функції (т.зв. ), називаючи себе рекурсивно (знову ж таки, з виділеним синтаксисом).

немає рекурсії?

Для мов, які навіть не підтримують можливість рекурсії, часто існує інше рішення у вигляді комбінатора з фіксованою точкою , але воно все ще потребує мови для підтримки функцій так званих об'єктів першого класу (тобто об'єктів, які можуть бути маніпулюють всередині самої мови).

Рекурсія як практика

Наявність цієї функції доступною мовою не означає, що вона є ідіоматичною. В Java 8 включені лямбда-вирази, тому може бути простіше застосувати функціональний підхід до програмування. Однак є практичні міркування:

  • синтаксис все ще не дуже рекурсійний
  • компілятори, можливо, не зможуть виявити цю практику та оптимізувати її

Суть

На щастя (або, точніше, для зручності використання), Java нехай методи знають про себе за замовчуванням, і, таким чином, підтримують рекурсію, тому це насправді не є практичною проблемою, але вона все ще залишається теоретичною, і я вважаю, що ваш учитель хотів вирішити це конкретно. Крім того, у світлі недавньої еволюції мови вона може перетворитися на щось важливе в майбутньому.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.