нетехнические преимущества неизменяемости строкового типа - PullRequest
17 голосов
/ 27 августа 2010

Мне интересно узнать о преимуществах неизменности строкового типа с точки зрения программистов.

Технические преимущества (на стороне компилятора / языка) сводятся, главным образом, к тому, что оптимизировать легче, если тип неизменен. Прочитайте здесь , чтобы узнать связанный вопрос.

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

Но каковы преимущества использования неизменяемых строковых типов? Какой смысл иметь неизменяемые типы, а другие нет? Это кажется мне очень противоречивым.

В C ++, если я хочу, чтобы какая-то строка была неизменной, я передаю ее как константную ссылку на функцию (const std::string&). Если я хочу получить изменяемую копию исходной строки, я передаю ее как std::string. Только если я хочу, чтобы он был изменяемым, я передаю его как ссылку (std::string&). Так что у меня просто есть выбор, что я хочу сделать. Я могу сделать это с любым возможным типом.

В Python или в Java некоторые типы являются неизменяемыми (в основном все примитивные типы и строки), другие - нет.

В чисто функциональных языках, таких как Haskell, все неизменно.

Есть ли веская причина, почему имеет смысл иметь это несоответствие? Или это просто по техническим причинам низкого уровня?

Ответы [ 8 ]

16 голосов
/ 27 августа 2010

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

Без некоторых изменчивых типов, вам бы пришлось пойти на чистую свиньюфункциональное программирование - совершенно другая парадигма, чем ООП и процедурные подходы, которые в настоящее время наиболее популярны, и, хотя они чрезвычайно мощны, по-видимому, очень сложны для многих программистов (что происходит, когда вам do нужны побочные эффекты вязык, в котором нет ничего непостоянного, а в реальном программировании, конечно, вы неизбежно это делаете, является частью проблемы - например, Monads на Haskell - это очень элегантный подход, но сколько программистов вы знаете?которые полностью и уверенно понимают их и могут использовать их так же, как и типичные конструкции ООП? -).

Если вы не понимаете огромной ценности наличия нескольких парадигм (как FP one , так и * 1014)* те, которые в основном полагаются на изменчивые данные), я рекомендую изучить шедевр Хариди и Ван Роя, Концепции, методы и модели компьютерного программирования - "a SICP для 21-го века", как я однажды описал это; -).

Большинство программистов,знакомы ли они с Хариди и Ван Роем или нет, с готовностью признают, что для них важно иметь как минимум некоторые изменяемые типы данных.Несмотря на предложение, которое я цитировал выше из вашего Q, которое принимает совершенно иную точку зрения, я полагаю, что это также может быть корнем вашей растерянности: не «почему некоторые из каждого», а скорее «почему некоторые immutables at all ".

Подход" полностью изменяемый "был однажды (случайно) получен в реализации на Фортране.Если бы у вас было, скажем,

  SUBROUTINE ZAP(I)
  I = 0
  RETURN

, то программный фрагмент, например,

  PRINT 23
  ZAP(23)
  PRINT 23

напечатал бы 23, тогда 0 - число 23 былобыл изменен, поэтому все ссылки на 23 в остальной части программы на самом деле ссылались бы на 0. Технически, это не ошибка компилятора: у Fortran были тонкие правила относительно того, что представляет собой ваша программа, и ей запрещено делать передачу констант против переменных.к процедурам, которые присваивают их аргументы, и этот фрагмент нарушает эти малоизвестные, не обязательные для исполнения правила, так что это не программа, а компилятор.На практике, конечно, количество ошибок, вызванных этим способом, было неприемлемо высоким, поэтому типичные компиляторы вскоре переключались на менее разрушительное поведение в таких ситуациях (помещая константы в сегменты только для чтения, чтобы получить ошибку времени выполнения, если ОС поддерживала это, илипередача свежей копии константы, а не самой константы, несмотря на накладные расходы и т. д.), хотя технически они были программными ошибками, позволяющими компилятору отображать неопределенное поведение довольно "правильно" ;-).

Альтернатива, применяемая в некоторых других языках, состоит в том, чтобы добавить усложнение нескольких способов передачи параметров - особенно в C ++, например, с помощью значения-значения, ссылки-ссылки, константы-ссылки, указателя,постоянный указатель, ... и затем, конечно, вы видите программистов, сбитых с толку объявлениями, такими как const foo* const bar (где самый правый const в основном не имеет значения, если bar является аргументом для некоторой функции ... но имеет решающее значение, если bar является локальной переменной ...! -).

На самом деле Алгол-68, вероятно, пошел дальше в этом направлении (если вы можете иметь значение и ссылку, почему бы не ссылка на ссылку?или ссылка на ссылку на ссылку?& c - Algol 68 не накладывает на это никаких ограничений, и правила для определения того, что происходит, являются, пожалуй, самым тонким, самым сложным сочетанием, когда-либо существовавшим в языке программирования «предназначенный для реального использования»).Ранний C (который имел только by-value и by-явный указатель - без const, без ссылок, без осложнений) был, без сомнения, частично реакцией на него, как и оригинальный Паскаль.Но const вскоре закрался, и осложнения снова начали нарастать.

Java и Python (среди других языков) прорезают эту чащу мощным мачете простоты: передача всех аргументов, и все присваивания, "по ссылке на объект" (никогда не ссылается на переменную или другоессылка, никогда не семантически неявные копии и т. д.).Определение (по крайней мере) чисел как семантически неизменных сохраняет здравомыслие программистов (а также этот драгоценный аспект простоты языка), избегая таких «упущений», как те, что были продемонстрированы в вышеприведенном коде Фортрана.

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

Python пошел немного дальше, добавив простой неизменяемый контейнер (tuple) и связав hashing с «эффективной неизменяемостью» (которая избегает определенныхсюрпризы для программиста, которые встречаются, например, в Perl, с его хешами, допускающими изменяемые строки в качестве ключей) - а почему бы и нет?Если у вас есть неизменность (драгоценная концепция, которая избавляет программиста от необходимости изучать N различных семантик для присваивания и передачи аргументов, когда N имеет тенденцию к увеличению со временем ;-), вы также можете извлечь из этого все возможное ;-).

2 голосов
/ 30 августа 2010

Я не уверен, что это квалифицируется как нетехническое, тем не менее: если строки являются изменяемыми, то большинству (*) коллекций необходимо делать частные копии своих строковых ключей.

В противном случае клавиша «foo», внешне измененная на «bar», приведет к тому, что «bar» будет находиться во внутренних структурах коллекции, где ожидается «foo». Таким образом, при поиске «foo» будет найден «bar», что представляет меньшую проблему (ничего не вернуть, переиндексировать вызывающий ошибку ключ), но поиск «bar» не найдет ничего, что является большей проблемой.

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

1 голос
/ 30 августа 2010

Основным преимуществом для программиста является то, что с изменяемыми строками вам никогда не нужно беспокоиться о том, кто может изменить вашу строку.Следовательно, вам никогда не придется сознательно решать «Должен ли я скопировать эту строку здесь?».

1 голос
/ 27 августа 2010

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

Класс StringBuilder довольно хорош, хотя я думаю, что было бы лучше, если бы у него было свойство «Значение» (read был бы эквивалентен ToString, но он бы отображалсяв инспекторах объектов запись позволила бы непосредственно установить весь контент) и расширяющее преобразование по умолчанию в строку.Теоретически было бы неплохо, чтобы тип MutableString происходил от общего предка со String, поэтому изменяемая строка может быть передана функции, которой не важно, является ли строка изменчивой, хотя я подозреваю, что оптимизации, основанные на фактечто строки имеют определенную фиксированную реализацию, были бы менее эффективны.

1 голос
/ 27 августа 2010

В языке со ссылочной семантикой для пользовательских типов наличие изменяемых строк было бы ужасным, потому что каждый раз, когда вы присваиваете строковую переменную, вы бы создавали псевдоним изменяемого строкового объекта, и вам приходилось бы делать защитные копии во всемместо.Вот почему строки являются неизменяемыми в Java и C # - если строковый объект является неизменным, не имеет значения, сколько переменных указывают на него.

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

1 голос
/ 27 августа 2010

Если вам нужна полная согласованность, вы можете сделать все неизменным , потому что изменяемые Bools или Ints просто не имеют никакого смысла.Фактически, некоторые функциональные языки делают это.

Философия Python - «Простое лучше, чем сложное».В C вы должны знать, что строки могут меняться, и думать о том, как это может повлиять на вас.Python предполагает, что сценарий использования по умолчанию для строк - это «соединить текст» - для этого абсолютно ничего не нужно знать о строках.Но если вы хотите изменить ваши строки, вам просто нужно использовать более подходящий тип (то есть списки, StringIO, шаблоны и т. Д.).

1 голос
/ 27 августа 2010

Не уверен, что вы посчитаете это преимуществом «технического низкого уровня», но тот факт, что неизменяемая строка неявно является поточно-ориентированной, экономит вам много усилий при программировании для обеспечения безопасности потока.

Слегка игрушечный пример ...

Тема A - Проверить пользователя с именем входа FOO имеет разрешение на что-то, вернуть true

Тема B - Изменить строку пользователя для имени входаBAR

Поток A - Выполните некоторую операцию с именем входа BAR из-за предыдущей проверки разрешения, переданной для FOO.

Тот факт, что String не может измениться, избавляет вас от необходимости защититься от этого.

1 голос
/ 27 августа 2010

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

Семантически, они должны быть неизменными, нет? Строка "hello" всегда должна представлять "hello". Вы не можете изменить это больше, чем вы можете изменить номер три!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...