Racket: Строки и неизменяемость
В большинстве языков программирования высокого уровня строки неизменяемы. Это свойство уже практически стало стандартом. В Racket же строки по умолчанию изменяемые! Но неизменяемые строки также существуют. Разберёмся же, как с этой двойственностью работать.
Изменяемость как свойство
Изменяемые и неизменяемые строки не относятся к разным типам. Устойчивость к изменению — это свойство каждого конкретного строкового значения. Узнать, какая перед нами строка, можно с помощью предиката immutable?
. Посмотрим его в действии:
(immutable? "a") ; #t
(immutable? (string #\a)) ; #f
(immutable? (make-string 1 #\a)) ; #f
(string? "a") ;#t
(string? (string #\a)) ;#t
Видно, что строковые литералы неизменяемы, а строки, созданные с помощью функций таковыми не являются, но при этом и те, и другие являются строками!
Большинство операций над строками оперирует любыми строками вне зависимости от свойства неизменяемости, однако возвращают почти все функции изменяемые строки. В большинстве случаев возвращается новая строка, так что беспокоиться о потенциальном изменении существующих строк практически не приходится. Кроме того, функции, изменяющие строку-аргумент, обычно имеют восклицательный знак (!
) в конце имени, что облегчает чтения кода с подобными побочными эффектами.
Преобразование строк в неизменяемые и обратно
Любую изменяемую строку можно сделать неизменяемой, создав её неизменяемую копию с помощью функции string->immutable-string
. Обратное преобразование не требует отдельной функции, вместо неё используется функция string-copy
, которая создаёт изменяемую копию любой строки.
Неизменяемые строки без потери неизменяемости можно только конкатенировать, используя отдельную функцию string-append-immutable
. Любые другие виды обработки потребуют работы с изменяемыми строками с преобразованием в неизменяемую строку результата.
Предназначение неизменяемых строк
Неизменяемые строки ценны… своей неизменяемостью: строковая константа, содержащая строковый литерал, всегда будет иметь одно и то же значение, ключ хеш-таблицы тоже должен быть неизменяемым, чтобы хеш-функция вычисляла для него одно и то же значение при обращении к таблице по ключу. Даже в памяти неизменяемые строки хранятся более компактно!
Но есть у неизменяемости и обратная сторона: неизменяемые строки очень невыгодно обрабатывать: даже конкатенация потребует выделения памяти в количестве, равном суммарной длине всех объединяемых строк. И если в неизменяемой строке нужно поменять ровно один символ, то потребуется полная копия.
А вот изменяемые строки интерпретатор может использовать более эффективно. Особенно хорошо ему работать с изменяемой строкой фиксированной длины и заменять её части посимвольно или целиком, не меняя эту самую длину — в этом случае новая память выделяться не будет!
Модификация изменяемой строки
Рассмотрим две функции, которые модифицируют содержимое изменяемой строки "по месту", не создавая изменённой копии. Это будут функции string-set!
и string-copy!
. Как уже было отмечено выше, восклицательный знак в имени означает оказание эффекта на изменяемый аргумент.
string-set!
заменяет символ по указанному индексу на заданный:
(define s (make-copy "Cat")) ; изменяемая копия!
(string-set! s 0 #\B)
(string-set! s 1 #\o)
(displayln s) ; => Bot
string-copy!
копирует одну строку в середину другой, размещая копию в некоторой позиции. Опционально можно указать, какой участок копируемой строки будет вставлен в модифицируемую. Вот так функция применяется:
(define names "Bob,Tom")
(define s (make-string 10 #\.))
(displayln s) ; => ..........
(string-copy! s 3 " VS ")
(displayln s) ; => ... VS ...
(string-copy! s 0 names 0 3)
(displayln s) ; => Bob VS ...
(string-copy! s 7 names 4 7)
(displayln s) ; => Bob VS Tom
Строку можно копировать и саму в себя:
(define s (string-copy ".Tod"))
(string-copy! s 0 s 1 4)
s ; "Todd"
Задание
Реализуйте функцию scroll-left!
, которая должна "прокручивать" изменяемую строку-аргумент так, чтобы первый символ попадал в конец строки, а остальные символы, начиная со второго сдвигались на одну позицию влево. Пустую строку модифицировать не нужно. Возвращать изменённую строку тоже не следует — функция должна только модифицировать свой аргумент!
Примеры:
(define s (string-copy "abc"))
(scroll-left! s)
s ; "bca"
(scroll-left! s)
s ; "cab"
Пожалуйста, авторизуйтесь, это необходимо для отслеживания прогресса выполнения уроков. Если у вас ещё нет учётной записи, то сейчас самое время создать аккаунт.