Haskell: Как да проверите дали char е валиден utf8

Как да проверя дали Char в haskell е валидна UTF8 кодова точка?

Имам клас, който генерира низове, предмет на някакъв набор от ограничения, и Arbitrary екземпляр на този клас (който генерира само низове, които отговарят на тези ограничения). Използвам GenValidity с това. Но стандартният генератор за String генерира случайни невалидни знаци; като '\xed'. Не е изненадващо, че това причинява проблеми по-късно.

Под „невалиден“ имам предвид, че Data.Text.Encoding.streamDecodeUtf8 отбелязва грешка:

λ> streamDecodeUtf8 (Data.ByteString.Char8.pack "\xed")
Some "" "\237" _

Бих искал да добавя ограничение към моя екземпляр на GenValidity, което се основава на (хипотетична) isValidUTF8 :: Char -> Bool функция, но изненадващо не мога да намеря нищо, което да съответства. Най-доброто, което мога да направя, е

((\ (Data.Text.Encoding.Some _ x _) -> x /= "") . Data.Text.Encoding.streamDecodeUtf8With (\ _ _ -> Nothing) . Data.ByteString.Char8.pack) . pure

Което със сигурност е доста тежко и се притеснявам, че преобразуването в ByteString, след това в Text, може да доведе до остри ръбове.

Изненадан съм, че не мога да намеря нищо по-добро/предварително консервирано.

Съвети и насоки са добре дошли!


person user3416536    schedule 13.05.2020    source източник
comment
Не съм сигурен, че разбирам изискването. UTF8 е само едно от байтовите предавания на Unicode. Обектите на Haskell Char са Unicode точки. Генераторът на низове има пълното право да включва знак, чийто цифров код е 0xED=237, това е i акут 'í'. Може да искате да опитате да оцените под ghci: GHC.Unicode.isPrint $ chr(237) и putStrLn $ ((chr 237):"") Разбира се, в началото на забранения диапазон на Unicode isPrint $ chr 55296 връща False.   -  person jpmarinier    schedule 13.05.2020
comment
Честно казано, не съм сигурен откъде получих "\xed" - това беше избледняване на мозъка. Трябваше да е '\xda65'. Въпреки че вече не съм сигурен, че и това е невалиден знак. Със сигурност моят нещастен пример fn не го маркира като невалиден.   -  person user3416536    schedule 13.05.2020
comment
Всъщност го намерих и преработих въпроса, за да съответства. Основният проблем е с Data.Text.Encoding.streamDecodeUtf8, който изглежда смята, че '\xed' е лошо.   -  person user3416536    schedule 13.05.2020
comment
Що се отнася до UTF-8, \xed е непълна последователност. Не можете да генерирате произволна последователност от байтове и да се надявате, че това ще бъде валиден UTF-8 низ. Ако има проблем от страната на производителя, отстраняването на невалидни низове от страна на потребителя може да отнеме цяла вечност; по-добре оправи продуцента.   -  person n. 1.8e9-where's-my-share m.    schedule 13.05.2020
comment
Израз isPrint $ chr (read "0xda65") връща False, това е в невалидния диапазон D800-DFFF. Може да се наложи да филтрирате продукцията на вашия генератор на ниво Char/String.   -  person jpmarinier    schedule 13.05.2020
comment
А, да, и двамата сте прави - благодаря ви.   -  person user3416536    schedule 13.05.2020


Отговори (1)


Тези примери работят според очакванията.

Въпросът „Как да проверя дали Char е валиден UTF-8“ няма смисъл (но не можете да бъдете обвинявани, че не знаете какво не знаете). Базира се на неразбиране на това какво е UTF-8. UTF-8 е кодиране: той описва един от начините за превръщане на тези кодови точки в байтове, които могат да се съхраняват или изпращат по мрежата.

За да направим аналогия, това е като да попитате „Как да проверя дали Integer е валидна основа 10“. Помислете защо това няма смисъл.

Кодирането е свойство на „конкретни данни“ като „байтови низове“ (последователности от байтове, това са ByteString типовете в Haskell). След като тези байтове бъдат декодирани, имаме само "текст" и UTF-8 вече не е уместна концепция на това ниво на абстракция (последователности от кодови точки (Char), това е Text или String в Haskell).

Но стандартният генератор за String генерира случайни невалидни знаци; като '\xed'.

Всички Chars са валидни [1] Unicode кодови точки. (Вижте документа.) '\xed' е кодова точка номер 237.

[1]: за някаква дефиниция на "валиден"... Unicode крие много сложност.

Под „невалиден“ имам предвид, че Data.Text.Encoding.streamDecodeUtf8 отбелязва грешка:

λ> streamDecodeUtf8 (Data.ByteString.Char8.pack "\xed")
Some "" "\237" _

streamDecodeUtf8 е предназначен да се прилага към UTF-8 байтови низове, но Data.ByteString.Char8.pack не произвежда UTF-8. Char8.pack е по-скоро хак за емулиране на литерали от байтестни низове; той злоупотребява с Unicode, за да заобиколи факта, че в Haskell има само литерали за Unicode низове. Но тук не искате да генерирате произволни байтове, така че не използвайте Char8.pack.

За да кодирате текст, използвайте един от функциите за кодиране от модула Data.Text.Encoding. Както можем да видим, има различни функции за различни кодировки, което допълнително илюстрира горната точка, че "кодирането" не е свойство, присъщо на текста, а е свързано с неговото представяне в паметта (което ByteString излага).

ghci> import Data.Text (pack)
ghci> import Data.Text.Encoding
ghci> streamDecodeUtf8 (encodeUtf8 (Data.Text.pack "\xed"))
Some "\237" "" _

Изненадан съм, че не мога да намеря нищо по-добро/предварително консервирано.

Има много оплаквания относно текста в Haskell и програмирането като цяло, но в този случай въпросът произтича от неразбиране на Unicode. Грешката не е във вас, тази система със сигурност не е очевидна, ако вече не сте запознати с нея.

person Li-yao Xia    schedule 13.05.2020
comment
Благодаря ти @Li-yao Xia. Смущавам се, че наистина трябва да знам по-голямата част от това, но съм благодарен за напомнянето и много ясното обяснение, тъй като бях забравил някои части и го хванах на врата си. Това, което казвате, има добър смисъл и наистина се опитвах да генерирам валидни низове от произволни байтове; и когато се отдръпнем, това наистина е глупост. Радвам се, че попитах, това ми спести много време и усилия да реша грешния проблем. - person user3416536; 13.05.2020