Previous Entry Share Next Entry
Приключения Unicode в Erlang.
elisitsky
Первое, на что я натолкнулся, начав изучать Эрланг - это проблемы со строками, отягощенные всей юникодностью нашего алфавита. Пожалуй, будь моим родным языком английский (или латынь или любой другой с алфавитом, полностью помещающимся в первые 128 символов utf-8), часть проблем бы я просто не заметил, и даже считал, что так оно и должно быть и я все делаю правильно. К счастью, мне сразу пришлось разбираться со всеми особенностями.

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

Строкой здесь считается список int`ов, которую можно напечатать на экране. Поэтому когда вы пишете
> Str = "ABCDEF".
то на самом деле вы создаете никакую не строку, а а список чисел [65,66,67,68,69,70]. В этом легко убедиться:
> [65,66,67,68,69,70] =:= Str.
true

Но и это тоже еще не фатально, во многих языках строка часто мимикрирует под массив, что позволяет удобно работать с ней как с массивом. И здесь вы так же получаете похожие возможности, например, так можно взять 3-ий символ строки:
> Char = lists:nth(3, Str).
67
Я думаю вас уже не удивило появление здесь именно 67, а не "С"? Потому что раз нет строк, то и символы тоже не нужны. Символ - это просто его код в таблице.
Если хотите увидеть как он выглядит на экране, напечатайте его как символ:
> io:format("~c~n", [Char]).
C
ok

Вторая важная мысль. Как еще можно представить строку текста? Конечно, как последовательность байт. Что, кстати говоря, выглядит даже логичнее. В любом случае, когда вы эту строку будете куда-то отправлять или записывать в файл, запишется именно последовательность байт.
Для бинарных данных есть специальный объект - binary.
Здесь все просто - последовательность байт она и в Африке последовательность байт:
> SB = <<"ABCDEF">>.
<<"ABCDEF">>

Можно убедиться, что это последовательность тех же самых байт, что и в списке:
> <<"ABCDEF">> =:= <<65,66,67,68,69,70>>.
true


С латиницей разобрались, казалось бы что нового может дать unicode?
Но вот тут-то и начинается веселье. Беда в том, что в список символов записывается не его представление в utf-8, а его номер в таблице unicode - тот самый code point.
Именно поэтому строка "АБВГД" так сильно отличается от своего латинского аналога:
> "АБВГД".
[1040,1041,1042,1043,1044]

А вот двоичное представление этой строки в utf-8 совсем другое:
> RB = unicode:characters_to_binary("АБВГД").
<<208,144,208,145,208,146,208,147,208,148>>

И если мы этот бинарь побайтово напечатаем, то получим не тоже самое, что в случае первого списка:
> io:fwrite("~w~n", [erlang:binary_to_list(RB)]).
[208,144,208,145,208,146,208,147,208,148]
ok


Что с этим делать?
Выбрать правильный формат и применять по месту. При необходимости одно можно конвертировать в другое. Для этого вам поможет ряд полезных функций:

1) RB = unicode:characters_to_binary("АБВГД"). % из списка юникодных символов делает бинарь в utf-8
<<208,144,208,145,208,146,208,147,208,148>>

2) unicode:characters_to_list( <<208,144,208,145,208,146,208,147,208,148>> ). % из бинаря в utf-8 делает список
[1040,1041,1042,1043,1044]
Вы же еще помните, что так выглядит строка "АБВГД"?

3) unicode:characters_to_list("АБВГД"). % причем вы можете "скормить" и список, только смысла в этом немного
[1040,1041,1042,1043,1044]

  • 1
Было б неплохо, если бы еще было написано про xmerl_ucs

Очень давно не встречал xmerl_ucs в бою. В основном статьи про него относятся к 2007 году. Тогда было несколько вариантов обработки юникода.
Сейчас по идее ту же функциональность по кодированию-раскодированю сейчас обеспечивают стандартные модули.
Если там есть что-то интересное - дайте знать.

Я тоже хотел бы узнать, кто что использует :)
я нашел полезность только в функциях is_*

Мне кажется после принятия EEP-0010 разрабатывать какие-то специфические решения не только ну нужно, но и даже вредно. Да мне самому там не все нравится, но если это стандарт, то больше пользы будет от следования ему, как минимум от совместимости. А то 6 несовместимых JSON-парсеров - курам на смех.

Было бы интересно встретить человека, которому родным языком является латынь. %)

Я это для хохмы написал, вы - первый, кто обратил внимание :)

Не надо приводить UTF-8 к внутреннему представлению в code points. Оставляйте всё в UTF-8, пускай строки соответствуют "раздербаненному" бинарнику с UTF-8 внутри.

Список с character codes, вместо списка с utf-8 sequences — это бред, который не решает поставленной задачи.

Спасибо, Лев!

Мне тоже кажется, что форма в виде бинарного объекта более естественна для строки. Но в этом случае мы лишаемся некоторых возможностей: например, проверить длину в символах (например, у меня стоит ограничение, что логин должен быть не меньше 3 символов), вхождение различных подстрок, то, что логин скажем не содержит запрещенных символов. Со списками это все просто, но с бинарями как быть?
И не возникнет ли у нас новый вид ошибок поиска - когда заматчатся половины букв? Скажем второй байт первого символа и первый байт второго?

А как вы вводите бинари с русскими символами в текст программы?
Конструкция Msg = "абв", выполеннная в консоли, дает мне список [1072,1073,1074]. Разумный ожидаемый результат. при печате на экране тоже то, что нужно.
Если я пишу в консоли Msg2 = <<"абв"/utf8>>, то получаю ошибку
** exception error: bad argument.

Если я тоже самое напишу в отдельном .erl файле скомпилирую его, то в первом случае у меня будет получаться такой список: [208,176,208,177,208,178] , который содержит бред, но по счастливой случай правильно отображается на экране.
А вот второй случай начинает компилироваться, но совсем весело: <<195,144,194,176,195,144,194,177,195,144,194,178>> - выходит вот такая 12-байтная колбаса из 3 букв. Судя по всему, это дважды неверный юникод, из-за чего его и раздуло в 4 раза.
Подозреваю, что компилятор воспринимает строки как ASCII не юникод, из-за чего вся ерунда и идет. Но как указать кодировку файла? В питоне это делается очень просто
-*- coding: utf-8 -*-. А как быть в эрланге?

Или вообще русского текста не должно быть в программе? Нужно сразу все выносить в gettext? Но это не всегда удобно.

Спасибо.

Мне тоже кажется, что форма в виде бинарного объекта более естественна для строки.

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

Но в этом случае мы лишаемся некоторых возможностей

Ничего мы такого не лишаемся, чего получаем, храня code points в списках. Подробнее здесь: http://lionet.livejournal.com/56779.html

И не возникнет ли у нас новый вид ошибок поиска - когда заматчатся половины букв? Скажем второй байт первого символа и первый байт второго?

Хранение в code points этой проблемы не решает. Подробнее здесь: http://lionet.livejournal.com/56779.html в частности, см. про combining characters.

А как вы вводите бинари с русскими символами в текст программы?

Честно говоря, не вводим. Код пишем на эрланге, комментарии на английском, а локализация — она вне программы, в отдельных сущностях (база или файлы).

Если я пишу в консоли Msg2 = <<"абв"/utf8>>, то получаю ошибку

Это меня самого раздражает.

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

Почему же это бред? Это UTF-8, именно так и должно быть.

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

Значит я вас неверно понял. Меня сильно смущает оверхед на хранение и обработку - что нам постоянно надо "бегать" по указателям для любых операций.

> Ничего мы такого не лишаемся, чего получаем, храня code points в списках.
Если список, то да - нам должны быть доступны все списковые операции и модуль string тоже.

> Если я пишу в консоли Msg2 = <<"абв"/utf8>>, то получаю ошибку
> Это меня самого раздражает.
Можно ли как-то с этим бороться? Чтобы поведение было одинаковым? Иначе очень не удобно - нельзя отладить программу в консоли, быстро проверить какую-нибудь мысль и т.п.

> Почему же это бред? Это UTF-8, именно так и должно быть.
Да, это utf-8. Но запись не соответствует EEP-10. По идее должны быть либо code point в списке, либо в виде байт бинарного объекта.
А такая запись - гибрид, который неясно как обрабатывать. Как например, ему сделать uppercase?

  • 1
?

Log in