Чому більшість мов програмування підтримують лише повернення одного значення з функції? [зачинено]


118

Чи є причина, чому функції в більшості (?) Мов програмування розроблені для підтримки будь-якої кількості вхідних параметрів, але лише одного зворотного значення?

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

Чи є для цього пояснення?


40
бо ти можеш повернути масив ...
Nathan hayfield

6
То чому б не дозволити лише один аргумент? Я підозрюю, що це пов'язано з мовою. Візьміть кілька ідей, згладьте їх в одне, що ви повернетеся до того, кому пощастило / нещасливо слухати. Повернення майже як думка. "це" - це те, що ти робиш із "цими".
Ерік Реппен

30
Я вважаю , що підхід пітона досить простий і елегантний , коли ви повинні повертати кілька значень просто повертаю кортеж: def f(): return (1,2,3)а потім ви можете використовувати кортеж-розпакування «розкол» кортеж: a,b,c = f() #a=1,b=2,c=3. Не потрібно створювати масив і витягувати вручну елементи, не потрібно визначати будь-який новий клас.
Бакуріу

7
Я можу повідомити вам, що Matlab має змінну кількість повернених значень. Кількість вихідних аргументів визначається викликає підпису (наприклад , [a, b] = f()проти [a, b, c] = f()) , і отриманий всередині fшляхом nargout. Я не є великим шанувальником Matlab, але це часом насправді дуже зручно.
gerrit

5
Я думаю, якщо більшість мов програмування розроблені таким чином, є дискусійним. В історії мов програмування було створено кілька дуже популярних мов, створених таким чином (як Pascal, C, C ++, Java, класичний VB), але сьогодні також існує багато інших мов, де все більше і більше шанувальників, які дозволяють багаторазово повертатися значення.
Док Браун

Відповіді:


58

Деякі мови, як-от Python , підтримують кілька повернених значень на самому собі, тоді як деякі мови, такі як C #, підтримують їх через свої базові бібліотеки.

Але загалом, навіть у мовах, які їх підтримують, кілька повернених значень використовуються не часто, оскільки вони неохайні:

  • Функції, що повертають кілька значень, важко чітко назвати .
  • Легко помилитися з порядком повернутих значень

    (password, username) = GetUsernameAndPassword()  
    

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

  • Мови OOP вже мають кращу альтернативу декільком повернутим значенням: класи.
    Вони більш сильно набрані, вони зберігають повернені значення, згруповані як одна логічна одиниця, і зберігають імена (властивості) повернених значень у всіх напрямках.

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


50
Важко сказати, що повернення кортежу - це повернення кількох речей. Повертається один кортеж . Написаний вами код просто розпаковує його, використовуючи синтаксичний цукор.

10
@Lego: Я не бачу відмінності - кортеж за визначенням має кілька значень. Що б ви вважали "множинними значеннями повернення", якби не це?
BlueRaja - Danny Pflughoeft

19
Це дійсно туманне відзначення, але врахуйте порожній кортеж (). Це одне або нульові речі? Особисто я б сказав одне. Я можу призначити x = ()просто чудово, як я можу призначити x = randomTuple(). В останньому, якщо повернутий кортеж порожній чи ні, я все одно можу призначити один повернутий кортеж x.

19
... Я ніколи не стверджував, що кортежі не можна використовувати для інших речей. Але стверджувати, що "Python не підтримує множинні повернені значення, він підтримує кортежі" просто є надзвичайно безглуздо педантичним. Це все-таки правильна відповідь.
BlueRaja - Danny Pflughoeft

14
Ні кортежі, ні класи не є "множинними значеннями".
Андрес Ф.

54

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

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


5
@FrustratedWithFormsDesigner - це питання з’явилося небагато в недавньому запитанні . Я можу порахувати, з однієї сторони, скільки разів я хотів кілька разів виходити за 20 років.
Теластин

61
Функції математики та функції в більшості мов програмування - це два дуже різні звіри.
tdammers

17
@tdammers в перші дні вони були дуже схожі по думці. Фортран, паскаль тощо, де сильно впливає математика більше, ніж архітектура обчислень.

9
@tdammers - як так? Я маю на увазі для більшості мов, зрештою, зводиться до обчислення лямбда - один вхід, один вихід, відсутність побічних ефектів. Все інше - це симуляція / хак-над цим. Функції програмування можуть бути не чистими, тому багато входів можуть давати однаковий вихід, але дух є.
Теластин

16
@SteveEvers: прикро, що ім'я "функція" перейняло імперативне програмування, замість більш підходящої "процедури" або "рутини". У функціональному програмуванні функція набагато ближче нагадує математичні функції.
tdammers

35

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

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

Для типів функцій, про які ви говорите (тобто функції, які повертають кілька значень, різних типів ), я бачу це дещо інакше, ніж вам здається: я бачу необхідність / використання параметів як вирішення для кращого дизайну чи більш корисна структура даних. Наприклад, я вважаю за краще, щоб *.TryParse(...)методи повернули Maybe<T>монаду, а не використовувати парам. Подумайте цей код у F #:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

Підтримка компілятора / IDE / аналізу дуже хороша для цих конструкцій. Це дозволило б вирішити більшу частину "потреби" у парамах. Якщо чесно сказати, я не можу придумати жодних інших методів з власної сторони, де це не було б рішенням.

Для інших сценаріїв - тих, яких я не пам'ятаю - достатньо простого кортежу.


1
Крім того, я дуже хотів би мати можливість писати на C #: var (value, success) = ParseInt("foo");що було б перевірено типом компіляції, оскільки (int, bool) ParseInt(string s) { }було оголошено. Я знаю, що це можна зробити за допомогою дженериків, але все-таки це приємно доповнить мову.
Гримаса відчаю

10
@GrimaceofDespair те, що ви дійсно хочете, - це руйнування синтаксису, а не кілька повернених значень.
Доменіч

2
@warren: Так. Дивіться тут і зауважте, що таке рішення не буде безперервним: en.wikipedia.org/wiki/Well-definition
Стівен Еверс

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

1
@MichaelSiler (я згоден з вашим коментарем) Але зауважте, що аргумент є зворотним: "аргументи, які функції програми можуть повернути кілька значень, тому що вони можуть повернути одне значення кортежу, теж не є переконливими" :)
Андрес Ф.

23

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


Ах! Дуже цікава деталь. +1!
Стівен Еверс

Це має бути прийнятою відповіддю. Зазвичай люди думають про цільові машини, коли будували компілятори. Інша аналогія - це те, чому ми маємо int, float, char / string тощо, тому що це підтримується цільовою машиною. Навіть якщо ціль не є голим металом (наприклад, jvm), ви все одно хочете отримати гідну продуктивність, не переймаючи занадто багато.
imela96

26
... Ви можете легко визначити умову виклику для повернення декількох значень функції, майже таким же чином ви можете визначити умову виклику для передачі декількох значень функції. Це не відповідь. -1
BlueRaja - Danny Pflughoeft

2
Було б цікаво знати, чи коли-небудь двигуни виконання на основі стека (JVM, CLR) коли-небудь розглядали / дозволяли множинні повернені значення .. це повинно бути досить легко; абонент повинен просто спливати потрібну кількість значень, так само, як він висуває потрібну кількість аргументів!
Лоренцо Дематте

1
@David ні, cdecl допускає (теоретично) необмежену кількість параметрів (тому можливі функції varargs ) . Хоча деякі компілятори С можуть обмежити вас кількома десятками або сотнями аргументів на функцію, що, на мою думку, все-таки більш ніж розумно -_-
BlueRaja - Danny Pflughoeft

18

Дуже багато випадків використання, коли ви раніше використовували кілька повернених значень, просто не потрібні в сучасних мовних функціях. Хочете повернути код помилки? Накиньте виняток або поверніть його Either<T, Throwable>. Хочете повернути необов’язковий результат? Повернути Option<T>. Хочете повернути один із декількох типів? Повернути Either<T1, T2>або теговане союз.

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

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

Рубін

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

Пітон

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

Скала

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

Хаскелл

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

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

1
Але як щодо динамічних мов, таких як Python чи Ruby? Припустимо, я записую щось на зразок функції Matlab sort: sorted = sort(array)повертає лише відсортований масив, тоді як [sorted, indices] = sort(array)повертає обидва. Єдиний спосіб, про який я можу придумати в Python, - це передавати прапор sortуздовж ліній sort(array, nout=2)або sort(array, indices=True).
gerrit

2
@MikeCellini Я не думаю, що так. Функція може визначити, скільки вихідних аргументів функція викликається ( [a, b, c] = func(some, thing)), і діяти відповідно. Це корисно, наприклад, якщо обчислення першого вихідного аргументу є дешевим, але обчислення другого є дорогим. Я не знайомий з будь-якою іншою мовою, де nargoutдоступний еквівалент Matlabs .
Герріт

1
@gerrit правильне рішення в Python, щоб написати це: sorted, _ = sort(array).
Майлз Рут

1
@MilesRout: І sortфункція може сказати, що їй не потрібно обчислювати індекси? Це здорово, я цього не знав.
Йорг W Міттаг

12

Справжньою причиною того, що одне повернене значення є настільки популярним, є вирази, які використовуються у багатьох мовах. Будь-якою мовою, де ви можете мати вираз, як x + 1ви вже думаєте, з точки зору єдиних повернутих значень, оскільки ви оцінюєте вираз у голові, розбиваючи його на шматки та визначаючи значення кожної частини. Ви дивитесь xі вирішуєте, що його значення дорівнює 3 (наприклад), і ви дивитесь на 1, а потім на васx + 1і складемо все разом, щоб вирішити, що значення цілого дорівнює 4. Кожна синтаксична частина виразу має одне значення, а не будь-яке інше число значень; це природна семантика виразів, яких очікують усі. Навіть коли функція повертає пару значень, вона все-таки дійсно повертає одне значення, яке виконує роботу двох значень, тому що уявлення про функцію, яка повертає два значення, які якимось чином не згорнуті в одну колекцію, надто дивна.

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

Perl - це ще одна мова, яка іноді може діяти так, як функції повертають кілька значень, хоча зазвичай це просто вважається поверненням списку. Способи списку "інтерполяції" в Perl дають нам такі списки, (1, foo(), 3)які можуть містити 3 елементи, як це очікувала більшість людей, які не знають Perl, але так само легко можуть мати лише 2 елементи, 4 елементи або будь-яку більшу кількість елементів, залежно від foo(). Списки в Perl сплющені так, що синтаксичний список не завжди має семантику списку; це може бути просто шматок більшого списку.

Іншим способом, щоб функції повертали кілька значень, було б мати альтернативну семантику вираження, де будь-яке вираження може мати кілька значень, і кожне значення представляє можливість. Візьміть x + 1ще раз, але на цей раз уявіть, що xмає два значення {3, 4}, тоді значення x + 1будуть {4, 5}, а значення x + xбуде {6, 8}, а може бути {6, 7, 8} , залежно від того, чи дозволяється однією оцінкою використовувати кілька значень для x. Мова подібної може бути реалізована за допомогою зворотного відстеження, як і Prolog, щоб дати кілька відповідей на запит.

Коротше кажучи, виклик функції - це єдина синтаксична одиниця, а одна синтаксична одиниця має єдине значення в семантиці вираження, яку ми всі знаємо і любимо. Будь-яка інша семантика змусить вас до дивних способів вчинити такі дії, як Perl, Prolog або Forth.


9

Як запропоновано у цій відповіді , мова йде про технічну підтримку, хоча традиція в мовному дизайні також грає певну роль.

коли функція повертається, вона залишає вказівник на об'єкт, що повертається, у певному регістрі

З трьох перших мов, Fortran, Lisp та COBOL, перша використовувала єдине повернене значення, як воно моделювалося з математики. Другий повертав довільну кількість параметрів так само, як і отримував їх: як список (можна також стверджувати, що він лише передав і повертав один параметр: адресу списку). Третє поверне нуль або одне значення.

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

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

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

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

Тож давайте розглянемо мову складання. Давайте спочатку розглянемо 6502 , мікропроцесор 1975 року, який знаменито використовували мікрокомп'ютери Apple II та VIC-20. Це було дуже слабко порівняно з тим, що використовувалося в мейнфреймі та мінікомп'ютерах того часу, хоча й потужним порівняно з першими комп’ютерами за 20, 30 років до цього, на зорі мов програмування.

Якщо ви подивитесь на технічний опис, у нього є 5 регістрів плюс кілька однобітних прапорів. Єдиним "повним" реєстром був лічильник програм (ПК) - той регістр вказує на наступну інструкцію, яку потрібно виконати. Інші регістри, де акумулятор (A), два "індексні" регістри (X і Y) і вказівник стека (SP).

Виклик підпрограми вводить ПК у пам'ять, на яку вказує SP, а потім зменшує SP. Повернення з підпрограми працює в зворотному порядку. На стеку можна натиснути та витягнути інші значення, але важко посилатися на пам'ять щодо SP, тому писати підпрограми для повторного вступу було важко. Ця річ, яку ми сприймаємо як належне, викликаючи підпрограму в будь-який час, коли нам здається, не була такою поширеною в цій архітектурі. Часто створюється окремий "стек", щоб параметри та повернення підпрограми залишалися окремими.

Якщо ви подивитеся на процесор, який надихнув 6502, 6800 , він мав додатковий регістр, індекс регістру (IX), ширину як SP, який міг отримати значення від SP.

На машині виклик підпрограми повторного учасника полягав у натисканні параметрів на стеку, натисканні ПК, зміні ПК на нову адресу, і тоді підпрограма підштовхує свої локальні змінні на стек . Оскільки кількість локальних змінних і параметрів відома, їх адресація може бути виконана відносно стека. Наприклад, функція, яка отримує два параметри і має дві локальні змінні, виглядатиме так:

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

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

8080 , який використовується на ТРС-80 і безліч CP / M мікрокомп'ютерів на основі могли б зробити що - щось схоже на 6800, натиснувши SP на стек , а потім вискакують його на непрямому регістрі, HL.

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

Проблема полягає в тому, як повернути щось ? Реєстри процесорів були не дуже численні на ранніх етапах, і часто потрібно було використовувати деякі з них навіть для того, щоб дізнатися, до якого фрагмента пам'яті потрібно звернутися. Повернення речей у стеку було б складним: вам доведеться вискакувати все, зберегти ПК, натиснути параметри повернення (які зберігатимуться там, де тим часом?), Потім знову натиснути ПК та повернутися.

Тож зазвичай робилося резервування одного регістра для значення повернення. Код, що викликає, знав, що повернене значення буде в певному реєстрі, яке повинно бути збережене, поки воно не може бути збережене або використане.

Давайте розглянемо мову, яка дійсно дозволяє отримати кілька повернених значень: Forth. Що Форт робить, це зберігати окремий стек повернення (RP) та стек даних (SP), так що вся функція повинна була виконати поп всі його параметри і залишити значення повернення на стеку. Оскільки стек повернення був окремим, він не заважав.

Як хтось, хто навчився мовою складання та Forth за перші шість місяців досвіду роботи з комп’ютерами, багатозначні повернені значення мені здаються цілком нормальними. Такі оператори, як Forth /mod, які повертають ціле поділ та інші, здаються очевидними. З іншого боку, я легко бачу, як хтось, чий ранній досвід був розумом С, вважає цю концепцію дивною: це суперечить їх вродженим очікуванням, що таке "функція".

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

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


@gnat Використання "інформувати" як у "для забезпечення істотної якості" було навмисним.
Даніель К. Собрал

сміливо відкатуйтесь, якщо ви сильно відчуваєте це; на моє читання вплив тут трохи підходить: "впливає ... важливим чином"
гнат

1
+1 Як зазначається, що розріджена кількість регістрів ранніх процесорів порівняно з рясним набором регістрів сучасних процесорів (які також використовуються в багатьох ABI, наприклад, x64 abi) може бути зміною ігор і раніше переконливими причинами повернути лише 1 значення це може бути лише історичною причиною.
BitTickler

Я не переконаний, що ранні 8-бітні мікропроцесори мають великий вплив на дизайн мови і те, що потрібно вважати необхідним для вимог C або Fortran у будь-якій архітектурі. Fortran передбачає, що ви можете передавати масиви масивів (по суті, вказівники). Отже, у вас вже є основні проблеми з реалізацією для звичайного Fortran на таких машинах, як 6502, через відсутність режимів адресації для покажчика + індексу, як це було обговорено у вашій відповіді та чому Чому компілятори C до Z80 створюють поганий код? на ретрокомп'ютері.SE.
Пітер Кордес

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

7

Функціональні мови, про які я знаю, можуть легко повертати кілька значень за допомогою використання кортежів (у динамічно набраних мовах ви навіть можете використовувати списки). Також кортежі підтримуються іншими мовами:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

У наведеному вище прикладі f- це функція, що повертає 2 вставки.

Аналогічно, ML, Haskell, F # тощо можуть також повертати структури даних (покажчики занадто низькі для більшості мов). Я не чув про сучасну мову GP з таким обмеженням:

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

Нарешті, outпараметри можна імітувати навіть у функціональних мовах за допомогою IORef. Є кілька причин, чому немає вбудованої підтримки для змінних більшості мов:

  • Незрозуміла семантика : чи відображається наступна функція 0, або 1? Я знаю мови, які надрукували б 0, і ті, які друкувалимуться 1. Є користь для обох (як з точки зору продуктивності, так і відповідності ментальній моделі програміста):

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • Не локалізовані ефекти : Як і в наведеному вище прикладі, ви можете виявити, що у вас може бути довгий ланцюг, і найпотужніша функція впливає на глобальний стан. Загалом, це ускладнює міркування про те, які вимоги функції є, і якщо зміна є законною. Зважаючи на те, що більшість сучасних парадигм намагаються або локалізувати ефекти (інкапсуляція в ООП), або усунути побічні ефекти (функціональне програмування), це суперечить цим парадигмам.

  • Зайві : якщо у вас є кортежі, у вас 99% функціональності outпараметрів і 100% ідіоматичного використання. Якщо ви додасте вказівники до суміші, ви покриєте решту 1%.

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


+1 Зазначимо, як функціональні мови вирішують це елегантно та безболісно.
Андрес Ф.

1
Технічно ви все ще повертаєте одне значення: D (Просто це одне значення є тривіальним, щоб розкласти на кілька значень).
Томас Едінг

1
Я б сказав, що параметр з реальною семантикою "поза" повинен вести себе як тимчасовий компілятор, який копіюється до місця призначення, коли метод нормально виходить; одна зі змінною семантики "inout" повинна поводитись як тимчасовий компілятор, який завантажується із змінної, що передається при вході, і записується назад при виході; той із семантикою "ref" повинен вести себе як псевдонім. Так звані "вихідні" параметри C # - це дійсно параметри "ref" та ведуть себе як такі.
supercat

1
Кортеж "вирішення" також не приходить безкоштовно. Він блокує можливості оптимізації. Якщо існував ABI, який дозволив повернути N повернутих значень у регістри процесора, компілятор міг насправді оптимізувати, замість того, щоб створювати екземпляр кортежу та конструювати його.
BitTickler

1
@BitTickler нічого не перешкоджає поверненню перших n полів структури, що передаються регістрами, якщо ви керуєте ABI.
Maciej Piechotka

6

Я думаю, це через вирази , наприклад (a + b[i]) * c.

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

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


4

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


3
Чи можете ви навести приклад, коли кілька повернень забезпечують більш чіткий та / або більш ефективний код?
Стівен Еверс

3
Наприклад, у програмуванні C ++ COM багато функцій мають один [out]параметр, але практично всі повертають HRESULT(код помилки). Було б цілком практично просто дістати туди пару. У мовах, які мають гарну підтримку кортежів, таких як Python, це звикає у багатьох кодах, які я бачив.
Фелікс Домбек

У деяких мовах ви повертаєте вектор з координатою X і Y, і повернення будь-якого корисного значення вважатиметься "успішним" за винятком, можливо, несучи це корисне значення, використовуючись для відмови.
doppelgreener

3
Багато часу ви закінчуєте кодування інформації у повернене значення неочевидними способами - тобто; негативні значення - це коди помилок, позитивні - результати. Юк. Доступ до хеш-таблиці, завжди безладним буде вказати, чи знайдено предмет, а також повернути його.
ddyer

@SteveEvers Matlab sortфункція зазвичай сортує масив: sorted_array = sort(array). Іноді мені потрібні відповідні індекси: [sorted_array, indices] = sort(array). Іноді я хочу лише, щоб показники: [~, indices]= сортувати (масив) . The function сортувати "насправді може сказати, скільки потрібних аргументів потрібно, тому, якщо потрібні додаткові роботи для 2 виходів порівняно з 1, він може обчислити ці результати лише за потреби.
Герріт

2

У більшості мов, де підтримуються функції, ви можете використовувати виклик функції в будь-якому місці, де може використовуватися змінна цього типу: -

x = n + sqrt(y);

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


5
Просто не використовуйте невідповідні функції. Це нічим не відрізняється від "проблеми", поставленої функціями, які не повертають ніяких значень, або повертають нечислові значення.
ddyer

3
У мовах, які я використовував, які пропонують кілька повернених значень (наприклад, SciLab), перше повернене значення є привілейованим, і воно буде використовуватися у випадках, коли потрібно лише одне значення. Тож ніякої реальної проблеми там немає.
The Photon

І навіть коли вони не такі, як, наприклад, при розпакуванні кортежу Python, ви можете вибрати, який саме вам потрібно:foo()[0]
Izkata

Точно, якщо функція повертає 2 значення, то тип повернення - це 2 значення, а не одне значення. Мова програмування не повинна читати вашу думку.
Марк Е. Хааз

1

Я просто хочу спиратися на відповідь Гарві. Я спочатку знайшов це запитання на веб-сайті новин (arstechnica) і знайшов дивовижне пояснення того, що я відчуваю, що дійсно відповідає суті цього питання і не має всіх інших відповідей (крім Harvey's):

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

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

Це як запитати, чому присвоєння є однією змінною за раз. Ви можете мати мову, яка дозволяє, наприклад, a, b = 1, 2. Але це закінчилося б на рівні машинного коду a = 1, а потім b = 2.

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


Якщо мови низького рівня, такі як C, підтримують декілька значень повернення як функцію першого класу, конвенції виклику C включатимуть декілька регістрів зворотних значень, аналогічно тому, що до 6 регістрів використовуються для передачі аргументів цілочислових / вказівних функцій у x86- 64 Система V ABI. (Насправді x86-64 SysV дійсно повертає структури до 16 байт, упаковані в реєстрову пару RDX: RAX. Це добре, якщо структура була тільки завантажена і буде збережена, але коштує додаткове розпакування порівняно з тим, що окремі члени навіть мають окремі регістри якщо вони вужчі, ніж 64 біти.)
Пітер Кордес,

Очевидною умовою буде RAX, потім аргументи, що передають аргументи. (RDI, RSI, RDX, RCX, R8, R9). Або в конвенції Windows x64, RCX, RDX, R8, R9. Але оскільки C не має в своєму розпорядженні декількома значеннями повернення, конвенції C ABI / виклики задають лише кілька регістрів повернення для широких цілих чисел та деяких структур. Див. Стандарт процедури виклику для архітектури ARM: 2 окремі, але пов'язані з ними значення повернення для прикладу використання широкого int для отримання компілятора для створення ефективної ASM для отримання 2 повернених значень на ARM.
Пітер Кордес

-1

Почалося з математики. FORTRAN, названий "Переклад формули", був першим компілятором. FORTRAN був і орієнтується на фізику / математику / техніку.

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

Go , наприклад, має кілька значень, що повертаються, і результат стає чистішим і менш двозначним , ніж при використанні «вихід» параметри. Після невеликого використання, це дуже природно і ефективно. Я рекомендую враховувати кілька повернених значень для всіх нових мов. Можливо, і для старих мов.


4
це не відповідає на поставлене запитання
gnat

@gnat як на мене це відповідає. Іншому вже потрібно мати деякі знання, щоб зрозуміти це, і ця людина, швидше за все, не задасть питання ...
Netch

@Netch навряд чи потрібно багато підґрунтя, щоб зрозуміти, що твердження типу "FORTRAN ... був першим компілятором" - це загальний безлад. Це навіть не помиляється. Так само, як і решта цієї "відповіді"
гнат

Посилання говорить, що раніше були спроби компіляторів, але що "команда FORTRAN під керівництвом Джона Бекуса в IBM, як правило, вважається тим, що представила перший повний компілятор у 1957 році". Питання було: чому лише один? Як я сказав. Це були здебільшого математика та інерція. Математичне визначення терміна "Функція" вимагає рівно одного значення результату. Так це була звична форма.
RickyS

-2

Це, мабуть, має більше спільного з спадщиною того, як здійснюються виклики функцій в інструкціях на процесорній машині, і тим, що всі мови програмування походять від машинного коду: наприклад, C -> Assembly -> Machine.

Як процесори виконують функціональні дзвінки

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

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

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