Как стать автором
Обновить

Комментарии 51

Не хватает опроса после статьи

Опрос добавил

Не хватает пункта - этот опрос сосёт.

Байтовые строки — индексация по байтам, символьные строки — индексация по символам.

Text и ByteString?

Поддерживаю. Я на днях долго геморроился с Unicode в Python 2.7, чтобы у меня в zip-архив нормально писались кириллические имена файлов. Так и не решил пока :(

Предлагаю изящное решение - перейти на питон 3.*

Второй уже три года как похоронили, есть куча гайдов по переезду и всякому сохранению совместимости

Я пока не совсем настоящий сварщик) В проекте 3 месяца. Перетащить сотню тысяч строк кода, писанного не мной, со второго на третий... Боюсь тимлид не одобрит такую инициативу, да и ссыкотно, если честно)

Тогда не вариант переводить всё. Но можно написать кусок кода на третьем питоне и вызывать через subprocess

У питона есть встроенная утилита 2to3, которая позволяет переводить код с 2.х на 3.х, вызывается через `python -m 2to3 файл`

Вообще нужен новый стандарт Unicode:) Там тоже хватает странностей, было бы хорошо от них избавиться.

Ну а что касается строк, то да, конечно лучше отдельный тип для строк, со специфическими функциями всякой "нормализации", изменения регистра, и т.п. В Qt например есть тип QString для строк и тип QByteArray для байтовых массивов, с близкими, но все-же отличающимися наборами методов (хотя я бы назвал тип не QByteArray а QBlob, было бы лаконичнее).

QString это же обертка над обычными std::string, только с QT прибамбахами

Это собственная реализация. И если дефолтный std::string хранит char без привязки к кодировки, то QString это всегда UTF-16 внутри.

Спасибо, не знал.

В питоне3 так и есть.

Язык программирования это интерфейс между разработчиком и средой исполнения. Выразительные средства языка должны зависеть от решаемых задач.

Если обработка текстов является типичной задачей для данного ЯП, то поддержку юникода есть смысл выносить на уровнь синтаксиса самого языка, чтобы сократить писанину. Если нет - то лучше оставить на откуп библиотекам. Поэтому ответ для python и c++ будет разным.

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

Я так понимаю такие попытки уже были. Именно поэтому PHP с пятой мажорной версии скакнул сразу на седьмую.

Что есть символ? Кластер графем, как это видит человек? Codepoint?

Для чего вообще нужен случайный доступ к символам? Как будет выглядеть представление таких строк в памяти?

Если речь про Unicode строки, то достаточно будет сделать доступ к code point. То тогда само собой решаются вопросы и с представлением строки в памяти и с индексацией, необходимостью конвертирования и т.д.

Но зачем доступ к code point? В общем случае это такая же абстракция. У него нет какого-либо значения, вот хорошая статья на тему.

Например, вот два code point: 🇦🇺. Если наивно "развернуть" это по code point'ам, получим другой флаг. Потому что не надо почти никогда разворачивать строчки в вакууме, если это игра — то наверняка игра под конкретный язык.

А вот один code point: ﷽ . Или вот несколько, но выделяются обычно как один: ᄀᄀᄀ각ᆨᆨ.

Потому что code point это минимально возможная единица данных для Unicode строк

И это объясняет индексацию по code point как именно? Вы вполне можете поступить как в Rust: индексация по code unit (в данном случае байтам), но попытка создать подстроку только с частью code point приводит к ошибке, требует предварительного преобразования строки в массив байт или требует использования unsafe и считается ошибкой программиста в случае успеха.
И вполне понятно, зачем они это сделали: так одновременно получается индексация за O(1) и при этом вы не платите в четыре раза больше байт за строку/не усложняете себе жизнь поддержкой трёх возможных размеров code unit и не занимаетесь постоянным перегоном в/из UTF-8. Реализации разных операций со строками это обычно либо совсем не усложняет, либо добавляет простые проверки на попадание на границы символов.

Вот люди привыкают к auto на C++ и привыкают, что 'a' это литерал char, а потом идут в Си и недоумевают, что происходит, потому ято там 'a' это литерал int.

auto здесь совсем не при чём.

Такое различие между языками.

Кстати , в C23 теперь тоже появится auto с выводом типа.

Auto скрывает от разрабатывает реальный тип, что сю часто приводит к ошибкам, если разраб не помнит всех правил вывода.

ИМХО, лучше явно тип указывать, либо сразу кастовать, типо

auto I = std::uint32_t(2);

Понятно, что из за разницы в языке, если в C23 auto ввели, количество багов увеличится при переходе с одного языка на другой.

А ещё это проблема слабой типизации.

Можно конкретный примерчик с "проблемой"? Где и какие сложности возникают? Или весь опус просто о лени и очередной вариации как бы хорошо иметь очередную кнопку "сделай офигенно"? За 15+ лет разработки на разных языках никакого геморроя с юникодом не испытывал.

P. S. : Тип auto - это какая то жесть. Вот потом и получается разработчик, который не понимает что делает и как все устроено под капотом.

Мне кажется, проблемы скорее не из-за юникода, а из-за всяких неверных предположений о языках при разработке (статья). Даже арабский (один из шести официальных языков ООН) на сайтах отображают неверно: https://isthisarabic.com/

А с эмоджи в текстах тоже никаких проблем не возникало? С русским-то проблем лишь чуть-чуть больше, чем с английским — просто меняем концепцию "один символ — один байт" на "один символ — один code point" и теперь работаем и с русским, и с английским. А вот эмоджи это разламывают.

просто меняем концепцию "один символ — один байт" на "один символ — один code point"

Это идеальный вариант, но он сработает только если будет два разных типа текстовых строк.

Поверх последовательности code-points нужен ещё один уровень абстракции: отображаемые символы. Иначе, придётся у себя в программе решать эту задачу.


Например, если символ состоит из 6 code-points (выше были примеры), а мы пишем текстовый редактор, и нужно при нажатии клавиши "вправо" перейти на следующий символ. Или если у нас бегущая строка, в буфер которой надо подкидывать вовсе не code-points, а печатные символы.

Поверх последовательности code-points нужен ещё один уровень абстракции: отображаемые символы. Иначе, придётся у себя в программе решать эту задачу.

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

То же можно сказать и о code-points. Массив байт это только хранилище данных, а интерпретация остаётся на усмотрение прикладного программиста.


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

То же можно сказать и о code-points. Массив байт это только хранилище данных, а интерпретация остаётся на усмотрение прикладного программиста.

Так об Unicode сказать нельзя, так как минимальная единица информации это именно codepoint, в противном случае можно скатиться в рассуждениях и до отдельных бит (ведь именно бит минимальная единица информации).

Вниз по абстракциям путь уже открыт — любой язык позволяет взять символ как (unsigned) int и инспектировать отдельные биты.

К сожалению code point тоже несколько видов и один единственный тип данных для хранения unicode строк сделать не получится.

Кажется, вы путаете code point и code unit.

Да, действительно попутал. Конечно же должно быть "code unit"

Любая программа, это абстракция над абстракцией. Было бы логичным использовать именно самый низкоуровневый элемент для хранения данных, и в случае Unicode это действительно code-point.
Правда их тоже несколько видов и если делать реализацию именно на уровне синтаксиса языка, то непонятно как это учесть. Возможно все как раз и скатится к тому виду, как это реализовано в С++ с разными вариантами code point.

Зачем тут тег "Rust" если он даже не упоминается?

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

А зачем отдельные байтовые строки, если можно использовать вектор байт?

А в чём проблема?

С++ позволяет создать собственный тип данных, думаю любой другой язык программирования с поддержкой ООП тоже, если кому-то нужна Unicode строка индексируемая по code-point или по отображаемым символам - он может сам реализовать её.

Только не получится ли, что такая строка вынуждена будет проверять все составные символы Unicode каждый раз когда выполняется индексирование, чтобы убедиться что очередной символ - не один из них и не требует специальной обработки?

Проблема в том, что этих собственных типов данных уже насоздавали дохрена. И вот начинаются преобразования туда-сюда между какими-нибудь char*, std::String и QString.

Встраивание Unicode в язык тоже решает проблему лишь на некоторое время. Например, когда создавался C# примерно в 2000-м, казалось, что UCS-2 будет достаточно, чтобы индексировать строку по codepoints. А потом пришли новые версии Unicode, и это допущение сломалось.


Введя сейчас в язык unicode-строки по самым последним стандартам, через 20 лет они тоже устареют, и нужно будет переделывать, а язык отправится в legacy. В этом смысле библиотеки лучше, их проще заменить (std::string → std::wstring → std::u32string → ...)

Ну в данном случае это исключительно косяк Майкрософта, тянущийся от изначально неудачного выбора в Win32. В Фортране, например, в 2003 году уже в стандарт ISO прописали UCS-4, а на уровне расширений это было ещё с 1995 года. Правда, большинство разработчиков компиляторов забило на этот стандарт :)

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

Но на самом деле никто же не просит прибивать гвоздями к языку (или библиотеке) конкретный способ кодирования и количество байтов на символ

То есть, вы считаете, что Microsoft без проблем сможет в своей System.String перейти на 32-битные символы? Мне кажется, слишком много unsafe-кода предполагают, что символы там 16-битные, и поменять это в языке уже невозможно, не сломав очень много старого кода.

Для этого с самого начала надо было прописать, что размер зависит от реализации. Теперь уж поздно.

Вряд ли имеет смысл выделять для Unicode отдельный тип данных.

Ведь тогда бы пришлось делать под каждый code unit делать отдельный тип? А если этого не сделать, тогда было бы как миниму 3 вида текстовых строк (для UTF8, UTF16 и UTF32). И как тогда с этим жить? Скобок на клавиатуре не хватит для указания конкретного типа строк.

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

Ведь тогда бы пришлось делать под каждый code unit делать отдельный тип?

А в чём проблема? У каждого типа своё назначение.

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

Библиотеки не запускаются во время компиляции, а c++ перфекционисты не потерпят, чтобы преобразование строки utf8 например в utf16le выполнялось при выполнении программы, когда это можно было бы сделать заранее при компиляции.


Сравните


auto str_utf16 = library::convert_to_utf16("hello");

против


auto str_utf16 = L"hello";
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории