Чому так багато мов передається за значенням?


36

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

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


5
void acceptEntireProgrammingLanguageByValue(C++);
Томас Едінг

4
Це може бути і гірше. Деякі старі мови також дозволяли дзвонити по імені
hugomg

25
Насправді в С не можна пройти посилання. Ви можете передавати вказівник за значенням , що дуже схоже на пропускний посилання, але не те саме. Однак у C ++ ви можете перейти за посиланням.
Мейсон Уілер

@Mason Wheeler Ви можете детальніше розібратися чи додати посилання, тому що ваша заява мені не зрозуміла, оскільки я не гуру С / С ++, просто звичайний програміст, дякую
Betlista

1
@Betlista: За допомогою переходу посилань ви можете написати процедуру заміни, яка виглядає приблизно так: temp := a; a := b; b := temp;І коли вона повертається, значення aі bбудуть помінятися місцями. Немає способу зробити це в C; вам потрібно передати покажчики на aта, bі swapрутина повинна діяти на значення, на які вони вказують.
Мейсон Уілер

Відповіді:


58

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

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

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


4
+1 Масиви, що розпадаються на покажчики, є помітним винятком, але загальне пояснення добре.
dasblinkenlight

6
@dasblinkenlight: масиви - це болісне місце в C :(
Matthieu M.

55

Call-by-value та call-by-reference - це методи реалізації, які давно помилялися в режимах передачі параметрів.

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

ALGOL придумав дзвінки по імені та за закликом за значенням. Значення дзвінка для речей, які не мали бути змінені (вхідні параметри). Call-by-name було для вихідних параметрів. Виклик за назвою виявився головним кроком, і ALGOL 68 скинув його.

PASCAL надав дзвінки за вартістю та за посиланням. Це не дало жодного способу програмісту сказати компілятору, що він передає великий об'єкт (як правило, масив) за посиланням, щоб уникнути зриву стека параметрів, але що об'єкт не слід змінювати.

PASCAL додав покажчики до лексикону дизайну мови.

C надає заклик за значенням і моделює виклик за посиланням, визначаючи оператора kludge для повернення покажчика на довільний об'єкт у пам'яті.

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

C ++ додав кляж зверху на C Cludge, щоб забезпечити виклик за посиланням.

Тепер, як прямий результат виклику за значенням проти виклику за посиланням проти заклику за вказівником, програма C і C ++ (програмісти) мають жахливі головні болі з покажчиками const та покажчиками на const (лише для читання) об’єкти.

Ада вдалося уникнути цього цілого кошмару.

Ада не має явного виклику за значенням проти виклику за посиланням. Швидше за все, у Ada є параметри (які можуть бути прочитані, але не записані), параметри (які ОБОВ'ЯЗКОВО записати, перш ніж їх можна прочитати), а також параметри, які можна прочитати та записати в будь-якому порядку. Компілятор вирішує, чи передається певний параметр за значенням або посиланням: він прозорий для програміста.


1
+1. Це єдина відповідь, яку я бачив тут поки що, що насправді відповідає на питання і має сенс, і не використовує це як прозорий привід для про-ПП.
Мейсон Уілер

11
+1 для "Пізніші мови скопіювали C, головним чином тому, що дизайнери ніколи не бачили нічого іншого". Кожного разу, коли я бачу нову мову з 0-префіксами восьмиконстант, я трохи гину всередині.
librik

4
Це може бути перше речення Біблії програмування: In the beginning, there was FORTRAN.

1
Що стосується ADA, якщо глобальна змінна буде передана параметру вводу / виходу, чи перешкоджатиме мові вкладений виклик перевірити або змінити його? Якщо ні, то я думаю, що буде чітка різниця між параметрами вводу / виходу та ref. Особисто мені хотілося б, щоб мови явно підтримували всі комбінації "вхід / вихід / вихід + вихід" та "за значенням / за посиланням / не байдуже", щоб програмісти могли запропонувати максимальну чіткість щодо свого наміру, але оптимізатори мав би максимальну гнучкість у впровадженні [до тих пір, поки це не змішано з наміром програміста].
supercat

2
@Neikos Існує також той факт, що 0префікс не відповідає всім іншим неофіційним префіксом десяти. Це може бути дещо виправданим у мові C (і в основному мовах, сумісних з джерелами, наприклад, C ++, об'єктив C), якщо восьмикратка була єдиною альтернативною базою при введенні, але коли більш сучасні мови використовують обидві 0xі 0з самого початку, це просто змушує їх виглядати погано Обміркував.
8bittree

13

Прохід за посиланням дозволяє отримати дуже тонкі непередбачувані побічні ефекти, які дуже важко, поряд з неможливим простежити, коли вони починають викликати ненавмисну ​​поведінку.

Передача за значенням, особливо final, staticабо constвхідні параметри, змушує весь цей клас помилок зникати.

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


7
Це одна з тих речей, яку ви з'ясували, намагаючись налагодити "стародавній" код VB, де все посилається як за замовчуванням.
jfrankcarr

Можливо, це просто мій фон, але я поняття не маю, про які "дуже тонкі ненавмисні побічні ефекти, які дуже важкі, поруч із неможливими простежити", ви говорите. Я звик до Паскаля, де значення за замовчуванням є стандартним, але посилання може використовуватися шляхом явного маркування параметра (в основному тієї ж моделі, що і C #,), і у мене ніколи цього не виникало проблем. Я бачу, як це було б проблематично в "древньому VB", де By-ref - це за замовчуванням, але коли by-ref відключається, це змушує задуматися про це, поки ви пишете його.
Мейсон Уілер

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

1
@MasonWheeler Прості приклади псевдоніму, як Swapхаки, які не використовують тимчасову змінну, хоча, ймовірно, самі по собі не є проблемою, показують, наскільки важким може бути більш складний приклад налагодження.
Марк Херд

1
@MasonWheeler: Класичний приклад у FORTRAN, який призвів до приказки "Змінні не будуть; константи - ні", передасть постійну з плаваючою комою на зразок 3.0 функції, яка модифікує параметр, що передається. Для будь-якої постійної з плаваючою комою, переданої функції, система створила б "приховану" змінну, яка була ініціалізована з належним значенням і могла бути передана функціям. Якщо функція додала 1,0 до свого параметра, довільний підмножина 3.0 у програмі може раптом стати 4.0.
supercat

8

Чому так багато мов передається за значенням?

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

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

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

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

Існує дві основні причини використання прохідних посилань:

  1. моделювання декількох повернених значень
  2. ефективність

Особисто я вважаю, що №1 є хибним: це майже завжди привід для поганого API та / або мовного дизайну:

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

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. Часто вам навіть не потрібно кілька повернених значень. Наприклад, одна популярна модель полягає в тому, що підпрограма повертає код помилки і фактичний результат записується назад за посиланням. Натомість слід просто кинути виняток, якщо помилка несподівана, або повернути значення, Either<Exception, T>якщо помилка очікується. Інша закономірність - повернути булеве значення, яке вказує, чи була операція успішною, і повертає фактичний результат шляхом посилання. Знову ж таки, якщо збій несподіваний, слід замість цього викинути виняток, якщо очікується збій, наприклад, шукаючи значення у словнику, вам слід повернути Maybe<T>а.

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

(Я розумію, що Haskell передається через посилання, хоча я не впевнений).

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

Справді, специфікація Haskell не визначає який - або конкретної стратегії оцінки. У більшості реалізацій Hakell використовується суміш дзвінків за іменем та "за потребою" (варіант "дзвінок за іменем" із запам'ятовуванням), але стандарт цього не передбачає.

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


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

+1 Я повністю згоден з цим пунктом . Сенс розбиття великих програм на невеликі підпрограми полягає в тому, що ви можете міркувати про підпрограми самостійно. Перехід за посиланням порушує цю властивість. (Як і спільний стан, що змінюється.)
Ремі

3

Існує різна поведінка залежно від моделі виклику мови, типу аргументів та моделей пам'яті мови.

За допомогою простих нативних типів проходження за значенням дозволяє передавати значення в регістри. Це може бути дуже швидко, оскільки значення не потрібно завантажувати з пам'яті, а також зберігати назад. Подібна оптимізація можлива і просто шляхом повторного використання пам’яті, яка використовується аргументом, як тільки виклик буде виконаний з нею, не ризикуючи возитися з копією об'єкта викликаючого. Якби аргумент був тимчасовим об'єктом, ви, ймовірно, зберегли б повну копію, роблячи це (C ++ 11 зробить подібну оптимізацію ще більш очевидною завдяки новому правильному посиланню та його семантичному переміщенню).

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

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


2

Ви можете передавати вираз за значенням, це природно. Передача вираження (тимчасової) посилання ... дивна.


1
Крім того, передача вираження за допомогою тимчасової посилання може призвести до поганих помилок (мовою, що належить до стану), коли ви щасливо її зміните (оскільки це просто тимчасово), але потім вона справляє зворотну реакцію, коли ви фактично передаєте змінну, і ви повинні створити некрасиве рішення, як проходження foo +0 замість foo.
herby

2

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

Посилання, як і глобальні, іноді корисні, але вони не повинні бути вашим першим вибором.


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