Нетехнические преимущества наличия неизменяемого типа строки

Мне интересно, какие преимущества имеют строковый тип, неизменяемый с точки зрения программистов.

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

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

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

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

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

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

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

Ответ 1

В чем смысл типы неизменяемы, а другие нет?

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

Если вы не понимаете огромную ценность наличия нескольких парадигм (как FP, так и один из них, основанных на изменяемых данных), я рекомендую изучить шедевр Haridi и Van Roy, Концепции, методы и модели компьютерного программирования - "a SICP для 21-го века ", так как Я однажды описал это; -).

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

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

  SUBROUTINE ZAP(I)
  I = 0
  RETURN

то снимок программы, например,

  PRINT 23
  ZAP(23)
  PRINT 23

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

Тот факт, что строка не может измениться, избавляет вас от усилий по защите от этого.

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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