Почему так много языков передается по значению?

36

Даже в тех языках, где у вас есть явные манипуляции с указателями, такие как C, они всегда передаются по значению (вы можете передавать их по ссылке, но это не поведение по умолчанию).

Какова польза от этого, почему так много языков передаются значениями и почему другие передаются по ссылке ? (Я понимаю, что Haskell передается по ссылке, хотя я не уверен).

В моем коде нет ошибок
источник
5
void acceptEntireProgrammingLanguageByValue(C++);
Томас Эдинг
4
Могло быть и хуже. Некоторые старые языки также допускаются для звонка по имени
hugomg
25
На самом деле, в C вы не можете перейти по ссылке. Вы можете передавать указатель по значению , что очень похоже на передачу по ссылке, но не одно и то же. В C ++, однако, вы можете перейти по ссылке.
Мейсон Уилер
@ Мейсон Уилер, можете ли вы уточнить подробности или добавить какую-нибудь ссылку, потому что ваше утверждение мне не понятно, поскольку я не гуру C / C ++, а обычный программист, спасибо
Betlista
1
@Betlista: С помощью передачи по ссылке вы можете написать процедуру подкачки, которая выглядит следующим образом: temp := a; a := b; b := temp;И когда она вернется, значения aи bбудут поменяны местами. Там нет никакого способа сделать это в C; Вы должны передавать указатели на aи bи swapрутина должна действовать на значения, на которые они указывают.
Мейсон Уилер

Ответы:

58

Передача по значению часто безопаснее, чем передача по ссылке, потому что вы не можете случайно изменить параметры вашего метода / функции. Это упрощает использование языка, поскольку вам не нужно беспокоиться о переменных, которые вы передаете функции. Вы знаете, что они не будут изменены, и это часто то, что вы ожидаете .

Тем не менее, если вы хотите изменить параметры, вам нужно выполнить некоторую явную операцию, чтобы прояснить это (передать указатель). Это заставит всех ваших абонентов сделать вызов немного по-другому ( &variableв C), и это сделает явным, что параметр variable может быть изменен.

Так что теперь вы можете предположить, что функция не изменит параметр вашей переменной, если она не помечена для этого явным образом (требуя передать указатель). Это более безопасное и чистое решение, чем альтернатива. Предположим, что все может изменить ваши параметры, если они прямо не скажут, что не могут.

Oleksi
источник
4
+1 Массивы, распадающиеся на указатели, представляют заметное исключение, но общее объяснение хорошее.
dasblinkenlight
6
@dasblinkenlight: массивы - это больное место в C :(
Матье М.
55

Call-by-value и call-by-reference - это методы реализации, которые давно были приняты за режимы передачи параметров.

В начале был Фортран. У FORTRAN был только вызов по ссылке, поскольку подпрограммы должны были иметь возможность изменять свои параметры, а вычислительные циклы были слишком дорогими, чтобы разрешать множественные режимы передачи параметров, плюс было недостаточно известно о программировании, когда был впервые определен FORTRAN.

Алгол придумал вызов по имени и вызов по значению. Вызов по значению был для вещей, которые не должны были быть изменены (входные параметры). Вызов по имени был для выходных параметров. Обращение по имени оказалось крупным черепком, и АЛГОЛ 68 сбросил его.

PASCAL предоставил вызов по значению и вызов по ссылке. Это не дало программисту никакого способа сообщить компилятору, что он передает большой объект (обычно массив) по ссылке, чтобы избежать перебора стека параметров, но что объект не должен изменяться.

PASCAL добавил указатели в лексикон языкового дизайна.

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

Более поздние языки копировали C, в основном потому, что дизайнеры больше ничего не видели. Вероятно, поэтому так важен call-on-value.

C ++ добавил Kludge поверх C-Kludge, чтобы обеспечить вызов по ссылке.

Теперь, как прямой результат «вызов по значению», «вызов по ссылке» и «вызов по указателю-kludge», C и C ++ (программисты) испытывают ужасные головные боли с указателями на const и указателями на const (только для чтения) объекты.

Аде удалось избежать всего этого кошмара.

Ада не имеет явного вызова по значению против вызова по ссылке. Скорее, Ада имеет входящие параметры (которые могут быть прочитаны, но не записаны), выходные параметры (которые ДОЛЖНЫ быть записаны до того, как их можно будет прочитать) и выходные параметры, которые могут быть прочитаны и записаны в любом порядке. Компилятор решает, будет ли определенный параметр передан по значению или по ссылке: он прозрачен для программиста.

Джон Р. Штром
источник
1
+1. Это единственный ответ, который я видел здесь, который на самом деле отвечает на вопрос и имеет смысл, а не использует его в качестве прозрачного оправдания для разглагольствования про FP.
Мейсон Уилер
11
+1 за «Позже языки скопировали C, в основном потому, что дизайнеры больше ничего не видели». Каждый раз, когда я вижу новый язык с восьмеричными константами с 0 префиксами, я умираю немного внутри.
Либрик
4
Это может быть первое предложение Библии программирования: In the beginning, there was FORTRAN.
1
Что касается ADA, если глобальная переменная передается параметру in / out, помешает ли язык проверять или изменять ее вложенный вызов? Если нет, то я думаю, что между параметрами in / out и ref будет четкая разница. Лично я хотел бы, чтобы языки явно поддерживали все комбинации «in / out / in + out» и «по значению / по ref / not-care», чтобы программисты могли предложить максимальную ясность в своих намерениях, но оптимизаторы будет иметь максимальную гибкость в реализации [при условии, что это соответствует намерениям программиста].
суперкат
2
@Neikos Существует также тот факт, что 0префикс несовместим с любым другим префиксом неосновной десятки. Это может быть несколько извиняющим в C (и в основном в исходно-совместимых языках, например, C ++, Objective C), если восьмеричная была единственной альтернативной базой при представлении, но когда более современные языки используют и то, 0xи другое 0с самого начала, это просто заставляет их выглядеть плохо продуманный.
8bittree
13

Передача по ссылке учитывает очень тонкие непреднамеренные побочные эффекты, которые очень трудно или почти невозможно отследить, когда они начинают вызывать непреднамеренное поведение.

Проходим по значению, особенно final, staticили constвходные параметры делают весь этот класс ошибки исчезают.

Неизменяемые языки еще более детерминированы и их легче рассуждать и понимать, что происходит и что ожидается от функции.


источник
7
Это одна из тех вещей, которые вы выясняете после попытки отладки «древнего» кода VB, где все по умолчанию используется по умолчанию.
jfrankcarr
Может быть, просто мой фон отличается, но я понятия не имею, о каких «очень тонких непреднамеренных побочных эффектах, которые очень трудно почти невозможно отследить», вы говорите. Я привык к Паскалю, где значение по умолчанию является значением по умолчанию, но путем ссылки можно использовать явную пометку параметра (в основном ту же модель, что и в C #), и у меня никогда не возникало таких проблем. Я могу видеть, как это было бы проблематично в «древнем VB», где по-умолчанию используется значение по-умолчанию, но когда опция «по-ref» включена, это заставляет вас думать об этом во время написания.
Мейсон Уилер
4
@MasonWheeler Что касается новичков, которые приходят за вами и просто копируют то, что вы делали, не понимая этого, и начинают косвенно манипулировать этим состоянием повсюду, реальный мир происходит постоянно. Меньше косвенности - это хорошо. Неизменным является лучшим.
1
@MasonWheeler Простые примеры псевдонимов, такие как Swapхаки, которые не используют временную переменную, хотя сами по себе вряд ли будут проблемой, показывают, насколько сложным может быть отладка более сложного примера.
Марк Херд
1
@MasonWheeler: Классический пример в FORTRAN, который привел к поговорке «Переменные не будут; константы нет», будет передавать константу с плавающей точкой, например 3.0, в функцию, которая изменяет переданный параметр. Для любой константы с плавающей точкой, передаваемой в функцию, система создаст «скрытую» переменную, которая была инициализирована с правильным значением и может быть передана в функции. Если функция добавит 1.0 к своему параметру, произвольное подмножество 3.0 в программе может внезапно стать 4.0.
суперкат
8

Почему так много языков передается по значению?

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

Даже в тех языках, где у вас есть явные манипуляции с указателями, такие как C, они всегда передаются по значению (вы можете передавать их по ссылке, но это не поведение по умолчанию).

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

Какова польза от этого, почему так много языков передаются значениями и почему другие передаются по ссылке ?

Существует две основные причины использования передачи по ссылке:

  1. моделирование нескольких возвращаемых значений
  2. эффективность

Лично я считаю, что № 1 - фальшивка: это почти всегда оправдание плохого API и / или языкового дизайна:

  1. Если вам нужно несколько возвращаемых значений, не имитируйте их, просто используйте язык, который их поддерживает.
  2. Вы также можете смоделировать несколько возвращаемых значений, упаковав их в некоторую облегченную структуру данных, такую ​​как кортеж. Это работает особенно хорошо, если язык поддерживает сопоставление с образцом или деструктурирование связывания. Например, Руби:

    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 не является строгим.

На самом деле, спецификация Haskell не определяет какую-либо конкретную стратегию оценки. Большинство реализаций Hakell используют смесь вызовов по имени и вызова по необходимости (вариант вызова по имени с запоминанием), но стандарт этого не требует.

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

Йорг Миттаг
источник
9
«Если вам нужно несколько возвращаемых значений, не имитируйте их, просто используйте язык, который их поддерживает». Это немного странно сказать. Это в основном говорят « , если ваш язык может сделать все , что нужно, но одна особенность , что вам , возможно , потребуется менее чем 1% вашего кода - но вам все равно будет нужен для того, что 1% - не может быть сделано в особенно чистый способ, тогда ваш язык недостаточно хорош для вашего проекта, и вы должны переписать все это на другом языке ». Извините, но это просто смешно.
Мейсон Уилер
+1 Я полностью согласен с этим . Смысл разбивать большие программы на маленькие подпрограммы состоит в том, что вы можете самостоятельно рассуждать о подпрограммах. Передача по ссылке нарушает это свойство. (Как и общее изменяемое состояние.)
Rémi
3

Поведение зависит от модели вызова языка, типа аргументов и моделей памяти языка.

При использовании простых нативных типов передача по значению позволяет передавать значение по регистрам. Это может быть очень быстро, так как значение не нужно ни загружать из памяти, ни сохранять обратно. Подобная оптимизация также возможна просто путем повторного использования памяти, используемой аргументом, как только вызываемый объект завершил работу, не рискуя связываться с копией объекта вызывающей стороны. Если бы аргумент был временным объектом, вам, вероятно, следовало бы сохранить полную копию (C ++ 11 делает этот вид оптимизации еще более очевидным с новой правой ссылкой и ее семантикой перемещения).

Во многих ОО-языках (в данном контексте C ++ является скорее исключением) вы не можете передать объект по значению. Вы вынуждены передать его по ссылке. Это делает код полиморфным по умолчанию и в большей степени соответствует представлению об экземплярах, соответствующих OO. Кроме того, если вы хотите передать по значению, вы должны сделать копию самостоятельно, признавая стоимость производительности, которая сгенерировала такое действие. В этом случае язык выбирает для вас подход, который, скорее всего, даст вам наилучшую производительность.

В случае функциональных языков, я думаю, передача по значению или ссылке - это просто вопрос оптимизации. Поскольку функции в таком языке чистые и не имеют побочных эффектов, на самом деле нет смысла копировать значение, кроме скорости. Я даже почти уверен, что такой язык часто использует одну и ту же копию объектов с одинаковыми значениями, возможность доступна только в том случае, если вы используете проходную (постоянную) ссылочную семантику. Python также использовал этот прием для целочисленных и общих строк (таких как методы и имена классов), объясняя, почему целое число и строки являются постоянными объектами в Python. Это также помогает снова оптимизировать код, позволяя, например, сравнение указателей вместо сравнения содержимого и выполнять ленивую оценку некоторых внутренних данных.

Фабьен Нинолес
источник
2

Вы можете передать выражение по значению, это естественно. Передача выражения по (временной) ссылке ... странно.

Херби
источник
1
Кроме того, передача выражения по временной ссылке может привести к плохим ошибкам (в языке с сохранением состояния), когда вы удачно меняете его (так как это просто временно), но тогда это приводит к обратным последствиям, когда вы фактически передаете переменную, и вы должны придумать уродливый обходной путь, такой как передача foo +0 вместо фу.
Herby
2

Если вы переходите по ссылке, то вы фактически всегда работаете с глобальными значениями, со всеми проблемами глобальных параметров (а именно с областью действия и непреднамеренными побочными эффектами).

Ссылки, как и глобальные, иногда полезны, но они не должны быть вашим первым выбором.

jmoreno
источник
3
Я полагаю, вы имели в виду передать по ссылке в первом предложении?
JK.